Why is my QML textArea not appending? - c++

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
}
}

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

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.

QQuickWidget send signal from c++ to slot in QML

I have a application and I want make a little animation for it.
I did a qml file and used QQuickWidget to open and show it in my display. Now a I want make iteration between c++ and QML. I want, for example, when a function in c++ is called, a ball move in my display. But I could not make a connection between c++ and qml.
Every help is welcome.
A little part of my code:
c++
QQuickWidget *quickWidget = new QQuickWidget;
quickWidget->setSource(QUrl("qrc:/QML/main.qml"));
auto rootObject = quickWidget->rootObject();
// Connect C++ signal to QML slot
connect(this, SIGNAL(cppSignal()), rootObject, SLOT(qmlSlot()));
emit cppSignal();
QML
Rectangle {
id: tela
visible: true
width: 715
height: 77
color: '#E8E8E8'
// NumberAnimation {
// running: true
// target: bolinha
// property: "x"
// duration: 1000
// to: 600
// }
function qmlSlot() {
bolinha.visible= enabled
animBolinha.start();
}
}
enter image description here
What I can do to solve it?
I am not sure if you can call a QML method from C++ code as you did.
The recommended way from QT documentation is:
All QML methods are exposed to the meta-object system. As the functions are exposed to meta-object system, you can use QMetaObject::invokeMethod(), to invoke the QML function.
Probably in your case, you should call as said below (not tested).
auto rootObject = quickWidget->rootObject();
QMetaObject::invokeMethod(rootObject, "qmlSlot");
Look documentation (search for Invoking QML Methods)
As said in documentation, you can use Q_ARG to pass the arguments and Q_RETURN_ARG for receiving return arguments.

Qt: c++ signal to qml connections

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

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!