我的MainWindow类中有一个复杂的函数,该函数定期运行查询并更改小部件的属性和数据。由于在主线程上可能要花费很长时间,因此GUI可能会冻结。
因此,我在另一个线程上创建了一个GUIUpdater类来执行定期操作。我基于此处的解决方案,该解决方案展示了如何从另一个线程更新QLabel:
Qt - updating main window with second thread
但是该解决方案需要定义一个连接。对于具有多个小部件的复杂功能,很难为每个小部件的属性和数据定义连接。
有没有更简单的方法?例如:是否可以创建全新的小部件,使其与GUIUpdater线程中的主线程上的API调用相同,并使用信号将整个小部件发送到UI,并在UI中替换?
答案 0 :(得分:3)
您的非GUI“请求”对小部件一无所知。
GUI和“请求”应尽可能独立。您不能在非GUI线程中创建窗口小部件,但是可以创建自己的类,该类可以通过来自另一个线程的调用来更新其内部的GUI窗口小部件。应该从GUI线程设置一次此类类的窗口小部件,但是您可以通过“直接”调用将值应用于窗口小部件。想法是有一个基类,它将为您执行排队方法调用。我还介绍了一个基于QObject的接口类:
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);
};
然后,基类执行排队的和常见的操作: 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;
}
您的控制示例:
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);
}
使用示例:
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;
}
几秒钟后:
答案 1 :(得分:2)
我是否可以创建全新的小部件,使其与GUIUpdater线程中的主线程进行相同的API调用,然后使用信号将整个小部件发送到UI,并在UI中进行替换?
您不能在非GUI线程上创建“小部件”。但是Qt术语中的“小部件”通常由两个部分组成,即“视图”和“模型”。 (如果您还没有,请读一下Qt's model/view separation,也许可以通过Model/View tutorial进行学习)
这意味着,可以使用接口部分链接到保存数据的单独部分,而不是使用同时管理界面和数据的便利“小部件”。实际上,数据源可能是完全虚拟的...只是响应要求它的功能(您将不得不为该行为编写自定义代码-可能值得这样做)。
因此,尽管您不能以编程方式在非GUI线程上构建窗口小部件,但是可以构建类似QStandardItemModel的东西。然后,您的UI可以使用QTreeWidget,而不是使用QTreeView之类的小部件。然后,您无需转移线程之间的窗口小部件,而是转移模型并将其连接到视图...将旧模型扔掉。
注意,但是... ,您必须将模型转移到GUI线程才能与小部件一起使用。数据模型和视图必须具有相同的thread affinity-我前一段时间遇到了这个问题(这是邮件列表中讨论的缓存,丢失了):
http://blog.hostilefork.com/qt-model-view-different-threads/
编写使用互斥量和信号量的数据模型是另一种方法。但是,通常很难正确地实现C ++中的多线程编程。因此,如果您正在做的事情确实很棘手,那么不会有任何简单超级答案。只需记住,无论您做什么,模型和视图最终都必须位于同一线程上才能协同工作。