Strange behaviour when open QML Popup Window from C++ code.
-
Hi everyone !
I'm implement M-V-VM Pattern and have this Strange BehaviourFirst, I open a main window like normal. then set the Context Object like this:
//Set Data Context to the Root Context QObject* mainViewModel = new MainWindowViewModel(window); QQmlContext* ctx = engine.rootContext(); ctx->setContextObject(mainViewModel);
Then I'm try to open Popup in MainWindowViewModel like this:
QQmlApplicationEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/FrmAuthenticate.qml"))); QObject *childItem = qobject_cast<QObject*>(component.create()); QQuickWindow *dialog = qobject_cast<QQuickWindow *>(childItem); QQmlContext* ctx = engine.contextForObject(dialog); FrmAuthenticateViewModel *viewModel = new FrmAuthenticateViewModel(dialog); ctx->setContextObject(viewModel); childItem->setParent(this->parent());
in Popup I have a WebView and it print out - console.log() - each time it navigate to new Url.
First strange thing:
- If open the popup by create a component from QML, I got the log data.
But when open from ViewModel, No log data print to Application Output (Can't debug in QML too)
Second strange thing:
- In the code to open popup above, I cannot set the contextObject for the Popup.
It show a warning "Cannot set context object for internal context" at runtime and QML cannot call to ViewModel's functions
Third strange thing:
- If open the popup by code in ViewModel without set the contextObject. Webview load & display the default page.
When trying to set contextObject. Webview cannot load & display the default page.
Anyone have any ideal why this happen ?!
Thanks & Best Regards
DongLD - If open the popup by create a component from QML, I got the log data.
-
I'm try to build simple sample. Don't know why, but it work (mostly)
- I can set context object to component
- I can get a property from context by calling View Model's binding function
- But I can't "connect" the signal from Qml to C++ functions like main.qml call to showLoginDialog() function
- It look like all signals in Qml wasn't fired. (No focus highlight, button onClicked not fired...
Here is the code:
main.qml
import QtQuick 2.3 import QtQuick.Controls 1.2 ApplicationWindow { id : mainWindow visible: true width: 640 height: 480 title: qsTr("Hello World") menuBar: MenuBar { Menu { title: qsTr("File") MenuItem { text: qsTr("&Open") onTriggered: { //Show popup from ViewModel (C++) showLoginDialog(); //Show popup from QML // var component = Qt.createComponent("qrc:/login.qml"); // if (component.status == Component.Ready) // { // component.createObject(mainWindow); // } } } MenuItem { text: qsTr("Exit") onTriggered: Qt.quit(); } } } }
main.cpp
int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); QObject *topLevel = engine.rootObjects().at(0); QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel); //Set Data Context to the Root Context QObject* mainViewModel = new MainWindowViewModel(window); QQmlContext* ctx = engine.rootContext(); ctx->setContextObject(mainViewModel); }
mainwindowviewmodel.cpp
#include "mainwindowviewmodel.h" MainWindowViewModel::MainWindowViewModel(QObject *parent) : QObject(parent) { } void MainWindowViewModel::showLoginDialog() { qDebug() << "Show Login Form"; LoginViewModel *viewModel = new LoginViewModel(); viewModel->setProperty("sampleText", "https://google.com"); QQmlApplicationEngine engine; QQmlContext* ctx = new QQmlContext(engine.rootContext()); ctx->setContextObject(viewModel); QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/login.qml"))); QObject *childItem = qobject_cast<QObject*>(component.create(ctx)); //QQuickWindow *dialog = qobject_cast<QQuickWindow *>(childItem); //FrmAuthenticateViewModel *viewModel = new FrmAuthenticateViewModel(dialog); childItem->setParent(this->parent()); }
login.qml
import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Layouts 1.1 import QtWebKit 3.0 //import QtWebView 1.0 import QtTest 1.0 ApplicationWindow { id: loginDialog title: qsTr("Hello World") width: 480 height: 550 visible: true TextField { id: txtUrl height: 30 text: binding("sampleText"); anchors.right: parent.right anchors.rightMargin: 100 anchors.left: parent.left anchors.leftMargin: 0 anchors.top: parent.top anchors.topMargin: 0 } Button { id : btn1 width: 100 height: 30 text: "Go" anchors.top: parent.top anchors.topMargin: 0 anchors.right: parent.right anchors.rightMargin: 0 onClicked: { text : binding("sampleText"); } } }
loginviewmodel.cpp
#include "loginviewmodel.h" LoginViewModel::LoginViewModel(QObject *parent) : QObject(parent) { } QVariant LoginViewModel::binding(const QString &propPath) const { return this->property(qPrintable(propPath)); } QVariant LoginViewModel::getAuthenCode(const QString &message) const { qDebug() << message; }
PS: "Tiếng tôi vang rừng núi... sao không ai trả lời"
-
I'm try to do everything in main.cpp like this... and it work fine.
int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); QObject *topLevel = engine.rootObjects().at(0); QQuickWindow *window = qobject_cast<QQuickWindow*>(topLevel); LoginViewModel *viewModel = new LoginViewModel(); viewModel->setProperty("url", "<AuthenticationUrl>"); QQmlContext *ctx = new QQmlContext(engine.rootContext()); ctx->setContextObject(viewModel); QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/login.qml"))); QObject *childItem = component.create(ctx); childItem->setParent(window); MainWindowViewModel *mainViewModel = new MainWindowViewModel(window); QQmlContext *mainContext = engine.rootContext(); mainContext->setContextObject(mainViewModel); return app.exec(); }
After investigation, I found out the root cause is :
QQmlApplicationEngine engine is not singleton. it completely different instance.So, I try to keep track the engine created from the main.cpp
but QQmlApplicationEngine is Q_DISABLE_COPY
Then, I create a class (ApplicationContext) with static RootContext object to store the mainContext.Then any time I need the "Root Engine", I get it from RootContext like this:
QQmlContext* rootContext = ApplicationContext::RootContext; ... QQmlComponent component(rootContext->engine(), QUrl(QStringLiteral("qrc:/login.qml")));