I'd like to use an ApplicationWindow as a main file and be able to switch to other QML files from C++ with QQuickView::setSource(const QUrl & url). Basically it would do this:
start-up => loads main.qml (ApplicationWindow) => click on help button => C++ loads help.qml file => etc.
int main(int argc, char *argv[])
{
QApplication app{argc, argv};
CustomQQuickView view;
view.setSource(QUrl{"qrc:/main.qml"});
view->show();
return app.exec();
}
main.qml
ApplicationWindow
{
visible: true
width: 640
height: 480
Loader
{
anchors.fill: parent
id: mainPageLoader
}
Button
{
text: "Help"
onClicked: { mainPageLoader.source = "help.qml"}
}
}
(I am wondering if the Loader here is really necessary here)
However QQuickView only supports loading of root objects that derive from QQuickItem. Therefore it doesn't work with ApplicationWindow.
I'm thinking about using QQmlApplicationEngine instead of QQuickView but the usage seems different, this class being only equipped with QQmlApplicationEngine::load(const QUrl & url)
What would be the best course of action for my purpose? Do I really need an ApplicationWindow in my main.qml file?
Use QQmlApplicationEngine as you suggest, and with the main.qml as you say, but set a context property from C++ with the content page URL, e.g. help.qml - then bind to this context property in the Loader's source property.
This is the normal way of controlling QML from C++ - expose context properties or singleton objects with properties, drive them from C++, and have QML bindings respond to changes.
Related
i am trying to set applicationwindow {} size for my android apk, so i wish to read the values from cpp file:
main.cpp:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QScreen *screen = QApplication::screens().at(0);
QVariant sz_width = screen->availableSize().width();
QVariant sz_height = screen->availableSize().height();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
then from qml file to read it (main.qml):
ApplicationWindow {
id: mainWindow
visible: true
width: sz_width
height: sz_height
}
this is in order to easly manipulate with all object sizes later on in qml, so basicaly for example i use font size by mainWindow*0.5 so i can have proper font size for every app resolution, but it only works if i realy set up the variables width and height...
Maybe this solution is a but "morbid", but however I would like to do it this way if you can help me with proper syntax...
Thank you
To quickly make C++ values visible in QML you can set them as a context property:
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("screenWidth", sz_width);
engine.rootContext()->setContextProperty("screenHeight", sz_height);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
After this the variables are available in QML by the quoted names ("screenWidth" and "screenHeight") (these could also match the C++ variable name if you prefer).
QSize type is also recognized by QML, so you could just set the size as one variable.
engine.rootContext()->setContextProperty("screenSize", screen->availableSize());
Also in this case the information you're looking for is already available in QML... check the Screen attached object and also the Qt.application.screens object/property for a list of available screens.
ADDED:
Since the linked documentation doesn't mention it directly, it should be noted that context property variables set in this way have no change notifier signals. Therefore they do not automatically update in QML, unlike other "bindable" properties. The only way to get QML to update the value automatically is to set the context property again (or create some signal which QML can connect to and cause it to re-read the value).
I can't find where exactly this is mentioned in the Qt docs, but the QQmlContext page provides a (subtle) clue:
The context properties are defined and updated by calling QQmlContext::setContextProperty().
The QML TreeView has an signal named: doubleClicked(QModelIndex)
ref: https://doc.qt.io/qt-5.10/qml-qtquick-controls-treeview.html#doubleClicked-signal
Its possible connect that existing signal in a C++ QObject::connect() ??
I tried this:
QQmlApplicationEngine engine;
QObject *myTreeMenu = engine.rootObjects().at(0)->findChild<QObject*>("myTreeMenu");
connect(myTreeMenu , SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotModelClicked(QModelIndex)));
But i receive this return error:
QObject::connect: No such signal TreeView_QMLTYPE_63_QML_68::doubleClicked(QModelIndex) in '...'
QObject::connect: (sender name: 'myTreeMenu ')
The documentation for Qt 5.11 explains why this is not the best way to work with C++ and QML. I would suggest you instead expose a slot or Q_INVOKABLE in your C++ object and call that in the onDoubleClicked handler:
Q_INVOKABLE void doStuff();
in QML:
onDoubleClicked: cppObject.doStuff()
The documentation has a "before and after" example demonstrating this; here's the bulk of it:
With this approach, references to objects are "pulled" from QML. The
problem with this is that the C++ logic layer depends on the QML
presentation layer. If we were to refactor the QML in such a way that
the objectName changes, or some other change breaks the ability for
the C++ to find the QML object, our workflow becomes much more
complicated and tedious.
Refactoring QML is a lot easier than refactoring C++, so in order to
make maintenance pain-free, we should strive to keep C++ types unaware
of QML as much as possible. This can be achieved by "pushing"
references to C++ types into QML:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The QML then calls the C++ slot directly:
import QtQuick 2.11
import QtQuick.Controls 2.4
Page {
Button {
text: qsTr("Restore default settings")
onClicked: backend.restoreDefaults()
}
}
With this approach, the C++ remains unchanged in the event that the
QML needs to be refactored in the future.
In the example above, we set a context property on the root context to
expose the C++ object to QML. This means that the property is
available to every component loaded by the engine. Context properties
are useful for objects that must be available as soon as the QML is
loaded and cannot be instantiated in QML.
Integrating QML and C++ demonstrates an alternative approach where QML
is made aware of a C++ type so that it can instantiate it itself.
I am trying to learn a bit of qt and qml and I want to make a small application which will monitor a local file for changes and change a Text component when changes happen. I based my code on this answer, but even though I am not getting any warning/error during compilation and running, connecting the fileChanged signal of the QFileSystemWatcher to the qml connections elements, does not work, i.e., the text does not change when watchedFile.txt is modified. How can I check whether the signal is received in the qml code?
Here is my code:
C++:
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QFileSystemWatcher watcher;
watcher.addPath(QStringLiteral("qrc:/watchedFile.txt"));
QQmlApplicationEngine* engine = new QQmlApplicationEngine;
engine->rootContext()->setContextProperty("cppWatcher", &watcher);
engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Text {
id: text
text:"TEXT"
}
Connections {
target: cppWatcher
onFileChanged: {
text.text = "CHANGED"
}
}
}
You should try a file that is on your file system. Files in qrc resources are embedded into the executable, they do not change. Not sure what exactly you expect to happen. Other than this, that is the declarative way you do connections to a CPP object.
As #dtech already noticed,
watcher.addPath(QStringLiteral("qrc:/watchedFile.txt"));
is returning false, because qrc:/ is not recognized by watcher as correct path. And, actually, this path doesn't exist in file system at all, because it's internal resource file embedded in executable.
If you will put the path to the file on the disk, your code works just fine.
Also you definitely should check return result here and do not allow proceed futher, if it returns false.
Something like following will work better here:
if (!watcher.addPath(QStringLiteral("C:/your_path/watchedFile.txt")))
return 1;
The signal fileChanged is emitted when the file path changes, and not its content.
https://doc.qt.io/qt-5/qfilesystemwatcher.html#fileChanged
In my application I want to create another window with QML UI from C++ code.
I know it's possible to create another window using QML Window type, but I need the same thing from C++ code.
So far I managed to load my additional qml file into QQmlComponent:
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadUrl(QUrl(QStringLiteral("qrc:/testqml.qml")));
if ( component.isReady() )
component.create();
else
qWarning() << component.errorString();
How do I display it in a separate window?
You can achieve that using a single QQmlEngine. Following your code, you could do something like this:
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadUrl(QUrl(QStringLiteral("qrc:/main.qml")));
if ( component.isReady() )
component.create();
else
qWarning() << component.errorString();
component.loadUrl(QUrl(QStringLiteral("qrc:/main2.qml")));
if ( component.isReady() )
component.create();
else
qWarning() << component.errorString();
I prefer QQmlApplicationEngine though. This class combines a QQmlEngine and QQmlComponent to provide a convenient way to load a single QML file. So you would have fewer lines of codes if you have the opportunity of using QQmlApplicationEngine.
Example:
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.load(QUrl(QStringLiteral("qrc:/main2.qml")));
return app.exec();
We could also use QQuickView. QQuickView only supports loading of root objects that derive from QQuickItem so in this case our qml files couldn't start with the QML types ApplicationWindow or Window like in the examples above. So in this case, our main could be something like this:
QGuiApplication app(argc, argv);
QQuickView view;
view.setSource(QUrl("qrc:/main.qml"));
view.show();
QQuickView view2;
view2.setSource(QUrl("qrc:/main2.qml"));
view2.show();
return app.exec();
You can try to create new QQmlEngine
For anyone curious, I ended up solving the problem with slightly different approach.
My root QML document now looks like this:
import QtQuick 2.4
Item {
MyMainWindow {
visible: true
}
MyAuxiliaryWindow {
visible: true
}
}
Where MainWindow is a QML component with root element ApplicationWindow and AuxiliaryWindow is a component with root element Window.
Works just fine and you don't have to worry about loading two separate QML files.
How can you properly extend the QML ApplicationWindow type? According to the documentation, ApplicationWindow instantiates a QQuickWindow. So I tried sub classing from QQuickWindow and exposing the type to QML as MyWindow. The problem is that MyWindow doesn't actually extend the QML type ApplicationWindow, so you don't get all the properties like menuBar and toolBar. How can I extend ApplicationWindow in C++ and expose it to QML? Here is what I'm currently doing:
class MyQuickWindow : public QQuickWindow
{
//...irrelevant additions
}
int main()
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyQuickWindow>("MyExtensions", 1, 0, "MyApplicationWindow");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
Here is the QML file:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import MyExtensions 1.0
MyApplicationWindow {
width: 800
height: 600
visible: true
menuBar: MenuBar { // ERROR: menuBar is not a property
Menu {
title: "File"
MenuItem { text: "New" }
MenuItem { text: "Open" }
}
}
}
Note that I need to have my own additions in C++ to the QQuickWindow for other reasons.
It seems like the job for qmlRegisterType. And it is a bit hard to say if you miss something with your C++ part but registering the type should help. That is for exposing the type itself and should enable the derived QQuickWindow functionality (derived from ApplicationWindow actually). But for what you are adding you need to deal with Q_PROPERTY and Q_INVOKABLE (which is for functions) mechanism. See the whole bunch of Q_* QObject macro.
And if that was not enough then there is an example for such inheritance.
Correction: the author is dealing with QML-made type but he can still try to mimic the type on his own. The path to ApplicationWindow.qml source code is: C:\Qt\5.3\Src\qtquickcontrols\src\controls where C:\Qt\5.3\ is the root for selected Qt version.I would attempt that and that is feasible unless we want to find out about explicit QML inheritance. That file can also be found at Qt source code repository.