QQuickWidget send signal from c++ to slot in QML - c++

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.

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

Accessing Qt / QML objects with C++

I'm working on a C++ Qt project that will eventually communicate with the serial port. One part of this is accessing QML objects in the C++ portion. I have code that can set properties of the QML, but accessing those features that are methods now has me stumped. View the following code:
object = view.rootObject();
rect = object->findChild<QObject *>("box");
rect->setProperty("color", "red"); // Verifies the object tree is accessible
viewer = object->findChild<QObject *>("viewer"); // Access the viewer text box
viewer->append("dummy text"); // OOPS! This doesn't compile!!!
Now, the type as a method setProperty(..), but how do you access methods of an object. "viewer" is a TextArea and I want to first do a selectAll(), then a cut() to clear the box.
The question here is how is this coded? Thanks all.
Of course it would not compile, QObject doesn't have an append() method.
If it is a C++ function, you will have to qobject_cast to the appropriate type that has it. This however is not always readily available for many of the stock QML types that are implemented in C++, and as C++ types they are not part of the public API and not generally intended for direct use by an end user.
If it is a JS function, you will have to use QMetaObject::invokeMethod. That will also work for C++ functions for which meta data has been generated. Which is also how setProperty() works, whereas setColor() would not work with a QObject* much like append() doesn't.
Last but not least, there is absolutely no good reason for you to be doing those kinds of things from C++. Using QML objects from C++ is poor design and an anti-pattern. You will only develop bad habits trying to do that. Such interactions must be limited to a clearly defined interface using signals, slots and properties. Generally speaking, it is OK for QML to reach into C++, because that only happens through an exposed interface, but the opposite way, even if possible, should not utilized.
Think of it like this - a car uses the engine, the engine doesn't use the car. And the engine control is interfaced through the starter key and the gas pedal, it is not used directly. The C++ stuff should be reserved to the application engine - the high performance or efficiency core logic, or the back-end, whereas the QML part is for the GUI/front-end.
The author's QML part may expose alias property to operate with desired text field content:
import QtQuick 2.0
import QtQuick.Controls 1.2
Item {
property alias viewerText: viewer.text // added
width: 350
height: 450
TextArea {
id: viewer
x: 8
y: 8
width: 223
height: 415
text: "text"
font.pixelSize: 12
objectName: "viewer"
}
Button {
id: open
x: 251
y: 8
text: "Open"
}
}
And then the author's C++ part can easily do:
auto* object = view.rootObject();
viewer = object->findChild<QObject *>("viewer");
viewer->setProperty("viewerText", "dummy text"); // using the new property added
Using the posted answer here using the invoke method, here's the solution that works:
// C++ Code to call function reset()
QMetaObject::invokeMethod(object, "reset");
// QML code to select all text the delete it
function reset() {
viewer.selectAll()
viewer.cut()
}

How to get a valid instance of a QQuickItem on C++ side

