Ваши "запросы" без 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;
}
Через несколько секунд: