How should your QML call a C++ method with result, when the operation is asynchronous?
-
I'm working on a QML dashboard that shows multiple remote PCs. The user can press buttons in the UI to send commands to those remote systems. I need to know for each of these commands whether they succeeded or not, and show a popup showing something like "Reboot of Server #3 was accepted". For some commands which require password input, I want to keep the dialog open until successful or other presses Cancel.
When I started out, I just thought I'd have "Q_INVOKABLE bool sendCommand(QString targetId, Command commandDetails)", and the QML would check the bool for success. But now I realize that doesn't work. I can't have my C++ context object block until there's a reply, that will freeze the UI if there's network issues.
Approaches I've thought of based on my limited experience with QML:
a) when QML invokes one of my context object's control methods, the invokabler becomes "uint sendCommand(...)". sendCommand returns immediately with a unique ID. The object will then have a generic signal "operationCompleted(QString id, Command commandDetails, bool successful), which the QML can match to the previously sent IDs.
b) Ghetto version of the above: don't bother mapping a request to a result directly. I make the C++ invokable without a return type (void), it returns immediately, then when I get a reply to any command I emit the generic signal "operationCompleted(Command commandDetails, bool successful)". The signal is not matched to an exact request from QML's perspective, but unless the requirements change this shouldn't come back to bite me in the ***.
How do you deal with this sort of thing in your apps?
-
I'm working on a QML dashboard that shows multiple remote PCs. The user can press buttons in the UI to send commands to those remote systems. I need to know for each of these commands whether they succeeded or not, and show a popup showing something like "Reboot of Server #3 was accepted". For some commands which require password input, I want to keep the dialog open until successful or other presses Cancel.
When I started out, I just thought I'd have "Q_INVOKABLE bool sendCommand(QString targetId, Command commandDetails)", and the QML would check the bool for success. But now I realize that doesn't work. I can't have my C++ context object block until there's a reply, that will freeze the UI if there's network issues.
Approaches I've thought of based on my limited experience with QML:
a) when QML invokes one of my context object's control methods, the invokabler becomes "uint sendCommand(...)". sendCommand returns immediately with a unique ID. The object will then have a generic signal "operationCompleted(QString id, Command commandDetails, bool successful), which the QML can match to the previously sent IDs.
b) Ghetto version of the above: don't bother mapping a request to a result directly. I make the C++ invokable without a return type (void), it returns immediately, then when I get a reply to any command I emit the generic signal "operationCompleted(Command commandDetails, bool successful)". The signal is not matched to an exact request from QML's perspective, but unless the requirements change this shouldn't come back to bite me in the ***.
How do you deal with this sort of thing in your apps?
@thierryhenry14 said in How should your QML call a C++ method with result, when the operation is asynchronous?:
a) when QML invokes one of my context object's control methods, the invokabler becomes "uint sendCommand(...)". sendCommand returns immediately with a unique ID. The object will then have a generic signal "operationCompleted(QString id, Command commandDetails, bool successful), which the QML can match to the previously sent IDs.
b) Ghetto version of the above: don't bother mapping a request to a result directly. I make the C++ invokable without a return type (void), it returns immediately, then when I get a reply to any command I emit the generic signal "operationCompleted(Command commandDetails, bool successful)". The signal is not matched to an exact request from QML's perspective, but unless the requirements change this shouldn't come back to bite me in the ***.
Both of these are valid solutions. Option (b) sounds simpler.
If this is a long-term project, I would avoid making any assumptions that requirements won't change. However, I think (b) can be scalable and adapted to new requirements.
If you want to explore (a) further, have a look at QNetworkAccessManager's design: Every request returns a QNetworkReply object, which can emit its own finished() or errorOccurred() signals.
-
I am working on an app that does this now.
I have multiple types of interactions with the network device. I have a state machine like design in qml that looks for pass/fail signals. Some signals are generic and I look at the state I am in to determine the next state. So if I fail a ping I show error message and try in a little later. If I am part through setup of the device and it fails I got back to ping state. The "states" part of Item are really nice for tracking interaction with a device.I have also made generic put and get routines that get specialized with a slot to call them for certain tasks. I pass reply routines into those to automatically assign the reply to the call.
void getRequest(const QByteArray& command, std::function<void(QNetworkReply *reply)> handler, int timeout=-1); void putRequest(const QByteArray& command, const QByteArray& data, std::function<void(QNetworkReply *reply)> handler, int timeout=-1);
The reply call look like this:
void handleSendReceiver(QNetworkReply* reply); void handleGetReceivers(QNetworkReply* reply);
A specialized call to do the request looks like this:
void WaterLinked::sendConfig(QByteArray data) { if(m_address.isValid()){ putRequest(wlcomPutConfig, data, std::bind(&WaterLinked::handleSendConfig, this, std::placeholders::_1)); } }
The reply has signals for generic calls and or specialized calls to determine pass fail.
The signals interact with my qml state machine. -
Add a property to the C++ context object indicating the state of the request. It sounds like this will need 2 to 4:
- idle - optional
- request in progress
- failed - optional
- succeeded / finished
This allows a newly initialized UI component to learn the state of a request that was previously sent, making the interface more flexible. Making the request, or at least its parameters available through a property is also worth considering.