Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Wait for QML-Dialog in C++ Backend



  • Re: Wait for dialog response

    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!


  • Qt Champions 2017

    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 impulse

    Hope this will help you to solve your problem.


Log in to reply