我可以在非 GUI 线程上创建小部件,然后发送到 GUI 吗? [英] Can I create widgets on a non-GUI thread, then send to the GUI?

查看:56
本文介绍了我可以在非 GUI 线程上创建小部件,然后发送到 GUI 吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的 MainWindow 类中有一个复杂的函数,它会定期运行查询并更改小部件的属性和数据.由于在主线程上可能需要很长时间,因此 GUI 可能会冻结.

所以我在另一个线程上创建了一个 GUIUpdater 类来执行周期性操作.我基于这里的解决方案,它展示了如何从另一个线程更新 QLabel:

I have a complex function in my MainWindow class which periodically runs queries and changes attributes and data of the widgets. Since it can take a long time on the main thread, the GUI can appear to freeze.

So I created a GUIUpdater class on another thread to do the periodic operation. I based it on the solution here, which shows how to update a QLabel from another thread:

Qt - updating main window with second thread

But that solution requires defining a connection. With a complex function with multiple widgets, it is difficult to define a connection for each attribute and data of the widgets.

Is there an easier way? For instance: Can I create entirely new widgets making the same API calls as on the main thread in the GUIUpdater thread, and send the whole widget over to the UI using a signal, to be replaced in the UI?

解决方案

Your non-GUI "requests" shall know nothing about widgets.

GUI and "requests" shall be as independent as possible. You cannot create widgets in a non-GUI thread, but you can create your own class that can update GUI widgets inside itself by calls from another thread. Widgets for such classes should be set once from a GUI thread, but then you can apply values to the widgets by "direct" calls. The idea is to have one base class which will do queued method invokes for you. Also I introduced one QObject-based interface class:

IUiControlSet.h

#include <QObject>
#include <QVariant>

class QWidget;

// Basic interface for an elements set which should be updated
// and requested from some non-GUI thread
class IUiControlSet: public QObject
{
    Q_OBJECT

public:
    virtual void setParentWidget(QWidget* par) = 0;

    virtual void setValues(QVariant var) = 0;

    virtual QVariant values() = 0;

signals:
    void sControlChanged(QVariant var);
};

Then base class to perform queued- and common operations: BaseUiControlSet.h

#include "IUiControlSet.h"

#include <QList>

class QDoubleSpinBox;
class QPushButton;

// Abstract class to implement the core queued-based functionality
class BaseUiControlSet : public IUiControlSet
{
    Q_OBJECT

public slots:
    void setParentWidget(QWidget* par) override;

    void setValues(QVariant var) override;

    QVariant values() override;

protected slots:
    virtual void create_child_elements() = 0;

    virtual void set_values_impl(QVariant var) = 0;
    virtual QVariant values_impl() const = 0;

    void on_control_applied();

protected:
    // common elements creation
    QDoubleSpinBox* create_spinbox() const;
    QPushButton* create_applied_button() const;

protected:
    QWidget*        m_parentWidget = nullptr;
};

BaseUiControlSet.cpp

#include "BaseUiControlSet.h"

#include <QDoubleSpinBox>
#include <QPushButton>

#include <QThread>

void BaseUiControlSet::setParentWidget(QWidget* par)
{
    m_parentWidget = par;

    create_child_elements();
}

// The main idea
void BaseUiControlSet::setValues(QVariant var)
{
    QMetaObject::invokeMethod(this, "set_values_impl",
        Qt::QueuedConnection, Q_ARG(QVariant, var));
}

// The main idea
QVariant BaseUiControlSet::values()
{
    QVariant ret;

    QThread* invokeThread = QThread::currentThread();
    QThread* widgetThread = m_parentWidget->thread();

    // Check the threads affinities to avid deadlock while using Qt::BlockingQueuedConnection for the same thread
    if (invokeThread == widgetThread)
    {
        ret = values_impl();
    }
    else
    {
        QMetaObject::invokeMethod(this, "values_impl",
            Qt::BlockingQueuedConnection, 
            Q_RETURN_ARG(QVariant, ret));
    }

    return ret;
}

