Qt: c++ signal to qml connections - c++

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

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.

Why is my QML textArea not appending?

I am trying to write some Qt C++ code that interacts with QML objects. The goal is to have strings received on a TCP socket appended to a text log on the GUI. Each time a new string is received, I run the appendText() function. I have one implementation of this currently working that uses QWidgets and a .ui file. I need to have a QML implementation that is identical. My QWidget implementation uses a textBrowser and the append function such as the following. "theString" is changing as the program runs and each change is appended, filling up the text log.
//update the text log with data received on TCP socket
void MainWindow::appendText() {
ui->textBrowser->append(theString);
}
This gives me the desired result, appending each string to the text box as they come in. The output should look like the following.
Control connection successful.
Data connection successful.
Control Packet Receieved:
1
Control Packet Receieved:
2
Control Packet Receieved:
3
Control Packet Receieved:
4
Control Packet Receieved:
1
Control Packet Receieved:
2
Control Packet Receieved:
3
Control Packet Receieved:
4
However, when doing what I believe to be the same function with a QML object with the following code...
//update the text log with data received on TCP socket
void MainWindow::appendText() {
QMetaObject::invokeMethod(textbox, "append", Qt::DirectConnection, Q_ARG(QVariant, theString));
//QQmlProperty(textbox, "text").write(theString);
}
It only appends the first two strings, and no more beyond that. The output looks like this instead.
Control connection successful.
Data connection successful.
I have looked over the documentation for invoking QML methods in C++ extensively and still haven't had any luck. Any help is appreciated. Thanks for your time.
I'm unable to reproduce your problem.
Possible solution
It may be a solution to use import QtQuick.Controls 2.0.
In that case, I obtain the following error message:
QMetaObject::invokeMethod: No such method QQuickTextArea::append(QVariant)
Candidates are:
append(QString)
As suggested by the error message, you should use QString now instead of QVariant as parameter type:
QMetaObject::invokeMethod(textbox, "append", Qt::DirectConnection, Q_ARG(QString, theString));
Better alternative
As mentioned by Qt, you should avoid manipulating QML object from C++ (deep into the object tree):
Warning: While it is possible to use C++ to access and manipulate QML objects deep into the object tree, we recommend that you do not
take this approach outside of application testing and prototyping. One
strength of QML and C++ integration is the ability to implement the
QML user interface separately from the C++ logic and dataset backend,
and this strategy breaks if the C++ side reaches deep into the QML
components to manipulate them directly.
Therefore, it may be a better alternative to implement a signal in C++ which emit the newly received messages and connect to it from the QML side. This approach clearly separates user interface and programming logic.
Working example code
The following code appends "test" to the TextArea every second.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QTimer>
#include <QQuickItem>
QObject *textbox;
void onTimeout()
{
QMetaObject::invokeMethod(textbox, "append", Qt::DirectConnection, Q_ARG(QVariant, "test"));
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer t;
QObject::connect(&t, &QTimer::timeout, &onTimeout);
textbox = engine.rootObjects().first()->children().first();
t.start(1000);
return app.exec();
}
main.qml:
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 1.0
Window
{
visible: true
width: 600
height: 600
TextArea
{
id: textbox
anchors.fill: parent
}
}

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!

Connecting qml-signals to Qt

I'm trying to use a qml-grid view in my code. I'm trying to couple it with my C++ code.
I've dynamically created a list view model and passed across the qml file. It works fine.
However, I'm facing trouble when I want to connect a Qml signal to Qt/c++ code. I've handled mouseArea in my Qml-rectangle and emitting a signal from there.
I'm trying to connect to the signal as follows:
QDeclarativeView *pQMLContainer = NULL;
TempWidget *pTemp = new TempWidget();
pQMLContainer = new QDeclarativeView(pTemp);
pQMLContainer->setResizeMode(QDeclarativeView::SizeRootObjectToView);
pQMLContainer->rootContext()->setContextProperty("imgModel", createModel() );
pQMLContainer->setSource(QUrl("../Temp/qml/gridview-example.qml"));
QObject *rootObject = dynamic_cast<QObject*>pQMLContainer->rootObject();
QObject::connect(rootObject, SIGNAL(keyPressed()), pTemp, SLOT(onKeyPressed()));
When the connect statement runs, I get an error: cannot connect to "null" object.
On debugging, I found I could never get "rootObject" as a valid pointer.
Where am I going wrong?
Thanks
Can you try this ? (it is example code from Qt Docs)
QObject *item = pQMLContainer->rootObject();
QObject::connect(item, SIGNAL(keyPressed()),
pTemp, SLOT(onKeyPressed()));
The code is pretty much straight:
in .cpp file:
ui->declarativeView->setSource(QUrl("qrc:/Resources/main.qml"));
QGraphicsObject *obj = ui->declarativeView->rootObject();
connect ( obj, SIGNAL(clicked()), this, SLOT(itemClicked()));
and QML File:
import Qt 4.7
Rectangle {
width: 100
height: 100
id: rect
signal clicked
Text {
text: "Hello World"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
rect.clicked();
}
}
}
one more thing, check the location of your qml file, it should be accessible to the binary.
Perhaps you should use qobject_cast instead of dynamic_cast? See e.g. question
dynamic_cast returns NULL but it shouldn't
QGraphicsObject is a QObject so no cast should be required. If your compiler complains, try adding #include <QGraphicsObject>.
Just casting without the compiler knowing the classes is asking for trouble. (Especially as there is multiple inheritance involved.)
I could finally get this working. I'm not sure if this is the real solution to the problem, but finally this got it working:
I was setting the qml path as a relative path to my working folder. And yes the path was indeed correct, as I could see the qml and its contents. I just happened to change the qml path from relative to the working folder to relative to "qrc" as:
pQMLContainer->setSource(QUrl("qrc:/gridview-example.qml"));
instead of:
pQMLContainer->setSource(QUrl("../Temp/qml/gridview-example.qml"));
and it started working. I'm not sure if I had to add the qml to the qrc (I've just started using qml).
Thanks everyone for your support!
Mots