记录QT运行过程中某些控件样式突然失效问题和解决办法
问题:
程序一开始启动时窗口正常显示, 但在运行一段时间后出现窗口样式失效问题。
经排查主要原因是在非GUI线程中修改UI控件属性(设置控件内容(setText)、宽高(setWidth/setHeight)、是否禁用(setEnable)等等)导致的样式失效。尽管在子线程中修改的控件和出现问题的消息窗口完全没有关系。
通过QDialog自定义的消息窗口:
正常消息窗口:
样式失效异常消息窗口:
所以,不管任何情况下,尽量不在非GUI线程访问UI控件,如果确实有必要,使用信号槽机制,通过发消息方式让子线程和GUI线程通讯。
例:
有一个Dialog窗口, 想通过线程修改按钮(pushButton)的禁用/启用, 虽然可以直接在线程中访问,运行也不会报错,但是尽量避开这样做,后期等项目越做越大可能会出现莫名其妙的问题:
Dialog::Dialog(QWidget *parent) : QDialog(parent) , ui(new Ui::Dialog) { ui->setupUi(this); setStyleSheet("Dialog QPushButton{border: 0 solid;border-radius: 5px;background-color: #0052FF;color: white;}" "Dialog #widget{background-color: white;border-radius: 4px;}" ); setWindowFlags(Qt::FramelessWindowHint|Qt::Tool); setAttribute(Qt::WA_TranslucentBackground, true); QtConcurrent::run([this]() { ui->pushButton->setEnabled(true); }); }
方法1:
可以在Dialog中声明一个信号,专门处理pushButton 启动/禁用操作:
class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget *parent = nullptr); ~Dialog(); private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked(); signals: void sigPushButtonEnable(bool ); private: Ui::Dialog *ui; };
Dialog::Dialog(QWidget *parent) : QDialog(parent) , ui(new Ui::Dialog) { ui->setupUi(this); setStyleSheet("Dialog QPushButton{border: 0 solid;border-radius: 5px;background-color: #0052FF;color: white;}" "Dialog #widget{background-color: white;border-radius: 4px;}" ); setWindowFlags(Qt::FramelessWindowHint|Qt::Tool); setAttribute(Qt::WA_TranslucentBackground, true); connect(this, &Dialog::sigPushButtonEnable, this, [this](bool isEnable) { ui->pushButton->setEnabled(isEnable); }); QtConcurrent::run([this]() { emit sigPushButtonEnable(true); }); }
方法2:
这样做不太灵活,如果后期除了想在子线程中修改pushButton的启动/禁用外,还想修改pushButton_2的启动/禁用,那就专门针对pushButton_2在写一个信号/槽,槽中写pushButton_2的setEnabled操作,如果在想修改pushButton的内容.....
可以通过C++11 标准库提供的std::function 函数对象类解决不灵活问题.
#include <functional> class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget *parent = nullptr); ~Dialog(); private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked(); signals: void sigPushButtonEnable(bool ); void sigGUIExec(std::function<void (void)> std); private: Ui::Dialog *ui; };
Q_DECLARE_METATYPE(std::function<void (void)>); Dialog::Dialog(QWidget *parent) : QDialog(parent) , ui(new Ui::Dialog) { ui->setupUi(this); setStyleSheet("Dialog QPushButton{border: 0 solid;border-radius: 5px;background-color: #0052FF;color: white;}" "Dialog #widget{background-color: white;border-radius: 4px;}" ); setWindowFlags(Qt::FramelessWindowHint|Qt::Tool); setAttribute(Qt::WA_TranslucentBackground, true); connect(this, &Dialog::sigPushButtonEnable, this, [this](bool isEnable) { ui->pushButton->setEnabled(isEnable); }); qRegisterMetaType<std::function<void (void)>>(); connect(this, &Dialog::sigGUIExec, this, [this](std::function<void (void)> std) { std(); }); qDebug() << QThread::currentThread();//GUI线程ID:QThread(0xf328613a20) QtConcurrent::run([this]() { emit sigPushButtonEnable(true); qDebug() << QThread::currentThread();//子线程:QThread(0xf32864a800, name = "Thread (pooled)") emit sigGUIExec([this]() { ui->pushButton_2->setEnabled(true); ui->pushButton_2->setText("Hello"); qDebug() << QThread::currentThread();//QThread(0xf328613a20) }); }); }
在子线程中除调用pushButton_2的setEnable外,还调用了setText,如果后期需要在子线程中访问其他的控件也可以直接通过发送信号emit sigGUIExec([](){})进行安全的GUI控件操作处理。