Alright. I have searched a lot but haven't got a good solution yet. I am new to Qt. I have a class which is a QQuickItem like so,
class MyQuickItemClass : public QQuickItem
{
Q_OBJECT
SetInfo(SomeCppClass object)
};
I do a qmlRegisterType in my main.cpp to register it on the qml side like this,
qmlRegisterType< MyQuickItemClass >("MyQuickItemClass", 1, 0, "MyQuickItemClass");
All fine till here. But -> I want to set an object instance & some properties in MyQuickItemClass which some C++ logic in it as well & then pass the MyQuickItemClass object to qml. Or, get a valid instance of MyQuickItemClass from Qml. How can I get a vlid instance MyQuickItemClass object instance from QML on C++ side in main.cpp ?
I tried doing the following learning from the link here. But this technique creates two separate objects of MyQuickItemClass. One from QML, & one from c++ side. Hence does not work for me.
Following is how I am trying to do this after lot of searching.
int main(int argc, char *argv[])
{
qmlRegisterType< MyQuickItemClass >("MyQuickItemClass", 1, 0, "MyQuickItemClass");
QQmlApplicationEngine engine;
SomeCppClass someCppClassObject;
someCppClassObject.updateSomething();
MyQuickItemClass myquickItemObject;
myquickItemObject.SetInfo(someCppClassObject);
engine.rootContext()->setContextProperty("myquickItemObject", &myquickItemObject);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
return app.exec();
}
But, doing the above gets the constructor of MyQuickItemClass called twice. Once from cpp side when I created an object, and once from qml side. Verified this by placing a breakpoint in the constructor of MyQuickItemClass as well. As a result, someCppClassObject that I had set is null inside MyQuickItemClass when program runs. Because qml has made the final call to MyQuickItemClass to instantiate, thusly ignoring the MyQuickItemClass object that I created in main.cpp.
Here is my qml code for MyQuickItemClass:
import QtQuick 2.5
import MyQuickItemClass 1.0
ParentContainerItem {
id: parentItem
color: "black"
MyQuickItemClass {
id: myQuickItemID
visible: true
objectName: "myQuickItem"
property bool someProperty1: false
property bool someProperty2: true
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
}
//Other qml components
}
And this is the C++ class whose object needs to be set into MyQuickItemClass.
SomeCppClass {
//Pure C++ class. No Qt
}
Please note that I need to keep MyQuickItemClass derived from QQuickItem. Please suggest...
Generally it is a good idea to avoid accessing QML instantiated objects from outside as most of the access methods generated a dependency from C++ toward QML, restricting the way the QML tree is done.
E.g. requiring certain objects to exist at certain point in times, having specific objectName values, etc.
It is better to either "register" the object from QML side by calling a method on an exposed C++ object/API or to make the QML instantiate object register itself from within its own C++ code.
The latter is obviously inherently automatic, i.e. each instance of such a class would do that, while the former puts it at the discretion of the QML code which of the created instances it wants to make known.
Doing the following from a suggestion in discussion here solves the issue & gets a valid object to the QuickItem qml file
QQuickItem *myItem = engine.rootObjects()[0]->findChild<QQuickItem *>("myQuickItem");

QML two-way C++ binding doesn't receive changes anymore

I'm facing a big problem that it's taking a lot of time to be fixed because I don't know the cause and how to fix it. The problem is really simple: I have an example QML component defined as:
Rectangle {
id: rect
property bool test: myclass.testproperty
Rectangle {
width: 50
height: 50
visible: parent.test
}
}
and I connected a MouseArea onClicked signal to do this:
test = !test
so I switch the value of the boolean variable. To push the value from C++ to QML and from QML to C++ Q_PROPERTY with READ, WRITE and NOTIFY signals, I used this
Binding {
target: myclass
property: "testproperty"
value: rect.test
}
everything works fine until I click on the mouseArea and so I push the changes via the binding. After that, every time I try to set a new property value from C++ I don't see any change in QML, like if the binding is destroyed. But if I try to click on the MouseArea I still call the setTestProperty method of the C++ class. Basically, it goes out of sync the C++ -> QML way. Why? I can't find the problem, the signal is emitted because QSignalSpy gives me 1 as count of emitted times after using
emit this->testPropertyChanged(newvalue)
EDIT
here an example: basically here we're using a QString property with the same exact signals. The only thing that changes is that instead of using a Rectangle QML element and binding to a own property, I'm using a TextInput element
TextInput {
id: mytext
text: myclass.testproperty
}
Binding {
target: myclass
property: "testproperty"
value: mytext.text
}
There is no problem here. It is a standard QML bindings behaviour. When you change some QML Widget's property in JavaScript code, then all declarative bindings to it will be terminated. It is your choice to use declarative binding or update values manually in JS code of event handlers.
EDIT
Rectangle {
id: rect
// Establishing initial value for 'test' property via QML binding
property bool test: myclass.testproperty
// Manual update of 'test' property when binding will be broken
Connections {
target: myclass
onTestpropertyChanged: {
rect.test = xxx
}
}
}

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