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

QML-Dialog with flexible method calls possible?



  • This idea seems intriguing for me, but I think it might be a naive question to ask. Please excuse, if this is the case.

    Is it possible to have a QML Dialog call C++ methods that can be changed at runtime?
    Simplified code idea:

    Let's say we have two sets of methods in C++:

    void Controller:numberOne();
    void Controller:numberTwo();
    
    void Controller:characterOne();
    void Controller:characterTwo();
    

    And we have a QML Dialog

    Dialog {
        property var firstMethod: Controller.characterOne()
        property var secondMethod: Controller.characterTwo();
    
        Button { 
            text = "first"
            onClicked: firstMethod
       }
        Button { 
            text = "second"
            onClicked: secondMethod
       }
    }
    

    I can open a QML Dialog and set its values with

    QQmlProperty::write(dialog, "someProperty", someValue);
    QMetaObject::invokeMethod(dialog, "open");
    

    I'd like to be able to change the methods that are used when buttons are clicked, like this:

    QQmlProperty::write(dialog, "firstMethod", this->numberOne());
    QQmlProperty::write(dialog, "secondMethod", this->numberTwo());
    

    This, surely, doesn't work "as is".
    But is there a way to achieve something similar?

    What I want is a single QML Dialog that can be reused for many totally different use cases.



  • Seems to have been a very naive question indeed. Sorry :-(
    So it's probably just not possible.
    Any hints are highly welcome.


  • Qt Champions 2018

    Nice usage of the Cunningham's Law.

    First I'd like to state that what you are doing is bad practice.
    You should not reach into QML from C++.
    Expose object, properties, signals, functions in C++ and use them in QML instead.
    Some relevant links:

    That being said you could pass a pointer to your object and the name of the function to be invoked to call it from QML: object[functionName]()



  • @GrecKo said in QML-Dialog with flexible method calls possible?:

    First I'd like to state that what you are doing is bad practice

    Thanks for that valuable assessment! Following this, I have re-thought my approach. I've got it working now by opening a QML file from C++ and feeding it with initial properties. I hope this is better.

    For anyone interested, here's how I do it. I have the possibility to use StandardButtons or user defined ones (initialized with a QStringList).

    userinteractionbox.qml

    import QtQuick 2.0
    import QtQuick.Controls 2.15
    
    Dialog {
        id: userInteractionBox
        property string text: ""
        property var userButtons: []
        property int initStandardButtons:    Dialog.NoButton
    
        contentItem: ScrollView {
            contentWidth: availableWidth
            contentHeight: 1000 
            height: availableHeight
            width: availableWidth
            TextArea {
                id: textArea
                text: userInteractionBox.text
                wrapMode: "WordWrap"
            }
        }
        signal standardButtonClicked(int nr)
        signal userButtonClicked(int nr)
        footer: DialogButtonBox {
    
            id: dbbx
            // STANDARD BUTTONS
            standardButtons: userInteractionBox.initStandardButtons
            function processButton(button) {
                switch (button) {
                    case standardButton(Dialog.Ok):                 userInteractionBox.standardButtonClicked(Dialog.Ok); break;
                    case standardButton(Dialog.Open):               userInteractionBox.standardButtonClicked(Dialog.Open); break;
                    case standardButton(Dialog.Save):               userInteractionBox.standardButtonClicked(Dialog.Save); break;
                    case standardButton(Dialog.Cancel):             userInteractionBox.standardButtonClicked(Dialog.Cancel); break;
                    case standardButton(Dialog.Close):              userInteractionBox.standardButtonClicked(Dialog.Close); break;
                    case standardButton(Dialog.Discard):            userInteractionBox.standardButtonClicked(Dialog.Discard); break;
                    case standardButton(Dialog.Apply):              userInteractionBox.standardButtonClicked(Dialog.Apply); break;
                    case standardButton(Dialog.Reset):              userInteractionBox.standardButtonClicked(Dialog.Reset); break;
                    case standardButton(Dialog.RestoreDefaults):    userInteractionBox.standardButtonClicked(Dialog.RestoreDefaults); break;
                    case standardButton(Dialog.Help):               userInteractionBox.standardButtonClicked(Dialog.Help); break;
                    case standardButton(Dialog.SaveAll):            userInteractionBox.standardButtonClicked(Dialog.SaveAll); break;
                    case standardButton(Dialog.Yes):                userInteractionBox.standardButtonClicked(Dialog.Yes); break;
                    case standardButton(Dialog.YesToAll):           userInteractionBox.standardButtonClicked(Dialog.YesToAll); break;
                    case standardButton(Dialog.No):                 userInteractionBox.standardButtonClicked(Dialog.No); break;
                    case standardButton(Dialog.NoToAll):            userInteractionBox.standardButtonClicked(Dialog.NoToAll); break;
                    case standardButton(Dialog.Abort):              userInteractionBox.standardButtonClicked(Dialog.Abort); break;
                    case standardButton(Dialog.Retry):              userInteractionBox.standardButtonClicked(Dialog.Retry); break;
                    case standardButton(Dialog.Ignore):             userInteractionBox.standardButtonClicked(Dialog.Ignore); break;
                }
            }
            // USER BUTTONS
            Repeater {
                model: userButtons.length
                Button {
                    visible: text !== ""
                    onClicked: userInteractionBox.userButtonClicked(index+1);
                    text: userButtons[index]
                }
            }
            onClicked: {
                processButton(button)
            }
        }
    }
    

    For interaction I have a userinteractionbox.h

    #ifndef USERINTERACTIONBOX_H
    #define USERINTERACTIONBOX_H
    
    #include <QObject>
    #include <QQuickItem>
    #include <QQmlApplicationEngine>
    #include <QGuiApplication>
    #include <QQmlContext>
    #include <QQuickWindow>
    #include <QQuickView>
    #include <QtQuick/qquickitem.h>
    #include <QDebug>
    
    
    class UserInteractionBox : public QObject
    {
        Q_OBJECT
    public slots:
        void buttonClickedSlot(int buttonNr);
    public:
        UserInteractionBox(QQuickView* view, QString title, QString text);
        ~UserInteractionBox() override {}
    
        enum StandardButton {
            NoButton           = 0x00000000,
            Ok                 = 0x00000400,
            Save               = 0x00000800,
            SaveAll            = 0x00001000,
            Open               = 0x00002000,
            Yes                = 0x00004000,
            YesToAll           = 0x00008000,
            No                 = 0x00010000,
            NoToAll            = 0x00020000,
            Abort              = 0x00040000,
            Retry              = 0x00080000,
            Ignore             = 0x00100000,
            Close              = 0x00200000,
            Cancel             = 0x00400000,
            Discard            = 0x00800000,
            Help               = 0x01000000,
            Apply              = 0x02000000,
            Reset              = 0x04000000,
            RestoreDefaults    = 0x08000000
        };
        Q_DECLARE_FLAGS(StandardButtons, StandardButton)
        Q_FLAG(StandardButton)
    
        enum ClosePolicyFlag {
            NoAutoClose = 0x00,
            CloseOnPressOutside = 0x01,
            CloseOnPressOutsideParent = 0x02,
            CloseOnReleaseOutside = 0x04,
            CloseOnReleaseOutsideParent = 0x08,
            CloseOnEscape = 0x10
        };
        Q_DECLARE_FLAGS(ClosePolicyFlags, ClosePolicyFlag)
        Q_FLAG(ClosePolicyFlag)
    
        void open();
        void close();
    
        QFlags<StandardButton> standardButtons() ;
        void setStandardButtons(QFlags<StandardButton> flags);
    
        int clickedButton() const;
    
        bool modal() const;
        void setModal(bool newModal);
    
        int width() const;
        void setWidth(int newWidth);
    
        int height() const;
        void setHeight(int newHeight);
    
        const ClosePolicyFlags &closePolicy() const;
        void setClosePolicy(const ClosePolicyFlags &newClosePolicy);
    
        const QStringList &userButtons() const;
        void setUserButtons(const QStringList &newUserButtons);
    
    signals:
        void standardButtonsChanged();
    
        void buttonClicked(int);
        void accepted();
        void applied();
        void discarded();
        void helpRequested();
        void rejected();
        void reset();
    
    
    private:
        QQmlApplicationEngine * m_engine = nullptr;
        QQuickWindow* m_mainWindow = nullptr;
        QQuickItem* m_item = nullptr;
        QQuickView* m_view = nullptr;
    
        QString m_title = QString();
        QString m_text = QString();
        QString m_addInfo = QString();
        QFlags<StandardButton> m_standardButtons = QFlags<StandardButton>(StandardButton::Ok|StandardButton::Cancel);
        QStringList m_userButtons;
        int m_clickedButton = StandardButton::NoButton;
        QObject* m_qmlObject = nullptr;
        bool m_modal = true;
        int m_width;
        int m_height;
        ClosePolicyFlags m_closePolicy = ClosePolicyFlag::NoAutoClose;
    };
    Q_DECLARE_METATYPE(UserInteractionBox::StandardButton)
    Q_DECLARE_METATYPE(QFlags<UserInteractionBox::StandardButton>)
    Q_DECLARE_OPERATORS_FOR_FLAGS(UserInteractionBox::StandardButtons)
    Q_DECLARE_METATYPE(UserInteractionBox::ClosePolicyFlag)
    Q_DECLARE_METATYPE(QFlags<UserInteractionBox::ClosePolicyFlag>)
    Q_DECLARE_OPERATORS_FOR_FLAGS(UserInteractionBox::ClosePolicyFlags)
    
    
    #endif // USERINTERACTIONBOX_H
    

    Implementation: userinteractionbox.cpp:

    #include "userinteractionbox.h"
    
    void UserInteractionBox::buttonClickedSlot(int buttonNr) {
        m_clickedButton = buttonNr;
    
        emit buttonClicked(buttonNr);
    
        switch (buttonNr) {
            case  Ok              : emit accepted();      close(); break;
            case  Open            : emit accepted();      close(); break;
            case  Save            : emit accepted();      close(); break;
            case  Cancel          : emit rejected();      close(); break;
            case  Close           : emit rejected();      close(); break;
            case  Discard         : emit discarded();     close(); break;
            case  Apply           : emit applied();                break;
            case  Reset           : emit reset();                  break;
            case  RestoreDefaults : emit reset();                  break;
            case  Help            : emit helpRequested();          break;
            case  SaveAll         : emit accepted();      close(); break;
            case  Yes             : emit accepted();      close(); break;
            case  YesToAll        : emit accepted();      close(); break;
            case  No              : emit rejected();      close(); break;
            case  NoToAll         : emit rejected();      close(); break;
            case  Abort           : emit rejected();      close(); break;
            case  Retry           : emit accepted();      close(); break;
            case  Ignore          : emit accepted();      close(); break;
            case  NoButton        :                                break;
        }
    }
    
    UserInteractionBox::UserInteractionBox(QQuickView *view, QString title, QString text) {
        m_view = view;
        Q_ASSERT(m_view);
        m_engine = qobject_cast<QQmlApplicationEngine *>(m_view->engine());
        Q_ASSERT(m_engine);
    
        m_mainWindow = qobject_cast<QQuickWindow*>(m_engine->rootObjects().value(0));
        Q_ASSERT(m_mainWindow);
    
        m_height = m_mainWindow->height()*3/5;
        m_width = m_mainWindow->width()*3/5;
        m_title = title;
        m_text = text;
    }
    
    void UserInteractionBox::open() {
        close(); // just in case
    
        QQmlComponent boxComp( m_engine, QUrl( "qrc:/UserInteractionBox.qml" ) );
        if (!boxComp.errorString().isEmpty()) {
            qDebug()<<boxComp.errorString();
        }
        Q_ASSERT(boxComp.errorString().isEmpty());
        Q_ASSERT(!boxComp.isNull()); if (boxComp.isNull()) return;
    
        QVariantMap initialProperties;
        initialProperties["parent"] = QVariant::fromValue(m_mainWindow->contentItem());
        initialProperties["title"] = m_title;
        initialProperties["text"] = m_text;
        initialProperties["modal"] = m_modal;
        initialProperties["width"] = m_width;
        initialProperties["height"] = m_height;
        initialProperties["initStandardButtons"] = QVariant::fromValue(int(m_standardButtons));
        initialProperties["userButtons"] = QVariant::fromValue(m_userButtons);
        initialProperties["closePolicy"] = QVariant::fromValue(int(m_closePolicy));
    
        QQmlContext *ctxt = m_view->rootContext();
        m_qmlObject = boxComp.createWithInitialProperties(initialProperties,ctxt);
        Q_ASSERT(m_qmlObject); if (!m_qmlObject) return;
    
        bool standardConnect =
                QObject::connect(m_qmlObject,SIGNAL(standardButtonClicked(int)), this, SLOT(buttonClickedSlot(int)),Qt::UniqueConnection);
        Q_ASSERT(standardConnect);
        bool userConnect =
                QObject::connect(m_qmlObject,SIGNAL(userButtonClicked(int)), this, SLOT(buttonClickedSlot(int)),Qt::UniqueConnection);
        Q_ASSERT(userConnect);
        QMetaObject::invokeMethod(m_qmlObject, "open");
    }
    QFlags<UserInteractionBox::StandardButton> UserInteractionBox::standardButtons() {
        return m_standardButtons;
    }
    
    void UserInteractionBox::setStandardButtons(QFlags<StandardButton> flags) {
        m_standardButtons = flags;
    }
    
    void UserInteractionBox::close()
    {
        if (m_qmlObject) {
            QMetaObject::invokeMethod(m_qmlObject, "close");
            m_qmlObject->deleteLater();
            m_qmlObject = nullptr;
        }
    }
    
    int UserInteractionBox::clickedButton() const
    {
        return m_clickedButton;
    }
    
    bool UserInteractionBox::modal() const
    {
        return m_modal;
    }
    
    void UserInteractionBox::setModal(bool newModal)
    {
        m_modal = newModal;
    }
    
    int UserInteractionBox::width() const
    {
        return m_width;
    }
    
    void UserInteractionBox::setWidth(int newWidth)
    {
        m_width = newWidth;
    }
    
    int UserInteractionBox::height() const
    {
        return m_height;
    }
    
    void UserInteractionBox::setHeight(int newHeight)
    {
        m_height = newHeight;
    }
    
    const UserInteractionBox::ClosePolicyFlags &UserInteractionBox::closePolicy() const
    {
        return m_closePolicy;
    }
    
    void UserInteractionBox::setClosePolicy(const ClosePolicyFlags &newClosePolicy)
    {
        m_closePolicy = newClosePolicy;
    }
    
    const QStringList &UserInteractionBox::userButtons() const
    {
        return m_userButtons;
    }
    
    void UserInteractionBox::setUserButtons(const QStringList &newUserButtons)
    {
        m_userButtons = newUserButtons;
    }
    
    

    Now I can call it like this:

    auto box = new UserInteractionBox(m_view, "Some Title", "Some information\n\nSoSome question");
            box->setStandardButtons(UserInteractionBox::StandardButton::YesToAll | UserInteractionBox::StandardButton::Cancel);
            bool connect = QObject::connect(box, SIGNAL(accepted()), this, SLOT(someSlot()), Qt::UniqueConnection);
            Q_ASSERT(connect);
            box->open();
    

    I hope this code is not all too bad, I am only a hobby programmer. Perhaps it may be useful for people having a similar problem.


Log in to reply