本文介绍两种在Qt子线程(非UI线程)中更新UI组件的常用方法。

1. 使用信号槽

这是一种非常常规的方式,通过自定义信号、槽,连接该信号和槽,在子线程中发送信号,在槽中更新 UI。

定义信号和槽:

1
2
3
4
signals:
void updateUi(int v);
private slots:
void onUpdateUi(int v);

在子线程中发送信号:

1
2
3
4
5
6
7
8
9
10
// 连接信号
connect(this, &UpdateUIInSubThread::updateUi, this, &UpdateUIInSubThread::onUpdateUi, Qt::AutoConnection);

std::thread t = std::thread([this]() {
for (int i = 0; i < 10000; i++) {
emit updateUi(i); // 发送信号
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
t.detach();

在槽函数中更新 UI:

1
2
3
4
void UpdateUIInSubThread::onUpdateUi(int v)
{
ui.label->setText(QString::number(v));
}

这种方式需要单独额外定义信号和槽,使用起来比较繁琐。

2. 使用invokeMethod

QMetaObject::invokeMethod 函数的原型如下:

1
template <typename Functor, typename FunctorReturnType> bool QMetaObject::invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)

该函数可以在context的事件循环中执行function函数。

1
2
3
4
5
6
7
8
9
10
11
12
std::thread t = std::thread([this]() {
for (int i = 0; i < 10000; i++) {
if (QMetaObject::invokeMethod(this, [i, this]() {
ui.label->setText(QString::number(i));
})) {
qDebug() << "Update UI success";
}

std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
t.detach();

由于在子线程中更新 UI,因此信号和槽肯定使用的是 QueuedConnection 的连接方式,所以无法将FunctorReturnType返回给调用者,否则会出现如下错误:

1
QMetaObject::invokeMethod: Unable to invoke methods with return values in queued connections

当然上述示例中也可以不使用 lambda 表达式,直接调用槽函数:

1
2
3
4
5
6
7
8
std::thread t = std::thread([this]() {
for (int i = 0; i < 10000; i++) {
QMetaObject::invokeMethod(this, "onUpdateUi", Qt::AutoConnection, Q_ARG(int, i));

std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
t.detach();