C++ signals and accessing QtQuick items - c++

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!

Related

What is the best way to pass a custom class (inherits QObject) to QML?

Having used Qt for C++ applications for quite some time now I wanted to do my next project using QML.
I now have the following scenario:
red: QML files and QML engine
blue: C++ classes
Now I want to be able to call C++ functions from my QML files (green arrows).
Content.qml needs to read properties from WifiManager
LiveField.qml and GameField.qml need to show / hide the corresponding C++ views
I used C++ for the views because of some heavy 3D stuff which I'm not that familiar with in QML (I only used QML for the UI menu).
I'd rather not create the C++ classes from within my QML code using qmlRegisterType since I need to do some initializing in my C++ code.
What is the best way to solve my problem?
C++ objects are typically shared using QQmlContext::setContextProperty. You can find more information about QQmlContext here. This makes any object (or value) that you put in the context widely available.
Two words of caution though:
Use your context properties only in high-level components, and not in reusable ones, as this will create a direct dependency to these values
Be careful to load your GUI after you set all your context properties, to make sure they are accessible by you UI from the start.
C++ side
#include "wifimanager.h"
// That one is required
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
void main() {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
WifiManager wifi;
engine.rootContext().setContextProperty("wifiManager", &wifi);
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
You can then use wifiManager on the QML side, along with its slots, Q_PROPERTYies, and signals
Reading a Q_PROPERTY
As with regular QML, you can now bind and read your object's properties.
Read: var value = wifiManager.value
Bind: someProperty: wifiManager.value
Any QML binding will be re-evaluated automatically whenever the value changes, as long as you emit the associated NOTIFY signal. For example:
Q_PROPERTY(QString ssid READ ssid WRITE setSsid NOTIFY ssidChanged)
Text {
// Calls the READ getter of your Q_PROPERTY
// Will automatically update whenever the SSID changes
text: wifiManager.ssid
}
Writing a Q_PROPERTY
As easy as reading the value, you can write to it by doing wifiManager.ssid = xxx
Button {
text: "Reset SSID"
onClicked: {
// Calls the WRITE setter of your Q_PROPERTY
wifiManager.ssid = ""
}
}
Handling signals
Signals can be handled with the Connections object. As with any QML object, you have to prefix your signal's name with on and a capital letter. Which gives onWifiConnected: {} for signal void wifiConnected();
Connections {
target: wifiManager
// Handle `wifiConnected` signal
onWifiConnected: {
console.log("Connected!")
// If your `wifiConnected` signal has an argument named `ip`
// it will be available here under the same name
console.log("My IP is", ip)
}
}
Calling slots
Slots and Q_INVOKABLEs are accessible as any other functions in javascript. So you can call wifiManager.disconnect()
Button {
text: "disconnect"
onClicked: {
// Calls the `disconnect` slot or Q_INVOKABLE
wifiManager.disconnect()
}
}

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

Access to a QQmlApplicationEngine inside my custom class

I am writing a Media Player in Qt using QML, I am making a Qlist of songs available in a directory and I want to send this list as a model to QML file, but I don't know how to access an engine declared in main inside my custom class Player.
So, there's my main.cpp
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<Player>("io.qt.examples.player", 1, 0, "Player");
QQmlApplicationEngine engine;
Player player;
engine.rootContext()->setContextProperty("player", &player); // this works fine
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
Inside my Player class I want to set another property, and I have found this code to help:
QQmlContext *currentContext = QQmlEngine::contextForObject(this); // debugger says it's null
QQmlEngine *engine = currentContext->engine(); // Segmentation fault
I am getting a Segmentation fault error when debugger reaches this line, I have properly set a QStringList with names of files and want to do this:
engine->rootContext()->setContextProperty("listModel", QVariant::fromValue(files));
And I want to use this model there:
ListView {
id: listView
model: listModel
delegate: Rectangle {
Text {
text: modelData
}
}
}
But getting also a QML error: ReferenceError: listModel is not defined
I'm going to answer the question as best as I can without a complete example.
I am getting a Segmentation fault error when debugger reaches this line
The problem is likely that the type is not constructed by the engine. The docs say:
When the QQmlEngine instantiates a QObject, the context is set automatically.
So you probably need to set the context manually. Usually this is done by using the context from an object that you know was constructed by the engine:
QQmlEngine::setContextForObject(this, qmlContext(someQmlObject))
But getting also a QML error: ReferenceError: listModel is not defined
Again, I don't have access to a complete example so I can only guess, and my guess would be that you set the context property too late. Context properties should be set before the QML that uses them is loaded.
What I would suggest though, is that you don't set context properties, and instead create a proper model and register it as a type that can then be instantiated in QML.
For more information about the various approaches to C++ and QML integration, take a look at this.
Actually I have found the answer here, changing my approach and switching my QStringList model to a Q_PROPERTY

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.

Dynamically Loading QML from Memory Instead of a URL

Is it possible to dynamically load QML from memory instead of using a file? I've seen a lot of code that always needs a URL, like this:
QGuiApplication app(argc, argv);
QQmlEngine* engine = new QQmlEngine;
QQmlComponent component(engine, QUrl(QStringLiteral("qrc:/main.qml")));
// ignoring error checking (make sure component is ready, compiles w/o errors, etc)
QObject* object = component.create();
app.exec();
In the example above, main.qml will be loaded, and the magic of QML will ensure any types get resolved. In particular, Qt considers the name of the file to be the QML type, which is made available to any other QML file within the same directory. So if main.qml used the type Simple, it would look for the file Simple.qml and use that as the type definition (http://doc.qt.io/qt-5/qtqml-documents-definetypes.html).
Now I'm going to get dangerous: I want to use in-memory QML instead of using a file. This is how it might work (please ignore bad design, memory leaks, etc. for the moment):
QByteArray SimpleQml()
{
return QByteArray("import QtQuick 2.7; Text {") +
"text: \"Hello, World!\"}";
}
QByteArray TextExampleQml()
{
return QByteArray("import QtQuick 2.7; Simple{") +
"color: \"Red\"}";
}
QObject* createObjectFromMemory(QByteArray qmlData, QQmlEngine* engine, QQmlComponent* componentOut)
{
componentOut = new QQmlComponent(engine);
// note this line: load data from memory (no URL specified)
componentOut->setData(qmlData, QUrl());
if (componentOut->status() != QQmlComponent::Status::Ready)
{
qDebug() << "Component status is not ready";
foreach(QQmlError err, componentOut->errors())
{
qDebug() << "Description: " << err.description();
qDebug() << err.toString();
}
qDebug() << componentOut->errorString();
return nullptr;
}
else
{
return component.create();
}
}
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
QQmlComponent* component = nullptr;
// works great: shows a small window with the text "Hello, World!"
QObject* object = createObjectFromMemory(SimpleQml(), view.engine(), component);
// problem: Simple is not a type
// QObject* object = createObjectFromMemory(TextExampleQml(), view.engine(), component);
// setContent() is marked as an internal Qt method (but it's public)
// see source for qmlscene for why I'm using it here
view.setContent(QUrl(), component, object);
view.show();
app.exec();
// don't forget to clean up (if you're not doing a demo snippet of code)
//delete object;
//delete component;
}
So SimpleQml() simply is a Text object with "Hello, World!" as the text. When createObjectFromMemory() is called with SimpleQml() as the data source, a small window appears with the text "Hello, World!".
I'm using QQuickView::setContent(), which is a function that is marked as internal in the Qt source, but it's used in qmlscene, and I'm trying to achieve a similar effect here (https://code.woboq.org/qt5/qtdeclarative/tools/qmlscene/main.cpp.html#main).
Obviously, the problem is when I try to call createObjectFromMemory() with TextExampleQml() as the data source. The QML engine has no idea that Simple should be a type--how can I tell QML about this type? I thought that qmlRegisterType (http://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType) might be a good candidate, but I could not find a way to register a type that is coming from a chunk of QML sitting in memory. I'd like to say, "Hey, I have this chunk of QML code, and it's type is 'Simple'."
Let's assume I could easily predict all type dependencies and would be able to know the proper order to load QML memory chunks. Can I register the QML code returned by SimpleQml() as the type Simple (keeping it in memory, and not saving it to a file)? If anything, I'd like to understand how the QML engine does this under the hood.
I realize I'm breaking a golden rule: don't reach into QML from C++ (reach into C++ from QML instead). So if anyone has a JavaScript solution that's cleaner, I'm open to suggestions, but please remember I'm trying to load from memory, not from file(s).
Update:
As noted in the comments, there is a JavaScript function called createQmlObject() that creates a QML object from a string, which is extremely similar to what I'm doing in C++ above. However, this suffers the same problem--the type that createQmlObject creates is...well, it's not a type, it's an object. What I'd like is a way to register a string of in-memory QML as a QML type. So my question still stands.
One option would be to provide a custom QNetworkAccessManager to your QML engine and implement handling of a custom URL scheme via createRequest().
E.g. your implementation of createRequest() would check if the passed URL has your scheme, lets say `memory", if yes, it takes the rest of the URL to decide which function to call or which data to deliver.
For anything else it just calls the base implementation.
Your main QML file could then be loaded like this
QQuickView view;
view.setSource(QUrl("memory://main.qml"));
All URLs in QML are relative to the current file if not specified otherwise to a lookup for Simple should look for memory://Simple.qml