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

时间:2012-12-26 16:53:23

标签: c++ qt user-interface

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

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秒钟。

当我点击按钮加载更多数据时,整个用户界面会冻结3秒钟,然后所有项目一切都显示在一起。有没有办法在创建项目时逐步加载更多项目?

2 个答案:

答案 0 :(得分:2)

@Adri通常是正确的,扭曲的是“另一个线程”必须再次成为UI线程。关键是允许UI线程的事件循环继续旋转。快速而肮脏的方法是将QCoreApplication::processEvents()放入for()周期。很脏,因为the doc says,它应该被称为“ocassionally”。即使没有UI事件,它也可能有一些开销,而且你正在弄乱Qt的性能优化,关于旋转循环的时间和频率。在result的块之后,稍微不那么肮脏的只是偶尔称它为它。

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

因为你只在一个线程中运行,所以你不需要对results进行任何锁定。

根据OP的请求添加示例:

虽然@TomPanning解决方案是正确的,但它隐藏了你不需要的QTimer背后的真正解决方案 - 你不需要任何计时,你只需要在特定参数值上有特定的非计时器行为。这个解决方案做了同样的事情,减去了QTimer层。另一方面,@ TomPanning对于普通ArrayList不是很好的数据存储有一个非常好的观点,当它们之间发生交互时。

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
}

答案 1 :(得分:2)

正如Pavel Zdenek所说,第一个技巧是只处理一些结果。您希望一起处理多个,以便(我们将在下一步中执行的操作)的开销很低,但您不希望做任何会使系统看起来没有响应的事情。基于广泛的研究,Jakob Nielsen says that“0.1秒是让用户感觉系统瞬间响应的极限”,因此粗略估计你应该将你的工作减少到大约0.05秒(再留下0.05秒)系统实际上对用户的交互作出反应。)

第二个技巧是使用QTimer,超时为0.正如QTimer documentation所说:

  

作为一种特殊情况,超时为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并重新处理数组的第一个元素)。因此,更强大的解决方案是使用列表而不是数组,并在处理时将每个元素从列表的前面弹出。然后无论添加什么结果都会附加到列表中。