How should errors in C++ slots be communicated to QML?
-
Let's say you have a QML app which invokes slots on a C++ object. Like for example:
typedef int JobID; public slots: JobID createAndBeginJob(jobarg1, jobarg2); void cancelOngoingJob(JobID);
QML calls createAndBeginJob to start a job, and can also call cancelOngoingJob.
Button { id: createJobButton text: "try me" onClicked: { var jobid = jobCreator.createAndBeginJob("called from QML", 123); joblist.append(jobid); } }
If the QML provides invalid arguments to createAndBeginJob, how am I supposed to communicate this to the QML, so it can show a message like "jobarg2 is invalid because blah blah". The options I can see are:
- Return a "magic value" like -1 for the JobID. Gets messy quick, and still doesn't communicate the actual reason for failure to QML. I'd probably need a generic "errorHappened(QString errmsg)" signal in my objects that QML connects to and shows popups. It totally decouples the call from the error which feels wrong.
- Replace every slot's return value with a "CallResult" container with the members "bool successful; QString errrmsg; RealReturnValue returnvalue;" and have QML check successful + errmsg first. Very boilerplatey.
- Throw an exception from the slot. Qt says exceptions are not supported in slots, and try/catch doesn't seem to work from QML so it just crashes the application.
-
Hi and welcome to devnet,
Another option more in line with what Qt uses: have an errorOccured signal. Then depending on your design you can pass an enum as parameter with the corresponding error code, or have an
error
method that returns the error code or even both. -
@thierryhenry14 said in How should errors in C++ slots be communicated to QML?:
- Return a "magic value" like -1 for the JobID. Gets messy quick, and still doesn't communicate the actual reason for failure to QML. I'd probably need a generic "errorHappened(QString errmsg)" signal in my objects that QML connects to and shows popups. It totally decouples the call from the error which feels wrong.
You could use a "magic value" to indicate that the job failed to start, coupled with a separate function that you can call to retrieve the error:
var jobid = jobCreator.createAndBeginJob("called from QML", 123); if (jobid >= 0) joblist.append(jobid); else console.log(jobCreator.lastError())
- Replace every slot's return value with a "CallResult" container with the members "bool successful; QString errrmsg; RealReturnValue returnvalue;" and have QML check successful + errmsg first. Very boilerplatey.
This is actually quite a common technique in RESTful APIs, where the return value is a complex JSON document that contains both diagnostic information and "actual" data.
{ "success": false, "errorMessage": "Server busy", "jobID": -1 }
Granted, this is less common in C++ APIs.
Let's say you have a QML app which invokes slots on a C++ object.
FYI: Slots are functions that are designed to be connected to signals, and they usually return
void
because there is no reliable way to get the slot's return value when emitting a signal.Your example shows an invokable function. You can declare these using the
Q_INVOKABLE
macro: https://doc.qt.io/qt-5/qobject.html#Q_INVOKABLEAll slots are invokable functions, but not all invokable functions are slots.
-
@thierryhenry14
You will have most probably to stick with manual solution suggested by @SGaist. You see, Qt simply doesn't have some common error handling strategy. There are default values, magic values, status methods, and no common ground for them. Same goes with QML, even worse.With QWidgets, we could override
QCoreApplication::notify
, catch our exceptions there and do something reasonable. And that strategy was rather effective, since each event usually constituted single operation which could succeed as a whole.With QML, we have neither
notify
(since QML runtime isn't exception-neutral) nor anything similar to override. Code which invokes native slot from QML expression is buried deep in implementation details. C++ API for creating JS exceptions is severely limited - you can obtain engine pointer only in a limited set of situations (namely from objects owned by QML) - or have to store it manually in some global variable.Lastly, the only way to do something to unhandled exceptions is
QQmlEngine::warnings
signal. But due to info loss when exception is converted toQQmlError
, and due to nature of that type, the capabilities we have here are also limited.@SGaist
Well yes, you're right that it seems to be the only way in vanilla Qt. Though it really saddens me that such a magnificent framework with such a long and prominent history has one of the worst error handling stories I've seen so far. It means that when one develops application, he must invent his own way of propagating and handling errors since Qt provides none. -
Thanks for explaining this.