QML-Dialog with flexible method calls possible?
-
wrote on 12 Jun 2021, 21:22 last edited by SeDi 6 Dec 2021, 21:48
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.
-
wrote on 17 Jun 2021, 20:52 last edited by
Seems to have been a very naive question indeed. Sorry :-(
So it's probably just not possible.
Any hints are highly welcome. -
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:- https://doc.qt.io/qt-5/qtquick-bestpractices.html#interacting-with-qml-from-c
- http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html#interacting-with-qml-objects-from-c
- https://youtu.be/vzs5VPTf4QQ?t=23m20s
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]()
-
wrote on 26 Jul 2021, 13:08 last edited by SeDi
@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.