【问题标题】:How can I change GUI from std::thread?如何从 std::thread 更改 GUI?
【发布时间】:2021-02-05 12:18:00
【问题描述】:

首先,我尝试使用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);
    }
}

编译静态程序集报错时程序崩溃:

QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到 由不同线程拥有的对象。当前线程 0x0x36c56540。 接收器“WarningMsg”(类型为“QGroupBox”)在线程中创建 0x0x341c2fa0",文件 kernel\qcoreapplication.cpp,第 558 行

在线:ui-&gt;SettingsLabel-&gt;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;
    }
}

有一个事件类:

#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?


З.Ы.对不起我的英语,我来自俄罗斯....


【问题讨论】:

  • 您不能正常从自定义线程修改 GUI。您应该从 UI 线程修改您的 GUI,或使用信号/插槽从线程修改它
  • @Pat.ANDRIA 那么,我该怎么做呢?我是学生,所以我只知道来自尘土飞扬的旧教科书的信号
  • @Pat.ANDRIA 可以给代码吗?
  • 仅供参考:关于Qt GUI and std::thread的另一个答案
  • @Pat.ANDRIA 我不知道如何使 QThread 发挥作用,所以如果你用它提供代码 - 它会很棒

标签: c++ qt user-interface qt5 c++17


【解决方案1】:

正如您要求在 cmets 中使用 QThread 进行演示,那么就在这里。

作为 GUI,我有一个带有两个简单按钮的主窗口,我想显示,用 QThread 隐藏大按钮(而不仅仅是单击的插槽)我从单击的位置发出一个中间信号以隐藏/显示按钮。

QThread的作用只是向sigShowHide发出信号,参数为truefalse

主 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 

【讨论】:

    【解决方案2】:

    在 Qt 中,与许多其他 GUI 框架一样,GUI 只能从主线程更新。这意味着如果您想从另一个线程更新 GUI,您必须将其传达给主线程,而主线程又会更新 GUI。

    有关更多详细信息,请参阅以下文章:


    其他语言的其他资源:Правильная работа с потоками в Qt

    【讨论】:

      【解决方案3】:

      正如@rustyx 的answer 中所述:在Qt 中,与许多其他GUI 框架一样,GUI 只能从主线程更新。

      我也被这个问题困住了,这是我的两个解决方案:

      1. 使用QMetaObject::invokeMethod

        QMetaObject::invokeMethod(ui->SettingsLabel, "setVisible", Q_ARG(bool, true));
        

        QMetaObject::invokeMethod是一个线程安全的API,它有一个Qt::ConnectionType type参数,它有一个默认值Qt::AutoConnection

        一些Qt::ConnectionType值的描述:

        Qt::AutoConnection:

        (默认)如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。

        Qt::DirectConnection:

        当信号发出时,槽会立即被调用。该槽在信号线程中执行。

        Qt::QueuedConnection:

        当控制返回到接收者线程的事件循环时调用该槽。该槽在接收者的线程中执行。

        Qt::BlockingQueuedConnection:

        与 Qt::QueuedConnection 相同,只是信号线程阻塞直到槽返回。如果接收者位于信号线程中,则不得使用此连接,否则应用程序将死锁。

        从描述中可以看出,API会将你的调用请求放入队列,然后主线程从队列中取出来最终处理你的调用请求。

        而如果需要获取目标方法的返回值,则需要将Qt::BlockingQueuedConnection显式传递给Qt::ConnectionType type参数。由于间接默认值Qt::QueuedConnection 不会等待调用完成,因此您可能会得到错误的返回值。

        但是这个方案有个问题是它只支持信号和槽函数,不支持非信号和非槽函数。

      2. 使用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参数。

        如果目标方法有返回值,Qt::BlockingQueuedConnection 也需要显式传递。

        此方案支持信号和槽函数,以及非信号和非槽函数。

      上述两种解决方案都需要您调用qRegisterMetaType 来注册用户定义的类型(如果有的话)。

      qRegisterMetaType<YourType>("YourType");
      

      显然,我更喜欢第二种解决方案。

      【讨论】:

      • std::thread 到GUI 对象,你知道你需要Qt::QueuedConnectionQt::DirectConnection 不可能工作
      • @MSalters 如果在调用connect 时显式传递receiver 参数,则Qt::ConnectionType type 参数值为Qt::AutoConnection(默认)。
      • @MSalters 哦,也许我的措辞不清楚,我已经编辑了我的答案。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2012-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多