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