QTreeView: fetch as many nested entries as fit into the view
QAbstractItemModel::canFetchMore and fetchMore implementations might only add a few rows to the model each time they are called. The item views don't generally expect that, and don't call fetchMore repeatedly, even if there would be room in the itemview for more data. This problem cannot be generally solved for all item views, as it would require in repeated expensive laying out of items. For nested indexes in a treeview however, we can try to fetch enough child rows to populate the screen when the item is laid out by repeatedly calling canFetchMore and fetchMore. To calculate how many items have space, apply the same heuristics as in the scrollContentsBy implementation to guess the number of items that can fit into the viewport. Created test case for the fix. Done-with: Doris Verria <doris.verria@qt.io> Fixes: QTBUG-85366 Pick-to: 5.15 Change-Id: I54f95552993873dd4cba80b0f70f4db9d98ddc1d Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> Reviewed-by: Andy Shaw <andy.shaw@qt.io> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
parent
9b35a16c64
commit
e74af68654
@ -3342,9 +3342,17 @@ void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninit
|
||||
|
||||
int count = 0;
|
||||
if (model->hasChildren(parent)) {
|
||||
if (model->canFetchMore(parent))
|
||||
if (model->canFetchMore(parent)) {
|
||||
// fetchMore first, otherwise we might not yet have any data for sizeHintForRow
|
||||
model->fetchMore(parent);
|
||||
count = model->rowCount(parent);
|
||||
// guestimate the number of items in the viewport, and fetch as many as might fit
|
||||
const int itemHeight = defaultItemHeight <= 0 ? q->sizeHintForRow(0) : defaultItemHeight;
|
||||
const int viewCount = viewport->height() / itemHeight;
|
||||
while ((count = model->rowCount(parent)) < viewCount && model->canFetchMore(parent))
|
||||
model->fetchMore(parent);
|
||||
} else {
|
||||
count = model->rowCount(parent);
|
||||
}
|
||||
}
|
||||
|
||||
bool expanding = true;
|
||||
|
@ -251,6 +251,7 @@ private slots:
|
||||
void taskQTBUG_8376();
|
||||
void taskQTBUG_61476();
|
||||
void testInitialFocus();
|
||||
void fetchUntilScreenFull();
|
||||
};
|
||||
|
||||
class QtTestModel: public QAbstractItemModel
|
||||
@ -5068,5 +5069,155 @@ void tst_QTreeView::taskQTBUG_61476()
|
||||
QCOMPARE(lastTopLevel->checkState(), Qt::Checked);
|
||||
}
|
||||
|
||||
void tst_QTreeView::fetchUntilScreenFull()
|
||||
{
|
||||
class TreeModel : public QAbstractItemModel
|
||||
{
|
||||
public:
|
||||
const int maxChildren = 49;
|
||||
explicit TreeModel(QObject* parent = nullptr) : QAbstractItemModel(parent)
|
||||
{
|
||||
QVariant rootData1("Parent Col 1");
|
||||
QVariant rootData2("Parent Col 2");
|
||||
QVector<QVariant> rootData;
|
||||
rootData.append(rootData1);
|
||||
rootData.append(rootData2);
|
||||
|
||||
m_root = new TreeItem(rootData, nullptr);
|
||||
|
||||
QVariant childData1("Col 1");
|
||||
QVariant childData2("Col 2");
|
||||
QVector<QVariant> childData;
|
||||
childData.append(childData1);
|
||||
childData.append(childData2);
|
||||
|
||||
TreeItem* item_1 = new TreeItem(childData, m_root);
|
||||
m_root->children.append(item_1);
|
||||
|
||||
TreeItem* item_2 = new TreeItem(childData, item_1);
|
||||
item_1->children.append(item_2);
|
||||
}
|
||||
|
||||
QModelIndex index(const int row, const int column,
|
||||
const QModelIndex& parent = QModelIndex()) const override
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
TreeItem* parentItem =
|
||||
parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : m_root;
|
||||
TreeItem* childItem = parentItem->children.at(row);
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override
|
||||
{
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
TreeItem* parentItem = parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer())
|
||||
: m_root;
|
||||
return parentItem->children.count();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex&) const override { return 2; }
|
||||
|
||||
QModelIndex parent(const QModelIndex& childIndex) const override
|
||||
{
|
||||
if (!childIndex.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
TreeItem* parentItem =
|
||||
static_cast<TreeItem*>(childIndex.internalPointer())->parent;
|
||||
return parentItem == m_root ? QModelIndex()
|
||||
: createIndex(parentItem->rowInParent(), 0, parentItem);
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex& index, const int role) const override
|
||||
{
|
||||
if (!index.isValid() || role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
|
||||
return item->data.at(index.column());
|
||||
}
|
||||
|
||||
bool canFetchMore(const QModelIndex& parent) const override
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return false;
|
||||
} else {
|
||||
TreeItem* item = static_cast<TreeItem*>(parent.internalPointer());
|
||||
return item->children.size() < maxChildren;
|
||||
}
|
||||
}
|
||||
|
||||
void fetchMore(const QModelIndex& parent) override
|
||||
{
|
||||
if (!parent.isValid())
|
||||
return;
|
||||
|
||||
fetchMoreCount++;
|
||||
TreeItem* parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
||||
int childCount = parentItem->children.size();
|
||||
|
||||
beginInsertRows(parent, childCount, childCount);
|
||||
|
||||
QVariant childData1("Col 1");
|
||||
QVariant childData2("Col 2");
|
||||
QVector<QVariant> childData;
|
||||
childData.append(childData1);
|
||||
childData.append(childData2);
|
||||
TreeItem* newChild = new TreeItem(childData, parentItem);
|
||||
parentItem->children.append(newChild);
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int fetchMoreCount = 0;
|
||||
private:
|
||||
struct TreeItem
|
||||
{
|
||||
TreeItem(const QVector<QVariant>& values, TreeItem* parent)
|
||||
: data(values), parent(parent)
|
||||
{
|
||||
}
|
||||
~TreeItem() { qDeleteAll(children); }
|
||||
int rowInParent() const
|
||||
{
|
||||
if (parent)
|
||||
return parent->children.indexOf(const_cast<TreeItem*>(this));
|
||||
return 0;
|
||||
}
|
||||
QVector<QVariant> data;
|
||||
QVector<TreeItem*> children;
|
||||
TreeItem* parent = nullptr;
|
||||
};
|
||||
TreeItem* m_root;
|
||||
};
|
||||
|
||||
QTreeView tv;
|
||||
TreeModel model;
|
||||
tv.setModel(&model);
|
||||
|
||||
const int itemHeight = tv.sizeHintForRow(0);
|
||||
tv.resize(250, itemHeight * 10);
|
||||
tv.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&tv));
|
||||
|
||||
tv.expand(model.index(0, 0));
|
||||
const int viewportHeight = tv.viewport()->height();
|
||||
const int itemCount = viewportHeight / itemHeight;
|
||||
const int minFetchCount = itemCount - 1;
|
||||
const int maxFetchCount = itemCount + 1;
|
||||
|
||||
const bool expectedItemNumberFetched = model.fetchMoreCount >= minFetchCount
|
||||
&& model.fetchMoreCount <= maxFetchCount;
|
||||
if (!expectedItemNumberFetched)
|
||||
qDebug() << model.fetchMoreCount << minFetchCount << maxFetchCount;
|
||||
QVERIFY(expectedItemNumberFetched);
|
||||
}
|
||||
|
||||
|
||||
QTEST_MAIN(tst_QTreeView)
|
||||
#include "tst_qtreeview.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user