Blocking operation in Qt Quick - c++

I want to perform a long task when a button is clicked. I want this task to block the UI, because the application cannot function until the task is done. However, I want to indicate to the user that something is happening, so I have a BusyIndicator (that runs on the render thread) and is set to display before the operation begins. However, it never renders. Why?
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDateTime>
#include <QDebug>
class Task : public QObject
{
Q_OBJECT
Q_PROPERTY(bool running READ running NOTIFY runningChanged)
public:
Task() : mRunning(false) {}
Q_INVOKABLE void run() {
qDebug() << "setting running property to true";
mRunning = true;
emit runningChanged();
// Try to ensure that the scene graph has time to begin the busy indicator
// animation on the render thread.
Q_ASSERT(QMetaObject::invokeMethod(this, "doRun", Qt::QueuedConnection));
}
bool running() const {
return mRunning;
}
signals:
void runningChanged();
private:
Q_INVOKABLE void doRun() {
qDebug() << "beginning long, blocking operation";
QDateTime start = QDateTime::currentDateTime();
while (start.secsTo(QDateTime::currentDateTime()) < 2) {
// Wait...
}
qDebug() << "finished long, blocking operation";
qDebug() << "setting running property to false";
mRunning = false;
emit runningChanged();
}
bool mRunning;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Task task;
engine.rootContext()->setContextProperty("task", &task);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.6
import QtQuick.Window 2.2
import Qt.labs.controls 1.0
Window {
width: 600
height: 400
visible: true
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
Column {
anchors.centerIn: parent
spacing: 20
Button {
text: task.running ? "Running task" : "Run task"
onClicked: task.run()
}
BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: task.running
onRunningChanged: print("BusyIndicator running =", running)
}
}
}
The debugging output looks correct in terms of the order of events:
setting running property to true
qml: BusyIndicator running = true
beginning long, blocking operation
finished long, blocking operation
setting running property to false
qml: BusyIndicator running = false

Most animations in QML depends on properties managed in the main thread and are thus blocked when the main UI thread is blocked. Look into http://doc.qt.io/qt-5/qml-qtquick-animator.html for animations that can run when the main thread is blocked. If possible, I'd move the operation into another thread though, that's much simpler and also allows for e.g. cancellation of the operation from the UI.

