I have a timer in qml (view in StackView) which I try to start from C++ code, calling javascript function. But my timer is never triggered. What Am I doing wrong? My flow is a.qml -> b.qml (on Button clicked)
File b.qml :
Item {
function connectionConfirmed() {
myTimer.start()
console.log("started timer")
}
Timer {
interval: 1000; running: false; repeat: false
id: myTimer
onTriggered: {
console.log("timer triggered")
}
}
}
file main.qml:
ApplicationWindow {
id: root
visible: true
width: 320
height: 530
StackView {
id: stackView
initialItem: "qrc:/a.qml"
anchors.fill: parent
}
}
file a.qml
MouseArea{
anchors.fill: parent
onClicked: {
stackView.push("qrc:/b.qml")
}
}
C++ part:
connect(&myObjectInstance, &X::somethingHappend, this, [this](){
QQmlComponent component(&engine_, "qrc:/b.qml");
QObject *obj = component.create();
QVariant returnedValue;
QMetaObject::invokeMethod(obj, "connectionConfirmed",
Q_RETURN_ARG(QVariant, returnedValue));
delete obj;
});
Output is:
started timer
When I tried to set running: true timer is triggered, does it mean that I am not able to start the timer from JS function?
The problem is caused because you are assuming that the component created on the C++ side is the same that is created on the QML side. The .qml file is the source code, when you invoke it, they generate a different object each time.
Explanation of the behavior:
connect(&myObjectInstance, &X::somethingHappend, this, [this](){
QQmlComponent component(&engine_, "qrc:/b.qml");
QObject *obj = component.create();
QVariant returnedValue;
QMetaObject::invokeMethod(obj, "connectionConfirmed",
Q_RETURN_ARG(QVariant, returnedValue));
delete obj;
});
In this code you are first creating the component, and then you create the object, call the function written in js, that function has just been executed and print the message, and after that you delete it, so when you delete it, it will never fire. new Timer, for that reason it seemed that the one that printed the first message was the item created in QML, but it is not, the new item printed it.
Calling a C ++ QML function brings these problems because many times you do not access the object you want.
The recommendation indicated by Qt is, on the contrary, to make the connection on the QML side, for this you must export the C ++ object using setContextProperty().
In the following example using its implementation of QML, we create a class that has a signal called mysignal, it is triggered every 5 seconds for the purpose of testing. An object of that class is exported to QML and connected so that it invokes the js function from QML
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
class Helper: public QObject{
Q_OBJECT
public:
using QObject::QObject;
signals:
void mysignal();
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Helper helper;
engine.rootContext()->setContextProperty("helper", &helper);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&helper](){
emit helper.mysignal();
});
timer.start(5000);
return app.exec();
}
#include "main.moc"
b.qml
import QtQuick 2.0
Item {
id: it
function connectionConfirmed() {
myTimer.start()
console.log("started timer")
}
Timer {
interval: 1000; running: false; repeat: false
id: myTimer
onTriggered: {
console.log("timer triggered")
}
}
Connections{
target: helper
onMysignal: it.connectionConfirmed()
}
}
Finally, in a StackView, items are created and destroyed each time a page is changed.
Related
I have a C++ class that emits a signal. I want that signal to be delivered to QML.
I set the object as a context property of qml application engine root context.
My C++ class
// Sample.h
class Sample : public QObject
{
Q_OBJECT
public:
explicit Sample(QObject *parent = nullptr);
public slots:
void emitSomething();
signals:
void emitted();
public slots:
};
And the implementation
// Sample.cpp
Sample::Sample(QObject *parent) : QObject(parent)
{
}
void Sample::emitSomething()
{
emit emitted();
}
My main implementation. This is very similar to the code provided by qt creator.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Sample sample;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("obj", &sample);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer::singleShot(1000, &sample, &Sample::emitSomething);
return app.exec();
}
The qml implementation is
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Connections {
target: obj
onEmitted: function() {
console.log("received")
}
}
}
When I run the code, emitSomething() slot is called, but I don't see emitted() signal in qml.
I didn't have version 5.9, but I tried it with 5.10.1. In that case, the text did not get printed to the console. I fixed it by changing the syntax on the signal handler. (Just remove function().)
Connections {
target: obj
onEmitted: {
console.log("received")
}
}
I have some issues changing the text of a QML window in Qt. I have a C++ file that calls a thread and from there I'm trying to change the value of the text label. The thread is running correctly but the text value from the QML is not changing. Below is part of my code:
main.cpp:
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///template.qml")));
QQuickItem *label = engine.rootObjects().at(0)->findChild<QQuickItem*>("myLabel");
thread2 thread(label);
thread.start();
}
Thread.cpp:
thread2::thread2(QQuickItem *label) {
this->label = label;
}
void thread2::run() {
int test = 0;
char buffer[10];
int speed = 100;
while(1) {
speed++;
sprintf(buffer,"%d km/h",speed);
this->label->setProperty("text", QString(buffer));
QThread::msleep(1000);
qDebug()<<"tic: "<<buffer<<endl;
}
template.qml:
Window {
id: window
visible: true
width: 360
height: 360
Component {
id: fruitDelegate
Row {
spacing: 10
Text { text: name }
Text { text: '$' + cost }
}
}
Text {
width: 99
height: 19
text: qsTr("Speed: ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 0
anchors.centerIn: parent
objectName: "lab"
}
Text {
width: 38
height: 19
text: qsTr(" 0 ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 46
anchors.centerIn: parent
objectName: "myLabel"
}
}
Can anyone tell me why is not working? Where is my mistake?
Thanks!
You have 2 errors:
You should not update the GUI from another thread, the run method is executed in another thread so Qt does not guarantee that it works correctly.
Do not export an element from QML to C++ because it brings several problems since it is many times impossible to obtain the object through the objectname, another inconvenient is that the life cycle of the Item is determined by QML so at a given moment it could be eliminated so that label could point to an unreserved memory making its use, etc. Instead it exports the C++ object to QML.
Considering the above the solution is:
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent=nullptr);
~Thread() override;
Q_SLOT void stop();
Q_SIGNAL void textChanged(const QString & text);
protected:
void run() override;
};
#endif // THREAD_H
thread.cpp
#include "thread.h"
#include <QDebug>
Thread::Thread(QObject *parent):
QThread(parent)
{
}
Thread::~Thread()
{
}
void Thread::stop()
{
requestInterruption();
wait();
}
void Thread::run()
{
int speed = 100;
QString text;
while(!isInterruptionRequested()) {
speed++;
text = QString("%1 km/h").arg(speed);
Q_EMIT textChanged(text);
QThread::msleep(1000);
qDebug()<<"tic: "<< text;
}
}
main.cpp
#include "thread.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Thread thread;
QObject::connect(&app, &QGuiApplication::aboutToQuit, &thread, &Thread::stop);
thread.start();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("thread", &thread);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
// ...
Text {
id: txt
width: 38
height: 19
text: qsTr(" 0 ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 46
anchors.centerIn: parent
objectName: "myLabel"
}
Connections{
target: thread
onTextChanged: txt.text = text
}
// ...
For more information read:
https://doc.qt.io/qt-5/qtquick-bestpractices.html#interacting-with-qml-from-c
https://doc.qt.io/qt-5/thread-basics.html#gui-thread-and-worker-thread
You shouldn't modify the UI from another thread. Use signal/slot instead.
You should not create a child class from QThread, also (create a worker and move it in another thread).
Item {
id: rooItem
visible: true
anchors.fill: parent
signal change(string s);
onChange: foobar.text = s
Text {
id: foobar
text: "Empty"
}
}
class Worker: public QObject
{
Q_OBJECT
public slots:
void run()
{
while(1) {
QThread::msleep(1000);
emit changed(QDateTime::currentDateTime().toString());
}
}
signals:
void changed(QString);
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QThread* th = new QThread();
Worker* worker = new Worker();
worker->moveToThread(th);
QObject::connect(th, &QThread::started, worker, &Worker::run);
th->start();
QQuickView view(QStringLiteral("qrc:/Main.qml"));
QObject* o = view.rootObject();
QObject::connect(worker, SIGNAL(changed(QString)), o, SIGNAL(change(QString)));
view.showMaximized();
return app.exec();
}
You are using the wrong mechanism to update a qml property, take a look at QQmlProperty for the right way. You could also export a QObject instance into the qml engine and bind the labels text property to some property of that object. Always keep in mind that qml/qt quick are essentially hacks. There is a way to update the gui safely from a non-gui thread without using signals. instead you can use events to do the work.
So I could change the property of a certain QML object via C++ code, but I couldn't see the result on screen.
I have an item repeated 64 times, and I want a certain image to be displayed only for the 32nd item (from C++) so I used invokeMethod to access the object via C++ then I used setProperty to change the visibility, if I view it with qDebug the property "visible" did change, but I notice no difference on the screen I still cannot see the image, but if I change the visibility from qml, I can see it.
This is the C++ code:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.setSource(QUrl("qrc:///main.qml"));
view.show();
QQuickItem* child;
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///Board.qml")));
QObject *rootObject = engine.rootObjects().first();
QQuickItem *qmlObject = rootObject->findChild<QQuickItem*>("grid")->findChild<QQuickItem*>("repeter");
QMetaObject::invokeMethod(qmlObject,"itemAt",Qt::DirectConnection, Q_RETURN_ARG (QQuickItem*,child), Q_ARG(int,32));
child=child->findChild<QQuickItem*>("pleaseWork");
qDebug() << child->property("visible");
child->setProperty("visible","true");
qDebug() << child->property("visible");
return app.exec();
}
I used qDebug to verify the property changed
This is the QML code :
Item
{
id: root
width: 8*45
height: 8*45
Grid
{
id: grid
objectName: "grid"
rows: 8
Repeater
{
objectName: "repeter"
model: 64
Image
{
objectName: "test"
width: 45; height: 45
source: "images/dark_square.png"
Image
{
id: isit
objectName: "pleaseWork"
visible: false
source: "images/avail_dark.png"
}
}
}
}
}
QQuickView and QQmlApplicationEngine are alternative ways to load and show QML views. What you are loading into QQmlApplicationEngine has nothing to do with the visible output of QQuickView.
In order to get things running, you need to change the top element of the QML file from Item to Window and show it on screen:
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///Board.qml")));
// end of your code
QObject *rootObject = engine.rootObjects().first();
QQuickWindow *window = qobject_cast<QQuickWindow *>(rootObject);
if (!window) {
qDebug() << "Error: Your root item has to be a window.";
return -1;
}
window->show();
// continue with your code
QQuickItem *qmlObject = rootObject->findChild<QQuickItem*>("grid")->findChild<QQuickItem*>("repeter");
I just began learning about Qt programming and found myself struggling with the signals and slot mechanism while making a simple test application.
I have two QML files: main.qml and grocery.qml.
I have a header file that manages the signals and slots of the application:
applicationamanger.h
#ifndef APPLICATIONMANAGER_H
#define APPLICATIONMANAGER_H
#include <QObject>
#include <QDebug>
class ApplicationManager : public QObject
{
Q_OBJECT
public:
explicit ApplicationManager(QObject *parent = 0);
signals:
void newGroceryItem();
public slots:
void addGroceryItem();
};
#endif // APPLICATIONMANAGER_H
The user starts the application on main.qml and when pressing a button loads grocery.qml
On grocery.qml I have an image that can be clicked by the user and emits the signal newGroceryItem()
MouseArea {
id: okBtnClkd
anchors.fill: parent
Connections {
onClicked: {
newGroceryItem()
}
}
}
And the main application main.cpp connects the signals but says it can't find the signal newGroceryItem() as it doesn't belong to main.qml. So my question is, how can I establish the connection to the slot addGroceryItem() from the secondary qml file grocery.qml?
EDIT:
As requested here're the contents of main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QQmlContext>
#include "applicationmanager.h"
int main(int argc, char *argv[]){
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
ApplicationManager applicationManager;
QObject::connect(window, SIGNAL(newGroceryItem()), &applicationManager, SLOT(addGroceryItem()));
return app.exec();
}
And the contents of grocery.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
//Declaration of signals.
signal newGroceryItem()
width: 300
height: 400
visible: true
TextField {
id: product
x: 14
y: 111
width: 270
height: 22
}
Text {
id: text1
x: 14
y: 81
width: 124
height: 24
text: qsTr("Product")
font.pixelSize: 20
}
Image {
id: okBtn
x: 99
y: 290
width: 100
height: 100
source: "img/main/okBtn.png"
MouseArea {
id: okBtnClkd
anchors.fill: parent
Connections {
onClicked: {
newGroceryItem()
}
}
}
}
}
Any method of a QObject-derived type is accessible from QML code if it is:
a public method flagged with the Q_INVOKABLE macro
a method that is a public Qt SLOT
So there are two ways to access addGroceryItem(). The first one is to use Q_INVOKABLE macro as follows:
class ApplicationManager : public QObject
{
Q_OBJECT
public:
explicit ApplicationManager(QObject *parent = 0);
Q_INVOKABLE void addGroceryItem();
};
The second way is to use public slots as follows:
class ApplicationManager : public QObject
{
Q_OBJECT
public:
explicit ApplicationManager(QObject *parent = 0);
public slots:
void addGroceryItem();
};
If you are creating your class object in C++ then you should set the object as the context data for main.qml as follows:
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QQmlEngine engine;
ApplicationManager app;
engine.rootContext()->setContextProperty("applicationManager", &app);
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
component.create();
return app.exec();
}
Now you can access the ApplicationManager object created in main.cpp from main.qml as follows:
MouseArea {
id: okBtnClkd
anchors.fill: parent
onClicked: {
applicationManager.addGroceryItem();
}
}
For more information see here.
I'm trying to do a simple task as changing a property (text: ) of some QML object from C++ yet I'm failing miserably. Any help appreciated.
I'm not getting any errors, the window shows up, just the text property doesn't change as (at least I think) it should.
Is even anything I'm NOT doing wrong here?!!
What I was trying is this:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQuickItem>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QString>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
QObject *object = component.create();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QString thisString = "Dr. Perry Cox";
object->setProperty("text", thisString); //<--- tried instead of thisString putting "Dr. ..." but nope.
delete object;
return app.exec();
}
main.qml
import QtQuick 2.2
import QtQuick.Window 2.1
Window {
visible: true
width: 360
height: 360
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
id: whot
text: ""
anchors.centerIn: parent
font.pixelSize: 20
color: "green"
}
}
When you call QObject *object = component.create(); you get access to the root context, which is the Window component and its properties.
To get access to Text properties, you can create property alias like this:
Window {
property alias text: whot.text
...
Text {
id: whot
text: ""
...
}
}
That will give you access to whot's text property from within the Window's context.
There is another slightly more round-about way. Assign objectName property instead of id (or both if you still need id) to whot:
Text {
id: whot // <--- optional
objectName: "whot" // <--- required
text: ""
...
}
Now you can do this in code:
QObject *whot = object->findChild<QObject*>("whot");
if (whot)
whot->setProperty("text", thisString);
On a side note: I don't think you are supposed to delete the object until after calling app.exec(). Otherwise, it will ... well, be deleted. :)
#include <QQmlContext>
#include <qquickview.h>
#include <qdir.h>
QQmlApplicationEngine engine;
QString root = QCoreApplication::applicationDirPath();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow*>(topLevel);
window->setProperty("root", root);
for qml
ApplicationWindow {
property string root
onRootChanged: {
console.log("root : "+ root);
}
}
For QML properties you should use QQmlProperty instead:
QQmlProperty::write(whot, "text", thisString);