如何从 std::thread 更改 GUI?
How can I change GUI from std::thread?
首先,我尝试使用 thread
中的 setVisible()
有一个事件:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, ui, &(this->settingsMap));
dThread.join();
}
有函数OnShow_threaded
:
void OnShow_threaded(Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
ui->SettingsLabel->setVisible(true);
}
}
编译静态程序集时程序崩溃,报错:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to
objects owned by a different thread. Current thread 0x0x36c56540.
Receiver 'WarningMsg' (of type 'QGroupBox') was created in thread
0x0x341c2fa0", file kernel\qcoreapplication.cpp, line 558
在线:ui->SettingsLabel->setVisible(true);
同时,动态链接时没有出现该错误
您可以在 GitHub
上找到完整的项目
其次,我尝试使用事件。
有函数OnShow_threaded
:
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
MyEvent* event = new MyEvent(EventTypes::InternetConnectionError);
QCoreApplication::postEvent(mw, event);
//delete event;
//delete receiver;
}
}
有一个事件class:
#ifndef EVENTS_HPP
#define EVENTS_HPP
#include <QEvent>
#include <QString>
enum EventTypes {
InternetConnectionError,
Unknown
};
class MyEvent : public QEvent
{
public:
MyEvent(const EventTypes _type) : QEvent(QEvent::User) {_localType = _type;}
~MyEvent() {}
auto localType() const {return _localType;}
private:
int _localType;
};
#endif // EVENTS_HPP
有一个事件处理程序:
void MainWindow::events(QEvent *event)
{
if (event->type() == QEvent::User)
{
MyEvent* postedEvent = static_cast<MyEvent*>(event);
if (postedEvent->localType() == EventTypes::InternetConnectionError){
ui->WarningMsg->setVisible(true);
ui->SettingsLabel->setVisible(true);
}
}
}
传递参数:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, this, ui, &(this->settingsMap));
dThread.detach();
}
有一个 mainwindows hpp 文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
#include <QMovie>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QCoreApplication>
#include <QSaveFile>
#include <QProcess>
#include <thread>
#include <chrono>
#include <unordered_map>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "settings.hpp"
#include "events.hpp"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void OnShow();
private slots:
void SettingsLabelPressed();
void on_CloseMsgButton_clicked();
void on_Settings_SaveButton_clicked();
void on_Settings_UseTranslation_stateChanged(int arg1);
protected:
void events(QEvent* event);
private:
Ui::MainWindow *ui;
std::unordered_map<QString,QString> settingsMap;
};
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap);
#endif // MAINWINDOW_H
但是事件没有执行。
我做错了什么?
以及如何从另一个线程正确更改 GUI?
З.Ы.抱歉我的英语不好,我来自俄罗斯....
在 Qt 中,与在许多其他 GUI 框架中一样,GUI 可能仅从主线程更新。这意味着如果您想从另一个线程更新 GUI,您必须将其传达给主线程,而主线程又会更新 GUI。
有关更多详细信息,请参阅这些文章:
其他语言的其他资源:Правильная работа с потоками в Qt.
正如您在评论中要求 QThread
提供的演示,这里是。
作为 GUI,我有一个带有两个简单按钮的主窗口,我想显示、隐藏带有 QThread
的大按钮(而不只是点击插槽)我发出一个中间值从点击到 hide/show 按钮的信号。
QThread
的作用只是用参数true
或false
向sigShowHide
发出信号。
主 UI 线程通过调用插槽 onShowHideButtonThreaded
显示或隐藏按钮来处理此信号,插槽 onShowHideButtonThreaded
对信号 sigShowHide
作出反应
代码文件如下:
mainwindows.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void sigShowHide(bool);
public slots:
void onShowHideButtonThreaded(bool);
void onButton1Click();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onButton1Click);
QObject::connect(this,&MainWindow::sigShowHide, this, &MainWindow::onShowHideButtonThreaded);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onShowHideButtonThreaded(bool a)
{
qDebug() << " the main thread id = " << QThread::currentThread() << "set the visibility ";
ui->pushButton_2->setVisible(a);
}
void MainWindow::onButton1Click()
{
qDebug()<< "clicked";
qDebug() << " the main thread id = " << QThread::currentThread();
QThread* l_thread = QThread::create([&]()
{
qDebug() << "Running Thread " << QThread::currentThreadId() << " to emit signal only ";
emit sigShowHide( !this->ui->pushButton_2->isVisible());
});
l_thread->start();
}
`
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
一个执行的例子是:
the main thread id = QThread(0x116ee18)
Running Thread 0x1ed8 to emit signal only
the main thread id = QThread(0x116ee18) set the visibility
如@rustyx 所述:在 Qt 中,与许多其他 GUI 框架一样,GUI 可能仅从主线程更新。
我也被这个问题卡住了,下面是我的两个解决方案:
-
QMetaObject::invokeMethod(ui->SettingsLabel, "setVisible", Q_ARG(bool, true));
QMetaObject::invokeMethod
是线程安全的API,它有一个Qt::ConnectionType type
参数,它有一个默认值Qt::AutoConnection
.
一些 Qt::ConnectionType
值的描述:
Qt::AutoConnection:
(Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
Qt::DirectConnection:
The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
Qt::QueuedConnection:
The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
Qt::BlockingQueuedConnection:
Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
从描述中可以看出,API会将你的调用请求放入队列中,然后主线程从队列中取出,最终处理你的调用请求。
而如果需要获取目标方法的return值,则需要显式将Qt::BlockingQueuedConnection
传递给Qt::ConnectionType type
参数。因为间接默认值 Qt::QueuedConnection
不会等待调用完成,所以您很可能会得到错误的 return 值。
但是这个解决方案有一个问题,就是它只支持信号和槽函数,不支持非信号和非槽函数。
使用connect
.
// In "MainWindow" class declaration
//
class MainWindow : public QMainWindow
{
// ...
Q_SIGNALS:
void setSettingsLabelVisibleSafety(bool value);
// ...
};
// In "MainWindow" constructor
//
connect(this, &MainWindow::setSettingsLabelVisibleSafety, this,
[this](bool value) {
ui->SettingsLabel->setVisible(value);
}
);
// In other thread
//
setSettingsLabelVisibleSafety(value);
connect
还有一个Qt::ConnectionType type
参数,如果显式传递receiver
参数,则默认值为Qt::AutoConnection
,否则默认值为Qt::DirectConnection
.因此,您需要显式传递 receiver
参数或显式传递 Qt::AutoConnection
值给 Qt::ConnectionType type
参数。
如果目标方法具有 return 值,则还需要显式传递 Qt::BlockingQueuedConnection
。
此解决方案支持信号和槽函数,以及非信号和非槽函数。
以上两种解决方案都需要您调用qRegisterMetaType
来注册用户定义的类型(如果有的话)。
qRegisterMetaType<YourType>("YourType");
显然,我更喜欢第二种解决方案。
首先,我尝试使用 thread
setVisible()
有一个事件:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, ui, &(this->settingsMap));
dThread.join();
}
有函数OnShow_threaded
:
void OnShow_threaded(Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
ui->SettingsLabel->setVisible(true);
}
}
编译静态程序集时程序崩溃,报错:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x36c56540. Receiver 'WarningMsg' (of type 'QGroupBox') was created in thread 0x0x341c2fa0", file kernel\qcoreapplication.cpp, line 558
在线:ui->SettingsLabel->setVisible(true);
同时,动态链接时没有出现该错误
您可以在 GitHub
上找到完整的项目其次,我尝试使用事件。
有函数OnShow_threaded
:
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
MyEvent* event = new MyEvent(EventTypes::InternetConnectionError);
QCoreApplication::postEvent(mw, event);
//delete event;
//delete receiver;
}
}
有一个事件class:
#ifndef EVENTS_HPP
#define EVENTS_HPP
#include <QEvent>
#include <QString>
enum EventTypes {
InternetConnectionError,
Unknown
};
class MyEvent : public QEvent
{
public:
MyEvent(const EventTypes _type) : QEvent(QEvent::User) {_localType = _type;}
~MyEvent() {}
auto localType() const {return _localType;}
private:
int _localType;
};
#endif // EVENTS_HPP
有一个事件处理程序:
void MainWindow::events(QEvent *event)
{
if (event->type() == QEvent::User)
{
MyEvent* postedEvent = static_cast<MyEvent*>(event);
if (postedEvent->localType() == EventTypes::InternetConnectionError){
ui->WarningMsg->setVisible(true);
ui->SettingsLabel->setVisible(true);
}
}
}
传递参数:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, this, ui, &(this->settingsMap));
dThread.detach();
}
有一个 mainwindows hpp 文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
#include <QMovie>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QCoreApplication>
#include <QSaveFile>
#include <QProcess>
#include <thread>
#include <chrono>
#include <unordered_map>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "settings.hpp"
#include "events.hpp"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void OnShow();
private slots:
void SettingsLabelPressed();
void on_CloseMsgButton_clicked();
void on_Settings_SaveButton_clicked();
void on_Settings_UseTranslation_stateChanged(int arg1);
protected:
void events(QEvent* event);
private:
Ui::MainWindow *ui;
std::unordered_map<QString,QString> settingsMap;
};
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap);
#endif // MAINWINDOW_H
但是事件没有执行。
我做错了什么?
以及如何从另一个线程正确更改 GUI?
З.Ы.抱歉我的英语不好,我来自俄罗斯....
在 Qt 中,与在许多其他 GUI 框架中一样,GUI 可能仅从主线程更新。这意味着如果您想从另一个线程更新 GUI,您必须将其传达给主线程,而主线程又会更新 GUI。
有关更多详细信息,请参阅这些文章:
其他语言的其他资源:Правильная работа с потоками в Qt.
正如您在评论中要求 QThread
提供的演示,这里是。
作为 GUI,我有一个带有两个简单按钮的主窗口,我想显示、隐藏带有 QThread
的大按钮(而不只是点击插槽)我发出一个中间值从点击到 hide/show 按钮的信号。
QThread
的作用只是用参数true
或false
向sigShowHide
发出信号。
主 UI 线程通过调用插槽 onShowHideButtonThreaded
显示或隐藏按钮来处理此信号,插槽 onShowHideButtonThreaded
对信号 sigShowHide
代码文件如下:
mainwindows.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void sigShowHide(bool);
public slots:
void onShowHideButtonThreaded(bool);
void onButton1Click();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onButton1Click);
QObject::connect(this,&MainWindow::sigShowHide, this, &MainWindow::onShowHideButtonThreaded);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onShowHideButtonThreaded(bool a)
{
qDebug() << " the main thread id = " << QThread::currentThread() << "set the visibility ";
ui->pushButton_2->setVisible(a);
}
void MainWindow::onButton1Click()
{
qDebug()<< "clicked";
qDebug() << " the main thread id = " << QThread::currentThread();
QThread* l_thread = QThread::create([&]()
{
qDebug() << "Running Thread " << QThread::currentThreadId() << " to emit signal only ";
emit sigShowHide( !this->ui->pushButton_2->isVisible());
});
l_thread->start();
}
`
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
一个执行的例子是:
the main thread id = QThread(0x116ee18)
Running Thread 0x1ed8 to emit signal only
the main thread id = QThread(0x116ee18) set the visibility
如@rustyx 所述
我也被这个问题卡住了,下面是我的两个解决方案:
-
QMetaObject::invokeMethod(ui->SettingsLabel, "setVisible", Q_ARG(bool, true));
QMetaObject::invokeMethod
是线程安全的API,它有一个Qt::ConnectionType type
参数,它有一个默认值Qt::AutoConnection
.一些
Qt::ConnectionType
值的描述:Qt::AutoConnection:
(Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
Qt::DirectConnection:
The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
Qt::QueuedConnection:
The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
Qt::BlockingQueuedConnection:
Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
从描述中可以看出,API会将你的调用请求放入队列中,然后主线程从队列中取出,最终处理你的调用请求。
而如果需要获取目标方法的return值,则需要显式将
Qt::BlockingQueuedConnection
传递给Qt::ConnectionType type
参数。因为间接默认值Qt::QueuedConnection
不会等待调用完成,所以您很可能会得到错误的 return 值。但是这个解决方案有一个问题,就是它只支持信号和槽函数,不支持非信号和非槽函数。
使用
connect
.// In "MainWindow" class declaration // class MainWindow : public QMainWindow { // ... Q_SIGNALS: void setSettingsLabelVisibleSafety(bool value); // ... }; // In "MainWindow" constructor // connect(this, &MainWindow::setSettingsLabelVisibleSafety, this, [this](bool value) { ui->SettingsLabel->setVisible(value); } ); // In other thread // setSettingsLabelVisibleSafety(value);
connect
还有一个Qt::ConnectionType type
参数,如果显式传递receiver
参数,则默认值为Qt::AutoConnection
,否则默认值为Qt::DirectConnection
.因此,您需要显式传递receiver
参数或显式传递Qt::AutoConnection
值给Qt::ConnectionType type
参数。如果目标方法具有 return 值,则还需要显式传递
Qt::BlockingQueuedConnection
。此解决方案支持信号和槽函数,以及非信号和非槽函数。
以上两种解决方案都需要您调用qRegisterMetaType
来注册用户定义的类型(如果有的话)。
qRegisterMetaType<YourType>("YourType");
显然,我更喜欢第二种解决方案。