QListView가 각 항목의 sizeHint를 호출하지 못하게하는 방법은 무엇입니까?

Nov 20 2020

다양한 높이의 항목이 많은 QListView가 있습니다. 항목을 칠하기위한 사용자 지정 대리자를 구현하고 레이아웃 모드를 Batched로 설정합니다.

그러나 모델이 할당되면 목록보기는 모델의 모든 항목에 대해 sizeHint를 미리 요청하여 Batched 설정을 무시하고 크기를 계산하기 위해 대리자가 많은 텍스트를 레이아웃해야하므로 성능이 저하됩니다. ).

아마도 이것은 스크롤바 위치를 계산하기 위해 수행되지만, 항목 수가 많을 때 스크롤바 위치는 항목 높이를 고려하지 않고 항목 인덱스만을 기반으로 할 수 있다고 생각했습니다. 그러나 이것이 QListView가 작동하는 방식이 아닌 것 같습니다.

또한 모델에서 canFetchMore / fetchMore를 사용하려고했지만 이로 인해 사용자 경험이 나빠졌습니다. 스크롤바 위치가 더 이상 정확하지 않고 더 많은 항목이로드 될 때 목록이 점프하며 전혀 매끄럽지 않았습니다.

따라서 질문은 다음과 같습니다.

  1. QListView가 보이지 않는 항목에 대해 sizeHint를 호출하지 못하도록하는 방법이 있습니까?
  2. 유일한 방법이 canFetchMore / fetchMore를 사용하는 것이라면 어떻게 부드러운 스크롤과 안정적이고 정확한 스크롤바를 얻을 수 있을까요?

감사합니다!

UPD : 다음은이 동작을 재현하는 최소한의 예입니다.https://github.com/ajenter/qt_hugelistview

엄청난 시작 지연과 5000 개 항목 모두의 sizeHint가 미리 요청되었음을 보여주는 디버그 메시지에 유의하십시오.

답변

2 AlexJenter Nov 23 2020 at 20:05

글쎄, 내가 해결책을 찾은 것 같아서 같은 문제가 있고이 스레드를 googles하는 모든 사람들을 위해 여기에서 공유 할 것입니다.

우선, 이것이 실제로 2011 년에 등록 된 Qt의 버그이며 여전히 열려 있음을 발견했습니다. https://bugreports.qt.io/browse/QTBUG-16592

나는 그것에 내 투표를 추가했습니다 (그리고 당신도해야합니다!). 그런 다음 QListView 대신 QTableView를 사용하기로 결정했습니다. 그리고 놀랍게도 작동하도록 관리했습니다.

QListView와 달리 QTableView는 resizeRowToContents (rowNum)을 호출하여 명시 적 요청시 행 크기 만 조정합니다. 따라서 트릭은 뷰포트에 표시되는 행에 대해 Just-In-Time 방식으로 호출하는 것입니다.

내가 한 일은 다음과 같습니다.

  1. QTableView에서 상속 (MyTableView라고합시다)

  2. QListView를 MyTableView로 바꾸고 생성자에서 이와 같이 초기화하십시오. 사용자 지정 항목 대리자를 할당하고 테이블 머리글을 숨기고 "행별"선택 모드를 적용합니다.

   MyTableView::MyTableView(QWidget* parent) : QTableView(parent)
    {
       setSelectionBehavior(QAbstractItemView::SelectRows);
       horizontalHeader()->setStretchLastSection(true); 
       horizontalHeader()->hide();
       verticalHeader()->hide();
       setItemDelegateForColumn(0, new CustomDelegate(&table)); // for custom-drawn items
    }
  1. MyTableView에서 QItemSelection 개인 필드와 행의 실제 높이를 계산하는 공용 함수를 추가하지만 현재 보이는 행만 추가합니다.
QItemSelection _itemsWithKnownHeight; // private member of MyTableView

void MyTableView::updateVisibleRowHeights()
{
    const QRect viewportRect = table.viewport()->rect();

    QModelIndex topRowIndex = table.indexAt(QPoint(viewportRect.x() + 5, viewportRect.y() + 5));
    QModelIndex bottomRowIndex = table.indexAt(QPoint(viewportRect.x() + 5, viewportRect.y() + viewportRect.height() - 5));
    qDebug() << "top row: " << topRowIndex.row() << ", bottom row: " << bottomRowIndex.row();

    for (auto i = topRowIndex.row() ; i < bottomRowIndex.row() + 1; ++i)
    {
        auto index = model()->index(i, 0);
        if (!_itemsWithKnownHeights.contains(index))
        {
            resizeRowToContents(i);
            _itemsWithKnownHeights.select(index, index);
            qDebug() << "Marked row #" << i << " as resized";
        }
    }
}
  1. 참고 : 항목 높이가 컨트롤의 너비에 따라 달라지는 경우을 재정의 resizeEvent()하고 지우고 _itemsWithKnownHeightsupdateVisibleRowsHeight ()를 다시 호출해야합니다.

  2. MyTableView 인스턴스에 모델을 할당 한 후 updateVisibleRowHeights ()를 호출하여 초기보기가 올바른지 확인합니다.

   table.setModel(&myModel);
   table.updateVisibleRowHeights();

실제로 모델 변경에 반응하는 MyTableView의 일부 메서드에서 수행해야하지만 연습으로 남겨 두겠습니다.

  1. 이제 남은 것은 테이블의 세로 스크롤 위치가 변경 될 때마다 updateRowHeights를 호출하는 것입니다. 따라서 MyTableView의 생성자에 다음을 추가해야합니다.
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
        updateRowHeights();
    });

완료-100,000 개 항목의 모델에서도 정말 빠르게 작동합니다! 그리고 시작은 즉각적입니다!

이 기술의 기본 개념 증명 예제 (하위 클래스 대신 순수한 QTableView 사용)는 여기에서 찾을 수 있습니다. https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp

경고 :이 기술은 아직 검증되지 않았으며 아직 알려지지 않은 문제가있을 수 있습니다. 자신의 책임하에 사용하십시오!