PDA

View Full Version : Dynamically loading and unloading 2 qml files from cpp



gowreesh
18th April 2020, 14:52
Hello,
I have requirement to launch 2 different Qml files "mainwindow.qml" and "basic.qml" ,wheras both are independent of each other.
Initially i need to launch qml window based on flag bSettingMainWindow, based on this i will be launching any one of the qml file.
After launching I need to switch between these 2 qml files anytime. It should be like loading and unloading of "mainwindow.qml" and "basic.qml", based on the user action.
Since because memory concern , i need load any of them at a time. and i dont want to play with visible true or false.
Below is the code, I have written for to do this operation.
whereas from this below code I can able to load any one of the qml file based on the flag bSettingMainWindow. and also after loading
i can able to switch to another qml file window.
Assume first I have launched in Mainwindow.qml then i clicked the mouse button over Rectangle of mainwindow.qml and moved to basic.qml and now if i click on the Rectangle of basic.qml it is not
switching to mainwindow.qml.
and vice versa.
Only one time i can able to switch b/w these 2 . I want to switch multiple times.
Below is the code , requesting to please provide your answers


//** windowLoader.hpp **//
class WindowLoader : public QObject{
Q_OBJECT
QQmlApplicationEngine loadQMlEngine;

public:
explicit WindowLoader(QObject * parent = 0);
void loadWindow();


public slots:
void loadMainWindow();
void loadBasicWindow();
void connectToMain(QObject *object = nullptr, const QUrl &url = QUrl(""));
void connectToBasic(QObject *object = nullptr, const QUrl &url = QUrl(""));

private:
};

//** windowLoader.cpp **//
WindowLoader::WindowLoader(QObject *parent) : QObject(parent) {

}

