【发布时间】:2014-03-28 14:25:21
【问题描述】:
我想在代码中的函数运行时在主窗口上设置标签的文本。但是标签在函数结束之前不会改变。
for (int i = 0; i < files.size(); ++i) {
ui->label->setText(files[i]);
myfoo();
}
【问题讨论】:
标签: qt qt4 qmainwindow mainwindow
我想在代码中的函数运行时在主窗口上设置标签的文本。但是标签在函数结束之前不会改变。
for (int i = 0; i < files.size(); ++i) {
ui->label->setText(files[i]);
myfoo();
}
【问题讨论】:
标签: qt qt4 qmainwindow mainwindow
标签没有更新的原因是更新 GUI 的主线程忙于你的 for 循环,没有时间处理事件,除其他外,导致 GUI 小部件被重绘.
虽然您可以使用 QApplication::processEvents,但这并不理想,尤其是在处理您的函数期间收到许多事件时。
处理此问题的正确方法是创建一个单独的线程 (QThread) 和一个从 QObject 派生的对象,该对象将在向主 (GUI) 线程发送消息以更新标签时起作用。
建议您阅读this article,了解如何正确使用QThread,而不是重复代码。没那么难。
然后,您将现在位于第二个线程的对象中的函数更改为如下所示:-
for (int i = 0; i < files.size(); ++i)
{
// calling UpdateLabel signal, which connects to an object on the main thread
UpdateLabel(files[i]);
myfoo();
}
假设来自 Worker 类的信号已连接到主线程上的对象中的插槽,例如 QMainWindow,您将收到插槽中的文本并更新标签:-
void QMainWindow::UpdateLabel(const QString text)
{
ui->label->setText(text);
}
【讨论】:
发生这种情况是因为您的循环阻塞了事件循环 - 您永远不会返回到事件循环。在您的代码中调用大多数任何插槽时,都会从事件循环中调用它。事件循环只能在您从插槽返回或从 QObject::event 方法返回时继续。
使用 Qt 5 和 C++11,从单独的线程更新文件列表非常容易。
QtConcurrent::run([this] {
// This code runs in a separate thread
for (int i = 0; i < files.size(); ++i) {
// This is safe for cross-thread use
QMetaObject::invokeMethod(ui->label, "setText", files[i]);
myfoo();
}
});
myfoo() 的实现必须是线程安全的,不能直接访问任何小部件。它仍然可以通过使用invokeMethod 机制安全地“写入”到小部件,因为它是跨线程的。
in my photomosaic Qt example, implemented in a mere 300 lines 是演示如何编写不阻塞 gui 的完全多线程文件访问的一个相当好的起点。
【讨论】:
您可以在一个单独的线程中执行您的 for 循环,该线程仅向 gui 线程发送信号,然后在适当的插槽中调用 label->setText()。 (使用 Qt::QueuedConnection)
或者试试这个:
for (int i = 0; i < files.size(); ++i) {
ui->label->setText(files[i]);
QApplication::processEvents();
myfoo();
}
【讨论】:
processEvents 重新进入事件循环,并可能导致比从多个线程不同步访问数据稍差的效果。
processEvents 本质上是一种技巧,应该在非常谨慎的情况下使用。这是非常危险的,因为突然所有你的代码必须在设计时考虑到可重入性,你甚至不能使用像互斥锁这样简单的东西来防止它,因为你只会得到僵局。 processEvents 只能从设计时考虑到可重入性的库代码中调用。它不属于通用实现代码。
processEvents 需要在设计所有代码时考虑到可重入性?它都在同一个线程中运行,因此不会有任何对数据的并发访问(前提是根本只有一个线程)。当然processEvents 可能会导致问题,尤其是在性能方面,为什么要小心使用它。但是在某些情况下它是更好的选择......(因为它使您的代码保持简单)
processEvents,它可能最终会在返回之前再次调用该方法。因此,该方法需要可以这样调用:重入。因此,任何可能调用使用processEvents 的方法/函数的代码都需要重新进入,除非它 100% 确定事件循环不会再次调用它。