如何防止 QListView 调用每个项目的 sizeHint?
How to prevent QListView from calling sizeHint of each item?
我有一个 QListView,其中包含许多高度不同的项目。我实现了一个用于绘制项目的自定义委托,并将布局模式设置为 Batched。
但是,当分配模型时,列表视图会预先为模型中的每个项目请求 sizeHint,忽略 Batched 设置,从而破坏性能,因为要计算大小,委托必须布局大量文本(这并不快)。
可能是为了计算滚动条的位置,但是我估计当item数量多的时候,滚动条的位置可以只根据item的索引,不考虑item的高度。然而,这似乎不是 QListView 的工作方式。
我也试过在模型中使用canFetchMore/fetchMore,但这导致用户体验不好——滚动条位置不再准确,加载更多项目时列表跳来跳去,不流畅完全没有。
所以,问题是:
- 有没有办法阻止 QListView 为不可见的项目调用 sizeHint?
- 如果只能使用canFetchMore/fetchMore,如何实现流畅的滚动和稳定准确的滚动条?
非常感谢!
UPD: 下面是重现此行为的最小示例:
https://github.com/ajenter/qt_hugelistview
请注意巨大的启动延迟和调试消息显示预先请求了所有 5000 个项目的 sizeHint。
好吧,看来我找到了解决方案,所以我会在这里分享它,以供遇到相同问题并搜索此线程的任何人使用。
首先,我发现这实际上是 Qt 中的一个错误,早在 2011 年就已注册,并且仍然存在:
https://bugreports.qt.io/browse/QTBUG-16592
我已经投了票(你也应该投!)。
然后决定尝试使用 QTableView 而不是 QListView - 而且,令人惊讶的是,我设法让它工作,或者看起来是这样。
与 QListView 不同,QTableView 仅根据明确请求通过调用 resizeRowToContents(rowNum) 调整行的大小。因此诀窍是以 just-in-time 方式调用它以显示在视口中可见的行。
这是我所做的:
继承自QTableView(姑且称之为MyTableView)
用MyTableView替换QListView,在构造函数中这样初始化。这将分配自定义项目委托,隐藏 table headers 并应用“按行”选择模式:
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
}
- 在 MyTableView 中,添加一个 QItemSelection 私有字段和一个计算实际行高的 public 函数,但仅计算当前可见的行高:
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";
}
}
}
注意:如果项目高度取决于控件的宽度,您需要覆盖 resizeEvent()
,清除 _itemsWithKnownHeights
,然后再次调用 updateVisibleRowsHeight()。
在将模型分配给 MyTableView 实例后调用 updateVisibleRowHeights(),以便初始视图正确:
table.setModel(&myModel);
table.updateVisibleRowHeights();
事实上,它应该在 MyTableView 的一些响应模型变化的方法中完成,但我会把它留作练习。
- 现在剩下的就是在 table 的垂直滚动位置发生变化时调用 updateRowHeights。所以我们需要在MyTableView的构造函数中加入如下内容:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
完成 - 即使使用 100,000 件物品的模型,它的运行速度也非常快!而且启动是即时的!
此技术的基本 proof-of-concept 示例(使用纯 QTableView 而不是子类)可在此处找到:
https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
警告:此技术尚未经过实战验证,可能包含一些未知问题。使用风险自负!
我有一个 QListView,其中包含许多高度不同的项目。我实现了一个用于绘制项目的自定义委托,并将布局模式设置为 Batched。
但是,当分配模型时,列表视图会预先为模型中的每个项目请求 sizeHint,忽略 Batched 设置,从而破坏性能,因为要计算大小,委托必须布局大量文本(这并不快)。
可能是为了计算滚动条的位置,但是我估计当item数量多的时候,滚动条的位置可以只根据item的索引,不考虑item的高度。然而,这似乎不是 QListView 的工作方式。
我也试过在模型中使用canFetchMore/fetchMore,但这导致用户体验不好——滚动条位置不再准确,加载更多项目时列表跳来跳去,不流畅完全没有。
所以,问题是:
- 有没有办法阻止 QListView 为不可见的项目调用 sizeHint?
- 如果只能使用canFetchMore/fetchMore,如何实现流畅的滚动和稳定准确的滚动条?
非常感谢!
UPD: 下面是重现此行为的最小示例: https://github.com/ajenter/qt_hugelistview
请注意巨大的启动延迟和调试消息显示预先请求了所有 5000 个项目的 sizeHint。
好吧,看来我找到了解决方案,所以我会在这里分享它,以供遇到相同问题并搜索此线程的任何人使用。
首先,我发现这实际上是 Qt 中的一个错误,早在 2011 年就已注册,并且仍然存在: https://bugreports.qt.io/browse/QTBUG-16592
我已经投了票(你也应该投!)。 然后决定尝试使用 QTableView 而不是 QListView - 而且,令人惊讶的是,我设法让它工作,或者看起来是这样。
与 QListView 不同,QTableView 仅根据明确请求通过调用 resizeRowToContents(rowNum) 调整行的大小。因此诀窍是以 just-in-time 方式调用它以显示在视口中可见的行。
这是我所做的:
继承自QTableView(姑且称之为MyTableView)
用MyTableView替换QListView,在构造函数中这样初始化。这将分配自定义项目委托,隐藏 table headers 并应用“按行”选择模式:
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
}
- 在 MyTableView 中,添加一个 QItemSelection 私有字段和一个计算实际行高的 public 函数,但仅计算当前可见的行高:
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";
}
}
}
注意:如果项目高度取决于控件的宽度,您需要覆盖
resizeEvent()
,清除_itemsWithKnownHeights
,然后再次调用 updateVisibleRowsHeight()。在将模型分配给 MyTableView 实例后调用 updateVisibleRowHeights(),以便初始视图正确:
table.setModel(&myModel);
table.updateVisibleRowHeights();
事实上,它应该在 MyTableView 的一些响应模型变化的方法中完成,但我会把它留作练习。
- 现在剩下的就是在 table 的垂直滚动位置发生变化时调用 updateRowHeights。所以我们需要在MyTableView的构造函数中加入如下内容:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
完成 - 即使使用 100,000 件物品的模型,它的运行速度也非常快!而且启动是即时的!
此技术的基本 proof-of-concept 示例(使用纯 QTableView 而不是子类)可在此处找到: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
警告:此技术尚未经过实战验证,可能包含一些未知问题。使用风险自负!