Create separate QML window from C++ code - c++

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.

Related

qml read variables from c++?

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().

It is possible connect a QML Object existing signal in C++ QObject::connect()?

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.

QT Program segfaults when trying to instantiate QWidget

I have a simple QT project that looks like the following:
#include <QtWidgets/qwidget.h>
TempClass::TempClass() {
QWidget* tempWidget = new QWidget();
}
Everything compiles fine, but when I try to run the program it segfaults on the line that instantiates a new QWidget. FYI I am using QT 5.7.1.
The issue here was that I was instantiating my application as a QGuiApplication vs. a QApplication. Once I changed the code to the following, my QWidget instantiates properly:
QApplication app(argc, argv);

Use QQuickView or QQmlApplicationEngine to switch between pages from ApplicationWindow

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.

C++ signals and accessing QtQuick items

I am trying to write an QtQuick program which works as an intelligent interface between user and a few CLI applications. I have implemented QtQuick + JavaScript application.
A QtQuick butten emits signal that is listened by C++ layer. So far everything works well.
However, in my C++ slot function I need to write to a certain Item in QtQuick application. This Item is an TextArea which serves as Log output of CLI applications. These CLI applications are run from the slot function in C++. I store their output into a variable, and I want to show output of this variable in this Log output TextArea.
I tried a lot of things, but I didn't find the right way to do that
I'd a similar problem.
This is how I solved it.
In C++ I created a class that handles the command with a QProcess (and I expose the class to QML), which attach the readyToRead signal to a C++ function in my exposed class, this function emits another signal showOutput with the output text.
With this information I just connect my new signal to a javascript function in qml:
cppExposed.showOutput.connect(jsFunction);
And in the javascript function I just append the text
function jsFunction(output) {
OutputTextArea.text += output;
}
To expose C++ properties to QML you can have a look at the documentation here: http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html
I think the easiest way for you is to create an object of your cpp class, and set as a context property in your main.cpp before load your main.qml:
Something like this:
QQmlApplicationEngine engine;
ProcessHandler procHandler;
engine.rootContext()->setContextProperty("procHandler", &procHandler);
Now you can access your object direct from QML, and you can connect signals
procHandler.showOutput.connect(jsFunction)
And in your C++ class don't forget to connect with the process ReadyToReady signal and emit your own signal with the data:
void readyToRead() {
emit showOutput(m_proc.readAllStandardOutput());
}
UPDATE:
Your property should be set before load the QML file by the engine:
AnalyzeSignal analyzeSignal;
engine.rootContext()->setContextProperty("analyzeSignal", &analyzeSignal);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
And I don't think you need that writeToLogOutput to be a property (and it has some syntax error btw), it's a signal method, right? So it's automatically available to QML as a signal, not a property.
DON'T create a new QQMLContext.
In this line what you are doing is creating a new QQMLContext
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("analyzeSignal", &analyzeSignal);
This won't work, as you are setting the property to the newly created context, not to the original root context.
My final solution of main function looks like this:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
AnalyzeSignal analyzeSignal;
QQmlContext *context = engine.rootContext();
context->setContextProperty("analyzeSignal", &analyzeSignal);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// Registering slot onto a signal
QObject *win = engine.rootObjects()[0];
QObject *item = win->findChild<QObject*>("myButton");
QObject::connect(item, SIGNAL(doAnalyzeSignal(QString,QString,QString,QString)), &analyzeSignal, SLOT(cppSlot(QString,QString,QString,QString)));
return app.exec();
}
And this works!