Mat*_*aus 7 c++ qt qlistview qabstractitemmodel model-view
我正在使用一个QListView源自的自定义模型QAbstractItemModel.我有数百万件物品.我打电话listView->setUniformItemSizes(true)来阻止在向模型添加项目时调用一堆布局逻辑.到目前为止,一切都按预期工作.
问题是使用键盘导航列表很慢.如果我在列表中选择一个项目,然后按向上/向下,选择将快速移动,直到选择需要滚动列表.然后变得非常迟钝.按向上翻页或向下翻页也非常滞后.问题似乎是当使用键盘选择项目(也就是"当前项目")时,列表也会向上/向下滚动.
如果我使用鼠标,导航列表很快.我可以使用快速的鼠标滚轮.我可以按照我想要的速度向上/向下拖动滚动条 - 从列表顶部到底部 - 列表视图快速更新.
关于为什么改变选择和滚动列表的组合如此缓慢的任何想法?有可行的解决方案吗?
为了更好地说明问题,我在此更新中提供了放大信息.
这主要是性能问题,尽管它确实与用户体验(UX)有关.看看当我使用键盘滚动浏览时会发生什么QListView:
注意底部附近的减速?这是我的问题的焦点.让我解释一下我如何浏览列表.
说明:
我希望列表能够像键盘的打字速度一样快地滚动 - 换句话说,选择下一个项目所花费的时间不应该在滚动列表时减慢.
这是我使用鼠标时的样子:
说明:
这证明了两个要点:
模型不是问题.如您所见,该模型在性能方面没有任何问题.它可以比显示元素更快地传递元素.
选择和滚动时性能会降低.选择和滚动的"完美风暴"(如使用键盘在列表中导航所示)导致减速.因此,我推测,在滚动期间正常执行的选择时,Qt以某种方式进行了大量处理.
我想指出,我的问题似乎与Qt有关.
在使用不同的框架之前,我已经实现了这种类型的东西.我想做的是在模型 - 视图理论的范围内.我可以使用带有juce :: ListBox的juce :: ListBoxModel以极快的速度完成我所描述的内容.这是愚蠢的快速(此外,当每个项目已经具有唯一索引时,不需要为每个项目创建重复索引,例如a ).我认为Qt需要针对其模型 - 视图架构的每个项目,虽然我不喜欢开销成本,但我认为我理性,我可以忍受它.无论哪种方式,我都不怀疑这些因素导致我的表现减慢.QModelIndexQModelIndexQModelIndex
通过JUCE实现,我甚至可以使用向上翻页和向下翻页键来导航列表,它只是在列表中闪现.使用Qt QListView实现,即使使用发布版本,它也会突然出现并且很迟钝.
使用JUCE框架的模型视图实现非常快.为什么Qt QListView实现这样的狗?!
难以想象为什么在列表视图中需要这么多项?好吧,我们以前都见过这种事:
这是Visual Studio帮助查看器索引.现在,我没有计算所有项目 - 但我认为我们同意它们中有很多!当然为了使这个列表"有用",他们添加了一个过滤器框,根据输入字符串缩小列表视图中的内容.这里没有任何技巧.这是我们几十年来在桌面应用程序中看到的所有实用的,现实世界的东西.
但是有数百万件物品吗?我不确定这很重要.即使有"仅"150k项目(根据一些原始测量结果大致准确),也很容易指出你必须做些什么来使它可用 - 这就是过滤器将为你做的事情.
我的具体示例使用德语单词列表作为纯文本文件,条目略多于170万条(包括变形形式).这可能只是德国文本语料库中用于组合此列表的部分(但仍然很重要)单词样本.对于语言学习,这是一个合理的用例.
关于改进用户体验(用户体验)或过滤的担忧是很好的设计目标,但它们超出了这个问题的范围(我当然会在项目的后期解决它们).
想要一个代码示例?你说对了!我不确定它会有多大用处; 它就像它获得的香草(大约75%样板),但我想它会提供一些背景.我意识到我正在使用a QStringList并且有一个QStringListModel用于此,但QStringList我用于保存数据的是占位符 - 模型最终会更复杂一些,所以最后我需要使用一个自定义模型派生自QAbstractItemModel.
//
// wordlistmodel.h ///////////////////////////////////////
//
class WordListModel : public QAbstractItemModel
{
Q_OBJECT
public:
WordListModel(QObject* parent = 0);
virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex& index) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
public slots:
void loadWords();
signals:
void wordAdded();
private:
// TODO: this is a temp backing store for the data
QStringList wordList;
};
//
// wordlistmodel.cpp ///////////////////////////////////////
//
WordListModel::WordListModel(QObject* parent) :
QAbstractItemModel(parent)
{
wordList.reserve(1605572 + 50); // testing purposes only!
}
void WordListModel::loadWords()
{
// load items from file or database
// Due to taking Kuba Ober's advice to call setUniformItemSizes(true),
// loading is fast. I'm not using a background thread to do
// loading because I was trying to visually benchmark loading speed.
// Besides, I am going to use a completely different method using
// an in-memory file or a database, so optimizing this loading by
// putting it in a background thread would obfuscate things.
// Loading isn't a problem or the point of my question; it takes
// less than a second to load all 1.6 million items.
QFile file("german.dic");
if (!file.exists() || !file.open(QIODevice::ReadOnly))
{
QMessageBox::critical(
0,
QString("File error"),
"Unable to open " + file.fileName() + ". Make sure it can be located in " +
QDir::currentPath()
);
}
else
{
QTextStream stream(&file);
int numRowsBefore = wordList.size();
int row = 0;
while (!stream.atEnd())
{
// This works for testing, but it's not optimal.
// My real solution will use a completely different
// backing store (memory mapped file or database),
// so I'm not going to put the gory details here.
wordList.append(stream.readLine());
++row;
if (row % 10000 == 0)
{
// visual benchmark to see how fast items
// can be loaded. Don't do this in real code;
// this is a hack. I know.
emit wordAdded();
QApplication::processEvents();
}
}
if (row > 0)
{
// update final word count
emit wordAdded();
QApplication::processEvents();
// It's dumb that I need to know how many items I
// am adding *before* calling beginInsertRows().
// So my begin/end block is empty because I don't know
// in advance how many items I have, and I don't want
// to pre-process the list just to count the number
// of items. But, this gets the job done.
beginInsertRows(QModelIndex(), numRowsBefore, numRowsBefore + row - 1);
endInsertRows();
}
}
}
QModelIndex WordListModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0)
return QModelIndex();
else
return createIndex(row, column);
}
QModelIndex WordListModel::parent(const QModelIndex& index) const
{
return QModelIndex(); // this is used as the parent index
}
int WordListModel::rowCount(const QModelIndex& parent) const
{
return wordList.size();
}
int WordListModel::columnCount(const QModelIndex& parent) const
{
return 1; // it's a list
}
QVariant WordListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else if (role == Qt::DisplayRole)
{
return wordList.at(index.row());
}
else
{
return QVariant();
}
}
//
// mainwindow.h ///////////////////////////////////////
//
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void updateWordCount();
private:
Ui::MainWindow *ui;
WordListModel* wordListModel;
};
//
// mainwindow.cpp ///////////////////////////////////////
//
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->listView->setModel(wordListModel = new WordListModel(this));
// this saves TONS of time during loading,
// but selecting/scrolling performance wasn't improved
ui->listView->setUniformItemSizes(true);
// these didn't help selecting/scrolling performance...
//ui->listView->setLayoutMode(QListView::Batched);
//ui->listView->setBatchSize(100);
connect(
ui->pushButtonLoadWords,
SIGNAL(clicked(bool)),
wordListModel,
SLOT(loadWords())
);
connect(
wordListModel,
SIGNAL(wordAdded()),
this,
SLOT(updateWordCount())
);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateWordCount()
{
QString wordCount;
wordCount.setNum(wordListModel->rowCount());
ui->labelNumWordsLoaded->setText(wordCount);
}
Run Code Online (Sandbox Code Playgroud)
如上所述,我已经审查并采纳了Kuba Ober的建议:
我的问题不是那个问题的重复!在另一个问题中,OP询问加载速度,正如我在上面的代码中所提到的,由于调用而没有问题setUniformItemSizes(true).
QListView在滚动列表时使用键盘导航a (模型中有数百万个项目)这么慢?QListView?1. 为什么在滚动列表时使用键盘导航 QListView(模型中有数百万个项目)如此缓慢?
因为当您使用键盘浏览列表时,您进入了内部 Qt 函数QListModeViewBase::perItemScrollToValue,请参阅堆栈:
Qt5Widgetsd.dll!QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, QAbstractItemView::ScrollHint hint, Qt::Orientation orientation, bool wrap, int itemExtent) Ligne 2623 C++
Qt5Widgetsd.dll!QListModeViewBase::verticalScrollToValue(int index, QAbstractItemView::ScrollHint hint, bool above, bool below, const QRect & area, const QRect & rect) Ligne 2205 C++
Qt5Widgetsd.dll!QListViewPrivate::verticalScrollToValue(const QModelIndex & index, const QRect & rect, QAbstractItemView::ScrollHint hint) Ligne 603 C++
Qt5Widgetsd.dll!QListView::scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) Ligne 575 C++
Qt5Widgetsd.dll!QAbstractItemView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3574 C++
Qt5Widgetsd.dll!QListView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3234 C++
Qt5Widgetsd.dll!QAbstractItemView::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Ligne 414 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, int signalOffset, int local_signal_index, void * * argv) Ligne 3732 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, const QMetaObject * m, int local_signal_index, void * * argv) Ligne 3596 C++
Qt5Cored.dll!QItemSelectionModel::currentChanged(const QModelIndex & _t1, const QModelIndex & _t2) Ligne 489 C++
Qt5Cored.dll!QItemSelectionModel::setCurrentIndex(const QModelIndex & index, QFlags<enum QItemSelectionModel::SelectionFlag> command) Ligne 1373 C++
Run Code Online (Sandbox Code Playgroud)
此功能执行以下操作:
itemExtent += spacing();
QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}
Run Code Online (Sandbox Code Playgroud)
WhereflowPositions包含与您的项目一样多的项目QListView,因此这基本上会遍历您的所有项目,这肯定需要一段时间来处理。
2、为什么选择和滚动项结合会导致速度变慢?
因为“选择和滚动”使 Qt 调用QListView::scrollTo(将视图滚动到特定项目),这就是最终调用QListModeViewBase::perItemScrollToValue. 当您使用滚动条滚动时,系统不需要要求视图滚动到特定项目。
3. 是否有我遗漏的任何实现细节,或者我是否达到了 QListView 的性能阈值?
恐怕你做对了。这绝对是一个 Qt 错误。必须完成错误报告以希望在以后的版本中修复此问题。我在这里提交了一个 Qt 错误。
由于此代码是内部代码(私有数据类)并且不以任何QListView设置为条件,因此我认为除了修改和重新编译 Qt 源代码之外没有其他方法可以修复它(但我不知道具体如何,这需要更多调查)。堆栈中第一个可覆盖的函数是,QListView::scrollTo但我怀疑在不调用的情况下轻松实现它QListViewPrivate::verticalScrollToValue...
注意:当这个错误被修复时,这个函数遍历视图的所有项目的事实显然是在 Qt 4.8.3 中引入的(请参阅更改)。基本上,如果您不隐藏视图中的任何项目,您可以修改 Qt 代码如下:
/*QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}*/
QVector<int>& visibleFlowPositions = flowPositions;
Run Code Online (Sandbox Code Playgroud)
然后你必须重新编译 Qt,我很确定这会解决这个问题(但是没有测试)。但是如果有一天你隐藏一些项目,你会看到新的问题......例如支持过滤!
最有可能的正确解决方法是让视图同时维护flowPositions并visibleFlowPositions避免动态创建它......
| 归档时间: |
|
| 查看次数: |
2100 次 |
| 最近记录: |