Invoking a function with Qt::QueuedConnection doesn't guarantee that the BusyIndicator has had the chance to begin animating. It merely guarantees that:
The slot is invoked when control returns to the event loop of the receiver's thread.
Another solution that might sound promising is QTimer::singleShot():
QTimer::singleShot(0, this, SLOT(doRun()));
As a special case, a QTimer with a timeout of 0 will time out as soon as all the events in the window system's event queue have been processed. This can be used to do heavy work while providing a snappy user interface [...]
However, this also won't work. I'm not sure why. It could be that, internally, the rendering/animation is not done via a queued invocation, and so the timeout happens too early.
You could specify an arbitrary amount of time to wait:
QTimer::singleShot(10, this, SLOT(doRun()));
This will work, but it's not that nice; it's just guessing.
What you need is a reliable way of knowing when the scene graph has begun the animation.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QQuickWindow>
class Task : public QObject
{
Q_OBJECT
Q_PROPERTY(bool running READ running NOTIFY runningChanged)
public:
Task(QObject *parent = 0) :
QObject(parent),
mRunning(false) {
}
signals:
void runningChanged();
public slots:
void run() {
qDebug() << "setting running property to true";
mRunning = true;
emit runningChanged();
QQmlApplicationEngine *engine = qobject_cast<QQmlApplicationEngine*>(parent());
QQuickWindow *window = qobject_cast<QQuickWindow*>(engine->rootObjects().first());
connect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
}
bool running() const {
return mRunning;
}
private slots:
void doRun() {
qDebug() << "beginning long, blocking operation";
QDateTime start = QDateTime::currentDateTime();
while (start.secsTo(QDateTime::currentDateTime()) < 2) {
// Wait...
}
qDebug() << "finished long, blocking operation";
QQmlApplicationEngine *engine = qobject_cast<QQmlApplicationEngine*>(parent());
QQuickWindow *window = qobject_cast<QQuickWindow*>(engine->rootObjects().first());
disconnect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
qDebug() << "setting running property to false";
mRunning = false;
emit runningChanged();
}
private:
bool mRunning;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Task task(&engine);
engine.rootContext()->setContextProperty("task", &task);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.6
import QtQuick.Window 2.2
import Qt.labs.controls 1.0
Window {
width: 600
height: 400
visible: true
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
Column {
anchors.centerIn: parent
spacing: 20
Button {
text: task.running ? "Running task" : "Run task"
onClicked: task.run()
}
BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: task.running
onRunningChanged: print("BusyIndicator running =", running)
}
}
}
This solution relies on having access to the application window, which isn't that nice, but it eliminates any guesswork. Note that if we don't disconnect from the signal afterwards, it will continue to be called each time the scene graph has finished synchronising, so it's important to do this.
If you have several operations that will need this type of solution, consider creating a reusable class:
class BlockingTask : public QObject
{
Q_OBJECT
Q_PROPERTY(bool running READ running NOTIFY runningChanged)
public:
BlockingTask(QQmlApplicationEngine *engine) :
mEngine(engine),
mRunning(false) {
}
bool running() const {
return mRunning;
}
signals:
void runningChanged();
public slots:
void run() {
qDebug() << "setting running property to true";
mRunning = true;
emit runningChanged();
QQuickWindow *window = qobject_cast<QQuickWindow*>(mEngine->rootObjects().first());
connect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
}
protected:
virtual void execute() = 0;
private slots:
void doRun() {
QQuickWindow *window = qobject_cast<QQuickWindow*>(mEngine->rootObjects().first());
disconnect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
execute();
qDebug() << "setting running property to false";
mRunning = false;
emit runningChanged();
}
private:
QQmlApplicationEngine *mEngine;
bool mRunning;
};
Then, subclasses only have to worry about their logic:
class Task : public BlockingTask
{
Q_OBJECT
public:
Task(QQmlApplicationEngine *engine) :
BlockingTask(engine) {
}
protected:
void execute() Q_DECL_OVERRIDE {
qDebug() << "beginning long, blocking operation";
QDateTime start = QDateTime::currentDateTime();
while (start.secsTo(QDateTime::currentDateTime()) < 2) {
// Wait...
}
qDebug() << "finished long, blocking operation";
}
};

Related

Qt QML - QModBus read corrupted by QML BusyIndicator/Animation - SingleThread

