3

Firstly, I tried to use setVisible() from thread

There is an event:

void MainWindow::OnShow(){
    // Start OnShow actions
    ui->LoadingBox->setVisible(true);
    std::thread dThread(OnShow_threaded, ui, &(this->settingsMap));
    dThread.join();
}

There is a function 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);
    }
}

The program crashes when compiling a static assembly with an error:

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

On the line: ui->SettingsLabel->setVisible(true);

At the same time, there is no such error when linking dynamically.

You can find full project on GitHub


Secondly, I tried to use events.

There is a function 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;
    }
}

There is an event 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

There is an event handler:

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);
        }
    }
}

Passing parameters:

void MainWindow::OnShow(){
    // Start OnShow actions
    ui->LoadingBox->setVisible(true);
    std::thread dThread(OnShow_threaded, this, ui, &(this->settingsMap));
    dThread.detach();
}

There is a mainwindows hpp file:

#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

But event doesn't execute.
What did I do wrong?
And how to properly change the GUI from another thread?


З.Ы. Sorry for my English, I'm from Russia....


Pat. ANDRIA
  • 2,330
  • 1
  • 13
  • 27
  • You may not modify a GUI from a custom thread normally. You should modify your GUI from the UI Thread or use signal/slot to modify it from a Thread – Pat. ANDRIA Feb 05 '21 at 12:29
  • @Pat.ANDRIA so, how can I do it? I'm a student, so I only know the signals from am old dusty textbook – Alrott SlimRG Feb 05 '21 at 12:31
  • @Pat.ANDRIA can you give code? – Alrott SlimRG Feb 05 '21 at 12:32
  • FYI: another answer about [Qt GUI and std::thread](https://stackoverflow.com/a/61750145/7478597) – Scheff's Cat Feb 05 '21 at 12:35
  • @Scheff in your post user used timer, so it is not access from thread. User used data from thread every N seconds in GUI thread. I want to send this event only once. But there will be lots of interactions with different GUI objects at different points in time. – Alrott SlimRG Feb 05 '21 at 12:43
  • My post shows an example how to do interthread comm. with `std::atomic`s which are accessed in worker threads as well as in the GUI. As the GUI doesn't get "push" messages from the threads, it checks with `QTimer` about changes. I also gave an explanation that this has the advantage to achieve a stable load caused by these GUI updates preventing that threads start to flood the GUI with events (which might happen otherwise). – Scheff's Cat Feb 05 '21 at 12:51
  • @Alrott SlimRG are you obliged to use std::thread? Is it possible to use a `QThread` or must it be a `std::thread`? – Pat. ANDRIA Feb 05 '21 at 12:58
  • 1
    @Pat.ANDRIA i don't know how to make QThread in function, so if you give code with it - it will be great – Alrott SlimRG Feb 05 '21 at 13:00

3 Answers3

2

In Qt, like in many other GUI frameworks, the GUI may be updated from the main thread only. This means if you want to update the GUI from another thread, you have to communicate that to the main thread, which in turn will update the GUI.

For more details refer to these articles:


Additional resources in other languages: Правильная работа с потоками в Qt.

rustyx
  • 80,671
  • 25
  • 200
  • 267
2

As you asked for the demo with QThread, in the comments, then here it is.

As GUI, I have a mainwindow with two simple buttons and I want to show, hide the big buttons with a QThread (instead of just the slot clicked) I emit an intermediate signal from the clicked to hide/show the button.

The role of the QThreadis only to emit the signal to sigShowHide with argument trueor false.

The main UI thread treats this signal by showing or hiding the button by calling the slot onShowHideButtonThreaded which reacts to the signal sigShowHide

enter image description here

Here are the code files:

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();
  }

`

The main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

An example of execution is :

the main thread id = QThread(0x116ee18)
Running Thread 0x1ed8 to emit signal only 
the main thread id = QThread(0x116ee18) set the visibility 
Pat. ANDRIA
  • 2,330
  • 1
  • 13
  • 27
2

As stated in @rustyx's answer: In Qt, like in many other GUI frameworks, the GUI may be updated from the main thread only.

I was also stuck on this problem, and here are my two solutions:

  1. Use QMetaObject::invokeMethod.

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

    QMetaObject::invokeMethod is a thread-safe API, it has a Qt::ConnectionType type parameter, which has a default value Qt::AutoConnection.

    Descriptions of some Qt::ConnectionType values:

    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.

    It is clear from the description that the API will put your call request into a queue, and then the main thread will take it out from the queue to finally process your call request.

    And if you need to get the return value of the target method, you need to explicitly pass Qt::BlockingQueuedConnection to the Qt::ConnectionType type parameter. Because the indirect default value Qt::QueuedConnection will not wait for the call to complete, you will likely get the wrong return value.

    But this solution has a trouble that is it only supports signal and slot functions, NOT non-signal and non-slot functions.

  2. Use 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 also has a Qt::ConnectionType type parameter, which has a default value Qt::AutoConnection if you pass the receiver parameter explicitly, otherwise the default value is Qt::DirectConnection. So you need to either explicitly pass the receiver parameter or explicitly pass Qt::AutoConnection value to the Qt::ConnectionType type parameter.

    If the target method has a return value, Qt::BlockingQueuedConnection needs to be passed explicitly as well.

    This solution supports signal and slot functions, as well as non-signal and non-slot functions.

Both of the above solutions require you to call qRegisterMetaType to register the user-defined types (if any).

qRegisterMetaType<YourType>("YourType");

Obviously, I prefer the second solution.

Sprite
  • 3,222
  • 1
  • 12
  • 29
  • From a `std::thread` to a GUI object, you know that you need `Qt::QueuedConnection`. `Qt::DirectConnection` can't possibly work – MSalters Feb 05 '21 at 15:02
  • @MSalters If you pass the `receiver` parameter explicitly when calling `connect`, the `Qt::ConnectionType type` parameter value is `Qt::AutoConnection` (default). – Sprite Feb 05 '21 at 15:07
  • @MSalters Oh, maybe my wording wasn't clear, I've edited my answer. Thank you! – Sprite Feb 05 '21 at 15:13