Wait for QML-Dialog in C++ Backend
-
Hey there,
I am referring to that upper thread which did not have an usable answer for me. I need some kind of dialog-mechanism in my (backend-) code which is able to invoke some Dialog in QML and (the important part) receives the result from it.
So far I have a small test-application to invoke some dialog:
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView m_view; m_view.setSource(QUrl("qrc:///main.qml")); m_view.show(); auto object = qobject_cast<QObject*> (m_view.rootObject()); QVariant returnedValue; QVariant msg = "Hello from C++"; QMetaObject::invokeMethod(object, "showMessageBox", Qt::ConnectionType::DirectConnection, Q_RETURN_ARG(QVariant, returnedValue)); // I want to wait for the result qDebug() << returnedValue.toString(); return app.exec(); }
QML:
Item { id: window visible: true width: 640 height: 480 function showMessageBox() { return dialog.open() } Dialog { id: dialog title: "Title" modal: true width: 200 standardButtons: Dialog.Ok | Dialog.Cancel } }
Is it somehow possible to wait for that QML-invocation? I know I could rewrite my code and split it into several signal-handlers but that would be a very unpractical way for me..
Thanks!
-
I suggest to use signal/slots mechanism to make it clean interface. Also is your code not working ?
-
@kain In my point of view, the easiest solution is to use a QObject and work with signals/slots.
I would first create a simple QObject class, like this:class DialogInterface : public QOject { Q_OBJECT public: explicit DialogInterface (Object *parent = 0): QObject(parent) {} // You can add as parameter what ever you want to become from QML Q_INVOKABLE void dialogClosed(int select) {} signals: void requestDialog(); }
modifiy the main.cpp to add DialogInterface instance and shared it with QML:
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView m_view; m_view.setSource(QUrl("qrc:///main.qml")); m_view.show(); // create intance and share it with QML context DialogInterface dialogInterface; m_view.rootContext()->setContextProperty("dialogInterface", &dialogInterface); // request dialog to be show QTimer::singleShort(0, &dialogInterface, &DialogInterface::requestDialog); return app.exec(); }
And finaly the QML part:
Item { id: window visible: true width: 640 height: 480 Connections { target: dialogInterface onRequestDialog: { dialog.open() } } Dialog { id: dialog title: "Title" modal: true width: 200 standardButtons: Dialog.Ok | Dialog.Cancel onOk: { console.log("OK pressed") dialogInterface.dialogClosed(0); } onDiscard: { console.log("Cancel pressed") dialogInterface.dialogClosed(-1); } } }
-
Thank you, but that does not solve my problem.
I want a blocking mechanism when I invoke the dialog.
In my code I have several places where I need to raise some dialog (notificaion, warning, yes/no, etc). It is just not very feasible to split this code up.Imagine I got the following to (pseudo)code-snippets which run in parallel on different QThreads:
SnippetA:
doSomethingA1(); auto result = dialogService.showMessageBox("Do you want to save your progress?"); if (result == Dialog::accepted) save(); doSomethingA2();
SnippetB:
doSomethingB1(); auto result = dialogService.showMessageBox("Do you want to to delete everything, kill every process and shutdown?"); if (result == Dialog::accepted) destroyeverything(); else doSomethingB2();
This works fine if I have that blocking mechanism like above. But now If I change this to be asynchronous:
SnippetA:
classA::someMethod() { doSomethingA1(); emit dialogService.showMessageBox("Do you want to save your progress?"); } classA::slotHandlerOnAccept() { save(); doSomethingA2(); } classA::slotHandlerOnReject() { doSomethingA2(); }
SnippetB:
classB::someMethod() { doSomethingB1(); emit dialogService.showMessageBox("Do you want to to delete everything, kill every process and shutdown?"); } classB::slotHandlerOnAccept() { destroyeverything(); } classB::slotHandlerOnReject() { doSomethingB2(); }
I hope it is clear to see that this code will cause some trouble if both classes want to show a dialog at the same time. How would I deal with that?
Or would it be more suitiable to create a dialogService which sits in its own thread and which blocks via an local eventloop until the result of the dialog is emitted?
-
@kain Yes, I think I have understand what you want to achieve. It ist not so easy.
I seen one possibility which is not too difficult.
You create a kind of template class to register the dialog message and store the result.
Something like this:class DialogRequest : public QOject { Q_OBJECT Q_PROPERTY(QString message READ message CONSTANT) public: explicit DialogRequest(const QString& message, Object *parent = 0): QObject(parent), m_message(message) {} QString message() const { return m_message; } signals: void done(QObject* dialog, int result); private: QString m_message; } class DialogInterface : public QOject { Q_OBJECT public: explicit DialogInterface (Object *parent = 0): QObject(parent) {} DialogRequest * createMessage(const QString& message) { // add parent to avoid QML taking ownership of instance!!! return new DialogRequest(message, this); } void showDialog(DialogRequest * dlg) { emit requestDialog(dlg); } signals: void requestDialog(QObject* dialogObj); }
Create new QML Component called, for example MyDialog:
Dialog { id: dialog property var dialogInfo title: !!dialogInfo ? dialogInfo.message : "Title" modal: true width: 200 standardButtons: Dialog.Ok | Dialog.Cancel onOk: { console.log("OK pressed") dialogInfo .done(dialogInfo , 0); } onDiscard: { console.log("Cancel pressed") dialogInfo.done(dialogInfo , -1); } }
change QML as follow:
Item { id: window visible: true width: 640 height: 480 Connections { target: dialogInterface onRequestDialog: { var newDialog = Qt.createQmlObject("MyDialog.qml", window, "dynamicMessage") newDialog.dialogInfo = dialogObj newDialog.open() } } }
Then in C++ something like this
auto* newDialog = dialogInterface.createMessage("This is a title"); connect(newDialog, &DialogRequest::done, this, [&](QObject* dialog, int result){ dialog->deleteLater(); if(result == 0) { // do Ok stuff } else { // do error stuff } }); dialogInterface.requestDialog(newDialog);
This is just an example in which way it can be done, and must be fine tune/adapted.
Just to give you a starting impulseHope this will help you to solve your problem.