I have a single thread QQuick application with one main window and one class that handles Modbus Write/Read functions. Everything is working fine so far but when I put a BusyIndicator in my qml window to show that the Bus is busy I get CRC mismatches and response timeouts, e.g.:
"Discarding response with wrong CRC, received: 64580 , calculated CRC: 55067"
"Read response error: Response timeout. (code: 0x5)" - qt.modbus: (RTU client) Cannot match response with open request, ignoring
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "modbusinterface.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
ModbusInterface modbus;
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
engine.rootContext()->setContextProperty("modbus", &modbus);
return app.exec();
}
modbusinterface.h
#ifndef MODBUSINTERFACE_H
#define MODBUSINTERFACE_H
#include <QObject>
#include <QSerialPort>
#include <QModbusRtuSerialMaster>
#include <QModbusDevice>
#include <QModbusClient>
#include <QVariant>
#include <QDebug>
class ModbusInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(bool busBusy READ busBusy NOTIFY busBusyChanged)
public:
explicit ModbusInterface(QObject *parent = nullptr);
bool busBusy(void) {return m_busBusy;}
Q_INVOKABLE bool read(int deviceId, int startAddress, quint16 count);
public slots:
void readReady();
signals:
void busBusyChanged();
private:
bool m_busBusy = false;
QModbusReply *m_lastRequest = nullptr;
QModbusClient *m_client = nullptr;
};
#endif // MODBUSINTERFACE_H
modbusinterface.cpp
#include "modbusinterface.h"
ModbusInterface::ModbusInterface(QObject *parent) : QObject(parent)
{
m_client = new QModbusRtuSerialMaster();
m_client->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "ttyUSB0");
m_client->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud19200);
m_client->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
m_client->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
m_client->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
m_client->setTimeout(1000);
m_client->setNumberOfRetries(1);
if (!m_client->connectDevice()) {
qDebug() << "Connect failed: " << m_client->errorString();
} else {
qDebug() << "Modbus Client is Connected";
}
}
bool ModbusInterface::read(int deviceId, int startAddress, quint16 count)
{
QModbusDataUnit RxData;
if(startAddress>=40000) RxData.setRegisterType(QModbusDataUnit::HoldingRegisters);
else RxData.setRegisterType(QModbusDataUnit::InputRegisters);
RxData.setStartAddress(startAddress);
RxData.setValueCount(count);
if (!m_client) {
qDebug() << "!m_client";
return false;
}
if (m_client->state() != QModbusDevice::ConnectedState){
qDebug() << "Modbus Client is not Connected in read section";
return false;
}
if (auto *reply = m_client->sendReadRequest(RxData, deviceId))
{
if (!reply->isFinished()){
connect(reply, &QModbusReply::finished, this, &ModbusInterface::readReady);
m_lastRequest = reply;
m_busBusy = true;
emit busBusyChanged();
} else {
delete reply;
}
return true;
}
return false;
}
void ModbusInterface::readReady()
{
auto reply = qobject_cast<QModbusReply *>(sender());
if (!reply) return;
if( reply == m_lastRequest){
m_busBusy = false;
emit busBusyChanged();
}
reply->deleteLater();
if (reply->error() == QModbusDevice::NoError)
{
qDebug() << reply;
}
else if (reply->error() == QModbusDevice::ProtocolError)
{
qDebug() << QString("Read response error: %1 (Mobus exception: 0x%2)").
arg(reply->errorString()).
arg(reply->rawResult().exceptionCode(), -1, 16);
} else {
qDebug() << QString("Read response error: %1 (code: 0x%2)").
arg(reply->errorString()).
arg(reply->error(), -1, 16);
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
Window {
width: 640
height: 480
visible: true
title: qsTr("ModbusTrial")
Button{
id: button
anchors.centerIn: parent
text: "read Modbus"
onClicked: {
for(var i=1; i<=10; i++){
modbus.read(i, 30001, 1)
}
}
}
BusyIndicator{
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: button.bottom
visible: modbus.busBusy //false
}
}
In this minimal working example I trigger/queue 10 read requests via a qml/c++ interface to my modbusinterface-class. As I acess devices which serveradress is not necessary known I need to scan multiple IDs for valid responses and wanted to display a busyIndicator during that time. That causes the mentioned timeout/CRC errors.
If there's no animation running in my window, the data is received without errors.
Can this be fixed by using a separate thread to run the modbus read/write methods and how would I implement that? Or would I only increase the misreads by putting the Serial functions in a separate Thread?
As I understand it so far, due to the fact that my application is running in a single thread, the continuous update of the GUI is somehow interfering with the reception of Serial Data.
I used the linux command line tool "stress" to see if I lose data under high cpu load as well but that's not the case.
Interestingly, the first response I get is always valid. So could there be a problem with queuing the requests the way I do? In the doc it says:
Note: QModbusClient queues the requests it receives. The number of requests executed in parallel is dependent on the protocol. For example, the HTTP protocol on desktop platforms issues 6 requests in parallel for one host/port combination.
Kind regards
I found the issue:
As I thought the qml rendering engine, scene graph or whatever you call it, caused missed frames of the modbus reception. I guess somehow threading might have helped with that, but I was not able to fix it by having the modbusInterface run in a separate thread.
In the end the solution was to enable the threaded render loop of the scene graph as stated here: https://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html#threaded-render-loop-threaded
I.e. by putting
qputenv("QSG_RENDER_LOOP","threaded");
in my main().

Is it possible to have a QObject set as a QML context property in a separate thread?

I have built a small test example to better understand the QML/C++ bindings offered by Qt 5.
The QML side is basically a StackLayout with several Page that display informations on the console or in a Label. The user is able to navigate through these pages using a Button.
As long as everything runs in a single thread, it's fine.
The QML/C++ bindings through QObject's signals and slots are working as expected.
But when i try to move the QObject exposed to the QML in another thread, i get this message and the application is killed :
QQmlEngine: Illegal attempt to connect to BackendWorker(0xbe8a7c28) that is in a different thread than the QML engine QQmlApplicationEngine(0xbe8a7c44.
Here is the main of the application :
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
BackendWorker backendWorker; // expose a signal and a slot only
QQmlApplicationEngine engine;
QQmlContext *ctx = engine.rootContext();
ctx->setContextProperty("backendWorker", &backendWorker);
QThread t1;
backendWorker.moveToThread(&t1); // here is the offending part
t1.start();
engine.load(QUrl(QStringLiteral("qrc:/UI/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Does all QObject's exposed to QML ( with properties, signals or slots or Q_INVOKABLE's) have to live in the same thread as the QGuiApplication ?
EDIT :
Here is a smaller example that shows a QStringListModel living in a separate thread than the QML ListView and it works, so how is this possible ?
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QStringListModel>
#include <QQmlContext>
#include <QTimer>
#include <QThread>
class Functor
{
public:
Functor(QStringListModel *model) : m_model(model) { }
void operator()() {
QStringList list;
list << "item 5" << "item 6" << "item 7" ;
m_model->setStringList(list);
}
private:
QStringListModel *m_model;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QStringListModel listModel;
Functor functor(&listModel);
QQmlApplicationEngine engine;
QQmlContext *ctx = engine.rootContext();
ctx->setContextProperty("listModel", &listModel);
QThread t1;
QStringList list;
list << "item 1" << "item 2" << "item 3" << "item 4" ;
listModel.setStringList(list);
listModel.moveToThread(&t1);
t1.start();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer::singleShot(5000, functor);
return app.exec();
}
QML side :
// main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
ListView {
width: 200
height: 500
anchors.centerIn: parent
model: listModel
delegate: Rectangle {
height: 50
width: 200
Text {
text : display
}
}
}
}
Thank you.
Connections across thread boundaries are supported by Qt in C++ level but not in QML level. It's probable QML engine won't support them in a near future (if ever). See the bug report It's not possible to connect two QML objects living in different threads which was closed as out of scope.
Below, you can see a simplified example to implement a worker and register it to QML engine as a context property:
class BackendWorker : public QObject {
Q_OBJECT
public slots:
void operate() {
int i = 0;
while (i < 1000) {
report(i++);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
signals:
void report(int x);
};
class BackendController : public QObject {
Q_OBJECT
public:
BackendController() {
w.moveToThread(&t);
connect(this, &BackendController::start, &w, &BackendWorker::operate);
connect(&w, &BackendWorker::report, this, &BackendController::report);
t.start();
}
signals:
void start();
void report(int x);
private:
BackendWorker w;
QThread t;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
BackendController c;
engine.rootContext()->setContextProperty("BackendController", &c);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}

Qt Qml connect to a signal of a QObject property of a Context Property

So this may seem like a strange setup. I have a C++ object that inherits from QObject called "MasterGuiLogic" for simplicity. It is created with a pointer to another object called "MainEventBroker" which as you might guess handles all of my applications events. The MasterGuiLogic object is registered with qml as a context property so that it's properties can be used anywhere in my qml. So main.cpp looks like this:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MasterEventBroker *MainEventBroker = new MasterEventBroker();
MasterGuiLogic *MainGuiLogic = new MasterGuiLogic(*MainEventBroker);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("MasterGuiLogic", &(*MainGuiLogic));
engine.load(QUrl(QLatin1String("qrc:/QmlGui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
MasterGuiLogic creates an instance of another class called SerialCom, which is set as a Q_PROPERTY so that it's properties and public slots can be reached in qml through the MasterGuiLogic property.
MasterGuiLogic.h:
class MasterGuiLogic : public QObject
{
Q_OBJECT
Q_PROPERTY(GuiLogic_SerialCom* serialCom READ serialCom CONSTANT)
public:
MasterEventBroker *eventBroker;
explicit MasterGuiLogic(MasterEventBroker &ev, QObject *parent = nullptr);
GuiLogic_SerialCom* serialCom() const {
return Gui_SerialCom;
}
private:
GuiLogic_SerialCom *Gui_SerialCom;
MasterGuiLogic.cpp:
MasterGuiLogic::MasterGuiLogic(MasterEventBroker &ev, QObject *parent) : QObject(parent)
{
this->eventBroker = &ev;
this->Gui_SerialCom = new GuiLogic_SerialCom(this);
}
SerialCom.h:
//Forward Declare our parent
class MasterGuiLogic;
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList portNames READ portNames NOTIFY portNamesChanged)
Q_PROPERTY(bool connectedToPort READ connectedToPort NOTIFY connectedToPortChanged)
public:
MasterGuiLogic *parent;
explicit GuiLogic_SerialCom(MasterGuiLogic *parent = nullptr);
std::map<QString, QSerialPortInfo> portsMap;
QStringList portNames() {
return _portNames;
}
bool connectedToPort() {
return _connectedToPort;
}
private:
QStringList _portNames;
bool _connectedToPort = false;
signals:
void portNamesChanged(const QStringList &);
void connectedToPortChanged(const bool &);
public slots:
void connectToPort(const QString portName);
void disconnectFromPort(const QString portName);
};
SerialCom.cpp:
GuiLogic_SerialCom::GuiLogic_SerialCom(MasterGuiLogic *parent) : QObject(qobject_cast<QObject *>(parent))
{
this->parent = parent;
QList<QSerialPortInfo> allPorts = QSerialPortInfo::availablePorts();
for (int i = 0; i < allPorts.size(); ++i) {
this->_portNames.append(allPorts.at(i).portName());
this->portsMap[allPorts.at(i).portName()] = allPorts.at(i);
}
emit portNamesChanged(_portNames);
}
void GuiLogic_SerialCom::connectToPort(const QString portName) {
//TODO: Connect To Port Logic Here;
//Set Connected
this->_connectedToPort = true;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
void GuiLogic_SerialCom::disconnectFromPort(const QString portName) {
//TODO: DisConnect To Port Logic Here;
//Set DisConnected
this->_connectedToPort = false;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
So from qml it's pretty easy to read any of these properties and even send signals from qml to c++
For example, this works just fine:
connectCom.onClicked: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
MasterGuiLogic.serialCom.connectToPort(comPort.currentText);
} else {
MasterGuiLogic.serialCom.disconnectFromPort(comPort.currentText);
}
}
The problem is, I can't seem to find a way to connect to signals that are emitted from SerialCom. I thought I would be able to do something like this:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
connectCom.text = "Disconnect";
comPort.enabled = false;
} else {
connectCom.text = "Connect";
comPort.enabled = true;
}
}
}
This should listen to the boolean property on SerialCom to change, but I get the following error:
QQmlApplicationEngine failed to load component
qrc:/QmlGui/main.qml:21 Type Page1 unavailable
qrc:/QmlGui/Page1.qml:49 Invalid attached object assignment
This just means that I can't "connect" using the target line above. Is there any other way I can connect to signals from a Q_PROPERTY of type QObject inside a ContextProperty?
First of all what should &(*MainGuiLogic) mean?
You are dereferencing and referencing again the MainGuiLogic? Why?
context->setContextProperty("MasterGuiLogic", MainGuiLogic); will be enought.
But registering MasterGuiLogic as Type and adding the Object named MasterGuiLogic can overide themself in QML world.
Set it like context->setContextProperty("MyGuiLogic", MainGuiLogic); to eleminate this behavior.
Also don't pass references between C++ and QML worlds like:
void connectedToPortChanged(**const bool &**);.
Just use atomic type and values (const bool);
and give it a name, to be able to use it as named value in QML:
void connectedToPortChanged(bool connected)
Here is an example with the structure like yours, which works. Just click in window and look in output console.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QObject>
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
public:
GuiLogic_SerialCom(){}
signals:
void connectedToPortChanged(bool connected);
public slots:
void connectToPort(const QString & portName);
};
class MasterGuiLogic : public QObject
{
Q_OBJECT
public:
MasterGuiLogic();
Q_PROPERTY(GuiLogic_SerialCom * serialCom READ serialCom CONSTANT)
GuiLogic_SerialCom* serialCom() const {return test;}
Q_INVOKABLE void generate_signal();
private:
GuiLogic_SerialCom * test;
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QDebug>
MasterGuiLogic::MasterGuiLogic()
{
this->test = new GuiLogic_SerialCom();
}
void MasterGuiLogic::generate_signal()
{
qDebug() << __FUNCTION__ << "Calling serialcom to gen signal";
this->test->connectToPort("88");
}
void GuiLogic_SerialCom::connectToPort(const QString &portName)
{
qDebug() << __FUNCTION__ << "got signal" << portName;
emit this->connectedToPortChanged(true);
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "test.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new MasterGuiLogic());
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import GrblCom 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea
{
anchors.fill: parent
onClicked:
{
Test.generate_signal();
}
}
Connections
{
target: Test.serialCom
onConnectedToPortChanged:
{
console.log("Got signal from SerialCom in QML. passed bool value is: " + connected);
}
}
}
Ok, I found the problem... The provided answers, while indeed helpful for other reasons, were not the correct solution. After going over the code by #Xplatforms, I couldn't figure out what the difference was between what I was doing and what he did.... until I saw this in my own code:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
There isn't supposed to be a colon(:) there...
Connections {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
Never try programming while sleepy...lol

Rate limit of callbacks when control is modified

Let's say I have a slider control and my user is sliding it back and forth really fast.
Is it possible to limit the rate at which QML calls the "new value available" C++ callback?
If you want to completely avoid the value being updated while the slider is dragged, you can use the updateValueWhileDragging property in Qt Quick Controls 1, and the live property in Qt Quick Controls 2.
In Qt Quick Controls 2, the slider controls have a valueAt() function which can be called to check the value at any time.
If you're writing your own slider in QML, you could limit the change signal emission using a Timer, for example:
property int value
readonly property int actualValue: // some calculation...
Timer {
running: slider.pressed
interval: 200
repeat: true
onTriggered: slider.value = slider.actualValue
}
Here's a generic C++-side solution that works with any QObject:
// https://github.com/KubaO/stackoverflown/tree/master/questions/qml-rate-limter-42284163
#include <QtCore>
class PropertyRateLimiter : public QObject {
Q_OBJECT
qint64 msecsPeriod{500};
const QByteArray property;
bool dirty{};
QVariant value;
QElapsedTimer time;
QBasicTimer timer;
QMetaMethod slot = metaObject()->method(metaObject()->indexOfSlot("onChange()"));
void signal() {
if (time.isValid()) time.restart(); else time.start();
if (dirty)
emit valueChanged(value, parent(), property);
else
timer.stop();
dirty = false;
}
Q_SLOT void onChange() {
dirty = true;
value = parent()->property(property);
auto elapsed = time.isValid() ? time.elapsed() : 0;
if (!time.isValid() || elapsed >= msecsPeriod)
signal();
else
if (!timer.isActive())
timer.start(msecsPeriod - elapsed, this);
}
void timerEvent(QTimerEvent *event) override {
if (timer.timerId() == event->timerId())
signal();
}
public:
PropertyRateLimiter(const char * propertyName, QObject * parent) :
QObject{parent}, property{propertyName}
{
auto mo = parent->metaObject();
auto property = mo->property(mo->indexOfProperty(this->property));
if (!property.hasNotifySignal())
return;
connect(parent, property.notifySignal(), this, slot);
}
void setPeriod(int period) { msecsPeriod = period; }
Q_SIGNAL void valueChanged(const QVariant &, QObject *, const QByteArray & name);
};
#include "main.moc"
And a test harness for it:
#include <QtQuick>
const char qmlData[] =
R"__end(
import QtQuick 2.6
import QtQuick.Controls 2.0
ApplicationWindow {
minimumWidth: 300
minimumHeight: 250
visible: true
Column {
anchors.fill: parent
Slider { objectName: "slider" }
Label { objectName: "label" }
}
}
)__end";
int main(int argc, char ** argv) {
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app{argc, argv};
QQmlApplicationEngine engine;
engine.loadData(QByteArray::fromRawData(qmlData, sizeof(qmlData)-1));
auto window = engine.rootObjects().first();
auto slider = window->findChild<QObject*>("slider");
auto label = window->findChild<QObject*>("label");
PropertyRateLimiter limiter("position", slider);
QObject::connect(&limiter, &PropertyRateLimiter::valueChanged, [&](const QVariant & val){
label->setProperty("text", val);
});
return app.exec();
}

Thread executed only once

I try to implement this: when app is started I need to create multiple threads that would use the same QDialog window to get messages from user. When thread is started, it asks user for input and if button OK pressed, it prints the message to console. I can't figure out why but I get dialog window only once and after that it prints my one message to console and application finishes.
Here's how I describe dialog window:
#include <QtWidgets>
class MyDialog : public QDialog
{
Q_OBJECT
public:
QWaitCondition* condition;
explicit MyDialog(QWidget *parent = 0);
signals:
void got_message(QString);
public slots:
void show_message_input();
void show_message();
private:
QLabel* message_label;
QVBoxLayout* vbox;
QHBoxLayout* hbox;
QLineEdit* message_input;
QDialogButtonBox* dialog_buttons;
};
MyDialog::MyDialog(QWidget *parent) : QDialog(parent)
{
setModal(true);
message_label = new QLabel("Message");
message_input = new QLineEdit();
dialog_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
hbox = new QHBoxLayout();
hbox->addWidget(message_label);
hbox->addWidget(message_input);
vbox = new QVBoxLayout();
vbox->addLayout(hbox);
vbox->addWidget(dialog_buttons);
setLayout(vbox);
connect(dialog_buttons, SIGNAL(accepted()), this, SLOT(accept()));
connect(dialog_buttons, SIGNAL(rejected()), this, SLOT(reject()));
condition = new QWaitCondition();
}
void MyDialog::show_message_input()
{
int result = this->exec();
if (result == QDialog::Accepted)
{
emit got_message(message_input->text());
condition->wakeAll();
}
}
Here's MyThread class:
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(int id, MyDialog* window, QObject *parent = 0);
signals:
void show_input();
public slots:
void print_message(QString);
private:
static QMutex mutex;
static QMutex mutex2;
MyDialog* window;
int id;
void run();
void get_captcha_value();
};
QMutex MyThread::mutex;
QMutex MyThread::mutex2;
MyThread::MyThread(int id, MyDialog* window, QObject *parent) :
QThread(parent)
{
this->id = id;
this->window = window;
connect(this, SIGNAL(show_input()), this->window, SLOT(show_message_input()));
}
void MyThread::get_captcha_value()
{
QMutexLocker lock(&mutex);
connect(this->window, SIGNAL(got_message(QString)), SLOT(print_message(QString)));
emit show_input();
mutex2.lock();
window->condition->wait(&mutex2);
mutex2.unlock();
}
void MyThread::run()
{
mutex.lock();
qDebug() << "Starting thread " << id;
mutex.unlock();
get_captcha_value();
mutex.lock();
qDebug() << "Finishing thread " << id;
mutex.unlock();
}
void MyThread::print_message(QString message)
{
qDebug() << message;
QObject::disconnect(this, SLOT(print_message(QString)));
}
And main function:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyDialog* window = new MyDialog();
QList<MyThread*> threads;
for(int i = 0; i < 5; i++)
{
MyThread* thread = new MyThread(i, window);
threads << thread;
thread->start();
}
return a.exec();
}
The first problem you have is that you're inheriting from QThread. Unless you're wanting to re-write how Qt handles threading, you're doing it wrong!.
What you need to do is have a class that inherits from QObject and move the instances to a new thread. The main problem from inheriting QThread is that it can cause confusion about thread affinity (which thread an object is running on).
Also, creating more threads than processor cores is just a waste of resources.
I suggest you read this article on how to use Qt threading and stop inheriting from QThread.
Finally, the use of QMutex is to protect multiple threads accessing the same data simultaneously. You should be able to remove all of them in the code you've shown. Emitting signals with data from one thread to be received by a slot on another thread is the preferred method in Qt.