void BaseUiControlSet::on_control_applied()
{
    QWidget* wgt = qobject_cast<QWidget*>(sender());

    QVariant val = values();

    emit sControlChanged(val);
}

// just simplify code for elements creation
// not necessary
QDoubleSpinBox* BaseUiControlSet::create_spinbox() const
{
    auto berSpinBox = new QDoubleSpinBox(m_parentWidget);


    bool connectOk = connect(berSpinBox, &QDoubleSpinBox::editingFinished,
        this, &BaseUiControlSet::on_control_applied);

    Q_ASSERT(connectOk);

    return berSpinBox;
}

// just simplify code for elements creation
// not necessary
QPushButton* BaseUiControlSet::create_applied_button() const
{
    auto button = new QPushButton(m_parentWidget);

    bool connectOk = connect(button, &QPushButton::clicked,
        this, &BaseUiControlSet::on_control_applied);

    Q_ASSERT(connectOk);

    return button;
}


Your control example:

MyControl.h

#include "BaseUiControlSet.h"

// User control example
class MyControl : public BaseUiControlSet
{
    Q_OBJECT

public:
    struct Data
    {
        double a = 0;
        double b = 0;
    };

protected slots:
    void create_child_elements() override;

    void set_values_impl(QVariant var) override;
    QVariant values_impl() const override;

private:
    QDoubleSpinBox*     dspin_A = nullptr;
    QDoubleSpinBox*     dspin_B = nullptr;

    QPushButton*        applyButton = nullptr;
};

Q_DECLARE_METATYPE(MyControl::Data);

MyControl.cpp

#include "MyControl.h"

#include <QWidget>

#include <QDoubleSpinBox>
#include <QPushButton>

#include <QVBoxLayout>

void MyControl::create_child_elements()
{
    dspin_A = create_spinbox();
    dspin_B = create_spinbox();

    applyButton = create_applied_button();
    applyButton->setText("Apply values");

    auto layout = new QVBoxLayout;

    layout->addWidget(dspin_A);
    layout->addWidget(dspin_B);
    layout->addWidget(applyButton);

    m_parentWidget->setLayout(layout);
}

void MyControl::set_values_impl(QVariant var)
{
    Data myData = var.value<MyControl::Data>();

    dspin_A->setValue(myData.a);
    dspin_B->setValue(myData.b);
}

QVariant MyControl::values_impl() const
{
    Data myData;

    myData.a = dspin_A->value();
    myData.b = dspin_B->value();

    return QVariant::fromValue(myData);
}


Example of use:

MainWin.h

#include <QtWidgets/QWidget>
#include "ui_QueuedControls.h"

#include <QVariant>

class MainWin : public QWidget
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = Q_NULLPTR);

private slots:
    void on_my_control_applied(QVariant var);

private:
    Ui::QueuedControlsClass ui;
};

MainWin.cpp

#include "MainWin.h"

#include "MyControl.h"

#include <QtConcurrent> 
#include <QThread>

#include <QDebug>

MainWin::MainWin(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);

    auto control = new MyControl;

    control->setParentWidget(this);

    connect(control, &IUiControlSet::sControlChanged,
        this, &MainWin::on_my_control_applied);

    // Test: set the GUI spinboxes' values from another thread
    QtConcurrent::run(
        [=]()
    {
        double it = 0;

        while (true)
        {
            it++;

            MyControl::Data myData;

            myData.a = it / 2.;
            myData.b = it * 2.;

            control->setValues(QVariant::fromValue(myData)); // direct call

            QThread::msleep(1000);
        }
    });
}

// will be called when the "Apply values" button pressed,
// or when spinboxes editingFinished event triggered
void MainWin::on_my_control_applied(QVariant var)
{
    MyControl::Data myData = var.value<MyControl::Data>();

    qDebug() << "value a =" << myData.a;
    qDebug() << "value b =" << myData.b;
}


After a few seconds:

这篇关于我可以在非 GUI 线程上创建小部件,然后发送到 GUI 吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