void WindowLoader::loadWindow() {
if(bSettingMainWindow){ //this will be from a internal flag, this check is only one time during launch
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToBasic(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/mainWindow.qml")));
} else {
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToMain(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/basic.qml")));
}
}

void WindowLoader::connectToBasic(QObject *object, const QUrl &url) {
if(object){
connect(object, SIGNAL(switchToBasicSignal()), this, SLOT(loadBasicWindow()));
}
}

void WindowLoader::connectToMain(QObject *object, const QUrl &url) {
if(object){
connect(object, SIGNAL(switchToMainSignal()), this, SLOT(loadMainWindow()));
}
}

void WindowLoader::loadBasicWindow() {
loadQMlEngine.rootObjects()[0]->deleteLater();
loadQMlEngine.destroyed();

loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/basic.qml")));
}

void WindowLoader::loadMainWindow() {
loadQMlEngine.rootObjects()[0]->deleteLater();
loadQMlEngine.destroyed();

loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/mainWindow.qml")));

}


//** main.cpp **//
int main( int argc, char *argv[] ) {
QApplication::setAttribute(Qt::AA_EnableHighDpiSca ling);
QApplication app(argc, argv);
WindowLoader root;
root.loadWindow();
return app.exec();
}


// ** mainWindow.qml **//
ApplicationWindow {
visible: true
width: 1200
height: 800
title: qsTr("MainWindow")

signal switchToBasicSignal()

Rectangle {
anchors.fill: parent
MouseArea{
anchors.fill: parent
onClicked: {
switchToBasicSignal()
}
}
}
}

//** basic.qml **//
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("basic")

signal switchToMainSignal()

Rectangle {
anchors.fill: parent
MouseArea{
anchors.fill: parent
onClicked: {
switchToMainSignal()
}
}
}
}

d_stranz
18th April 2020, 16:29
Since because memory concern , i need load any of them at a time. and i dont want to play with visible true or false.

What evidence do you have that keeping two widget instances in memory at the same time is a "memory concern"? Or are you just optimizing based on an untested assumption? The amount of memory each window allocates is probably negligible compared to the memory other parts of your program are using.

In any case, your WindowLoader:: loadWindow() method only connects to one of the two signals based on your startup flag, so once you switch to the other window, the signals it sends aren't connected to anything.

gowreesh
20th April 2020, 17:12
Thank you for pointing out the issue. Reconnecting the signal resolved the issue.
And yes I have tried on making visible true or false. It actually consuming almost 40mb more compare to loading and unloading like above.
Thank you very much....

gowreesh
13th May 2020, 04:36
I have observed one more issue in my code after implementation, actually switching b/w each window that is Basic and mainwindow is adding some extra size in memory .
for example assume basic window is launched by default , and then i will switch to MainWindow and then i will switch back to basic window like that...., please find the memory consumption in task manager.
Basic Window - 160 Mb
Main Window - 200 Mb
Basic Window - 170 Mb
Main Window - 210 Mb
Basic Window - 180 Mb
Main Window - 220 Mb
Basic Window - 190 Mb
Main Window - 230 Mb
It will keep on increase while switching until we close and reopen.
Whereas in CPP side both consumes almost same data. But UI is little more graphical representation in main window at qml side.
Where as above memory readings are taken by only pressing switching button in both the windows, and no cursor playing or keyboard playing on the window anywhere apart from switching button.
for each switching i have taken 5 seconds break until memory comes and settle down with some fixed value.

So I thought the memory consumption is increasing since i am not clearing some caches, so then I started consuming trimComponentCache() or clearComponentCache() in
both the functions loadBasicWindow() and loadMainWindow() I have added this cache clearing functions immediately after destroyed(), even after that result is same.

It should remain same b/w each switching. Suppose if the user performing any action in Basic or Advance window then we can say yes memory increases during some action . If we keep it idle it will come back to idle memory.
Requesting to help me in fixing this memory issue which I can see in task manager.

d_stranz
13th May 2020, 16:12
Task Manager is not a very accurate way of measuring memory use. It can be an indicator of a memory leak, but it can be misleading.

In Windows, freeing memory (using free() in C or operator delete() in C++) does not shrink the size of your program in memory. The freed memory is not released back to the OS for use by other programs, instead it is freed to the local heap used by your program so that it can be reused within your program. So the typical behavior for a leak-free C++ program if you watch Task Manager is that it will grow to a certain size and then stay there. It doesn't shrink even if it frees all of the memory allocated from the heap.

However, if you program allocates and frees varying size chunks of memory repeatedly, the heap can become fragmented to the point that there is not enough contiguous free memory available for the next allocation, so the program has to ask for more heap from Windows. So even though there is no leak, the program continues to grow.

QML is implemented in C++ on top of Qt's C++ libraries, as far as I know, so the same rules should apply. If you are using pure QML in your program, then the memory leak is either within the QML implementation itself or in the way you are using it.

I think that what is likely happening with your program is that each time you swap windows, QML must ask for more memory because it is unable to find enough contiguous free memory to create the new window and all of its widgets. If you are loading the window from QML source each time you switch, then the QML engine must go through the loading, parsing, and byte code compilation each time you switch, and that probably consumes some memory. The QML engine doesn't "remember" that you are using the same two files over and over again; each one is a brand new file as far as it is concerned.

These kinds of problems are why I advised against doing this in my first answer to your thread. :rolleyes:

gowreesh
1st June 2020, 05:01
Yes I agree playing with Visibility, resolved this issue of memory consumption as per your first answer.
and also thanks for this detailed explanation on memory consumption.

Once after changing my code to play with visibility I am facing one issue that is previously made visible false window getting settled backside(beyond some other opened application window) after a first launch, and it will not come on top if i make that window as visible true. For Every switch it will be settled backside in the screen , beyond some other application.
I tried changing it to "show" and "hide" by replacing visible true or false. and also I have tried using raise() and activatewindow() even after that i am facing the same issue.
Requesting to please help me out how to fix this issue.

d_stranz
1st June 2020, 16:31
If I understand your code correctly, your MainWindow and BaseWindow are both top-level windows (they have no "parent" - they are children of the desktop window). This makes them "sibling" windows, and so you may be able to use the QWidget::stackUnder() method. When you make BaseWindow visible, call MainWindow->stackUnder( BaseWindow ) and vice-versa. I don't know if it will work, but it is worth trying.

gowreesh
2nd June 2020, 05:01
Thank you for the reply.

I am using QQuickWindow and i am not using QWidget.
I have created a custom window i.e "class CustomWindow : public QQuickWindow {...}"
This CustomWindow only i have used in QML side. and sorry in my first thread i have mentioned ApplicationWindow
So only I am not able to access or pass QQuickWindow to QWidget::stackUnder(QWidget *w).
Requesting you to please let me know if there is any other solution.

d_stranz
2nd June 2020, 17:08
Sorry, I don't have any other ideas. I do not have much experience with QWindow and QML, so maybe someone else here can give some advice.

gowreesh
3rd June 2020, 15:17
its OK and thank you very much for replying.
If i don't find any solution i need to go back to that previous method of loading and unloading concept even if it consumes more memory since I don't have any other option.

Could anyone please help me out for the issue I have mentioned above.?????

d_stranz
3rd June 2020, 18:03
Could anyone please help me out for the issue I have mentioned above.?????

Maybe you should post your current code for loading the windows and switching between them. I am sure it is not the same as the code in your first post and might contain some clues about why it isn't working correctly.

gowreesh
4th June 2020, 14:10
Ya sure , I will put my latest code here.
I have designed this below code like if the user launches the application for the first time , then the application will load Mainwindow.qml or basic.qml based on the flag "bSettingMainWindow" .
Next time based on the switch button click from application will load another window (other than first one) and making the first launched qml window visible false.
Once both of them launched once, then 2 windows will play with visibility true or false on click of switch button or signal is emitted.
This below code is working perfectly fine except the window is not coming on top after making visible true. I have tried using raise() and activatewindow() even after that i am facing the same issue.
Please find the code below



//CustomWindow.h
class CustomWindow : public QQuickWindow {
//Custom QQuick window used in QML
}



//** windowLoader.hpp **//
class WindowLoader : public QObject{
Q_OBJECT
QQmlApplicationEngine loadQMlEngine;

public:
explicit WindowLoader(QObject * parent = 0);
void loadWindow();


public slots:
void loadMainWindow();
void loadBasicWindow();
void connectToMain(QObject *object = nullptr, const QUrl &url = QUrl(""));
void connectToBasic(QObject *object = nullptr, const QUrl &url = QUrl(""));

private:
};



//** windowLoader.cpp **//
WindowLoader::WindowLoader(QObject *parent) : QObject(parent) {

}

//This function will be called only one time during launch
void WindowLoader::loadWindow() {
if(bSettingMainWindow){ //this will be from a internal flag, this check is only one time during launch
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToBasic(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/mainWindow.qml")));
m_iMainWindowIndex = 0;
m_iBasicWindowIndex = 1;
} else {
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToMain(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/basic.qml")));
m_iMainWindowIndex = 1;
m_iBasicWindowIndex = 0;
}
}

void WindowLoader::connectToBasic(QObject *object, const QUrl &url) {
if(object){
connect(object, SIGNAL(switchToBasicSignal()), this, SLOT(loadBasicWindow()));
}
}

void WindowLoader::connectToMain(QObject *object, const QUrl &url) {
if(object){
connect(object, SIGNAL(switchToMainSignal()), this, SLOT(loadMainWindow()));
}
}

void WindowLoader::loadBasicWindow() {

if(loadQMlEngine.rootObjects().size() <= 2) {
disconnect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToBasic(QObject *, const QUrl &)));
}

if(loadQMlEngine.rootObjects().size() < 2) {
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToMain(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/basic.qml")));
loadQMlEngine.rootObjects()[m_iMainWindowIndex]->setProperty("visible",false);
} else {
//QMetaObject::invokeMethod(loadQMlEngine.rootObject s()[m_iBasicWindowIndex],"show");
//QMetaObject::invokeMethod(loadQMlEngine.rootObject s()[m_iMainWindowIndex],"hide");
loadQMlEngine.rootObjects()[m_iBasicWindowIndex]->setProperty("visible",true);
loadQMlEngine.rootObjects()[m_iMainWindowIndex]->setProperty("visible",false);
}
}

void WindowLoader::loadMainWindow() {

if(loadQMlEngine.rootObjects().size() <= 2) {
disconnect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToMain(QObject *, const QUrl &)));
}

if(loadQMlEngine.rootObjects().size() < 2) {
connect(&loadQMlEngine,SIGNAL(objectCreated(QObject *, const QUrl &)),this,SLOT(connectToBasic(QObject *, const QUrl &)),Qt::QueuedConnection);
loadQMlEngine.rootContext()->setContextProperty( "interface", m_interface );
loadQMlEngine.load(QUrl(QStringLiteral("qrc:/Qml/mainWindow.qml")));
loadQMlEngine.rootObjects()[m_iBasicWindowIndex]->setProperty("visible",false);
} else {
//QMetaObject::invokeMethod(loadQMlEngine.rootObject s()[m_iBasicWindowIndex],"hide");
//QMetaObject::invokeMethod(loadQMlEngine.rootObject s()[m_iMainWindowIndex],"show");
loadQMlEngine.rootObjects()[m_iBasicWindowIndex]->setProperty("visible",false);
loadQMlEngine.rootObjects()[m_iMainWindowIndex]->setProperty("visible",true);
}
}


//** main.cpp **//
int main( int argc, char *argv[] ) {
QApplication::setAttribute(Qt::AA_EnableHighDpiSca ling);
QApplication app(argc, argv);
qmlRegisterType<CustomWindow>("CNQml", 1, 0, "CustomWindow");
WindowLoader root;
root.loadWindow();
return app.exec();
}



// ** mainWindow.qml **//
CustomWindow {
visible: true
width: 1200
height: 800
title: qsTr("MainWindow")

signal switchToBasicSignal()

Rectangle {
anchors.fill: parent
MouseArea{
anchors.fill: parent
onClicked: {
switchToBasicSignal()
}
}
}
}

//** basic.qml **//
CustomWindow {
visible: true
width: 640
height: 480
title: qsTr("basicwindow")

signal switchToMainSignal()

Rectangle {
anchors.fill: parent
MouseArea{
anchors.fill: parent
onClicked: {
switchToMainSignal()
}
}
}
}