如何在QT中逐步加载小部件

How do I progressively load a widget in QT?

本文关键字:加载 小部 QT      更新时间:2023-10-16

我有一个自定义小部件,该小部件在行中显示许多项目:

void update(){ //this is a SLOT which is connected to a button click
    QVBoxLayout *layout = this->layout();
    if (layout == NULL){
        layout = new QVBoxLayout;
        this->setLayout(layout);
    } else {
        QLayout_clear(layout); //this is a function that I wrote that deletes all of the items from a layout
    }
    ArrayList *results = generateData(); //this generates the data that I load from
    for (int i = 0; i < results->count; i++){
        layout->addWidget(new subWidget(results->array[i]));
    }
}

问题在于大约有900个项目,并且配置文件显示,将子对象添加到布局需要50%的时间(构造需要其他50%)。总体而言,加载所有项目大约需要3秒钟。

当我单击按钮加载更多数据时,整个UI将在3秒钟内冻结,然后在完成所有操作后所有项目都会出现。有没有办法在创建更多项目时逐步加载它们?

正如帕维尔·兹登克(Pavel Zdenek)所说,第一个技巧仅处理一些结果。您想一起处理尽可能多的时间,以便开销(下一步我们要做的事情)很低,但是您不想做任何会使系统看起来不反应的事情。基于广泛的研究,雅各布·尼尔森(Jakob Nielsen)说:" 0.1秒是让用户觉得系统即时反应的限制",因此,作为一个粗略的估计,您应该将工作切成大约0.05秒(留下0.05秒(留下0.05秒)对用户交互的实际反应的系统)。

第二个技巧是使用QTimer,超时为0。

作为特殊情况,一个超时时间为0的QTIMER很快就会超时 由于窗口系统事件队列中的所有事件已经 处理。这可以用来从事繁重的工作,同时提供活泼 用户界面。

,这意味着接下来将执行具有超时时间为0的计时器,除非事件队列中还有其他内容(例如,单击鼠标)。这是代码:

void update() {
    i = 0; // warning, this is causes a bug, see below
    updateChunk();
}
void updateChunk() {
    const int CHUNK_THRESHOLD = /* the number of things you can do before the user notices that you're doing something */;
    for (; i < results->count() && i < CHUNK_THRESHOLD; i++) {
         // add widget
    }
    // If there's more work to do, put it in the event queue.
    if (i < results->count()) {
        // This isn't true recursion, because this method will return before
        // it is called again.
        QTimer::singleShot(0, this, SLOT(updateChunk()));
    }
}

最后,因为有一个陷阱,请测试一下:现在,用户可以与循环"中间"中的系统进行交互。例如,用户可以在仍在处理结果时单击"更新"按钮(在上面的示例中,这意味着您将索引重置为0并重新处理数组的第一个元素)。因此,更强大的解决方案是使用列表代替数组,然后在处理列表时将每个元素从列表的正面弹出。然后,任何添加结果的内容都只会附加到列表中。

@adri通常是正确的,扭曲是"另一个线程"必须再次是UI线程。关键是允许UI线程的事件循环继续旋转。快速而肮脏的方法是将QCoreApplication::processEvents()放入您的for()周期中。肮脏,因为正如文档所说,它应该被称为"杀人"。即使没有UI事件,它也可能有一些开销,并且您正在弄乱QT的何时以及旋转循环的性能优化。在result的大块之后,只能杀死它。

清洁程序和正确的方法是创建一个私有插槽,该插槽弹出一个结果元素(或块,加快速度),会增加布局和增量索引。然后,它将回忆起自己,直到results结束。cotcha是用强制连接类型的Qt::QueuedConnection定义connect(),因此在已经排队的UI事件(如果有)之后将被延期。

并且由于您仅在一个线程中运行,因此您不需要锁定results

添加示例每个OP的请求:

虽然@Tompanning解决方案是正确的,但它隐藏了您不需要的QTimer背后的真实解决方案 - 您不需要任何时间安排,您只需要在特定的参数值上进行特定的非计时器行为即可。该解决方案执行相同的操作,减去QTimer层。另一方面,@Tompanning在互动之间的互动中不太好。

something.h

signals: void subWidgetAdded();
private slots: void addNextWidget();
ArrayList* m_results;
int m_indexPriv;

something.cpp

connect(this,SIGNAL(subWidgetAdded()),
        this,SLOT(addNextWidget(),
        Qt::QueuedConnection);
void addWidget() {
  // additional chunking logic here as you need
  layout->addWidget(new subWidget(results->array[m_indexPriv++]));
  if( m_indexPriv < results->count() ) {
    emit subWidgetAdded(); // NOT a recursion :-)
  }
}
void update() {
  // ...
  m_results = generateData();
  m_indexPriv = 0;
  addNextWidget(); // slots are normal instance methods, call for the first time
}