cascades and signals / slots - c++

I'm running around in circles about this. Just can't wrap my head around signals and slots.
Just looking for some mechanism that can automatically update my UI when a signal in my C++ occurs.
Example:
I have two labels in Qml that have text: _app.method that returns a value.
I have a button that onClicked runs a Q_INVOKABLE method. That method emits a signal when it's done, eg, fetches geocordinates and updates the values that the above text: assignments rely on.
What I want is SOMETHING to update the text: assignments once those values change.
I just need these signals / slots explained plainly. The only examples in documentation seem to assume ONLY QML or C++ but not a mix of both. The sample code have examples, but not explained specifically in documentation.
If you had plain description, im sure I could adapt to it. Eg, 1: define this in QML, 2: define this in hpp file, 3: define these in cpp file.
I've tried using QObject's setPropery("text","value") but my app crashes when attempting this.
Tell me if i'm wrong...
1) in QML:
Button {
id: aButton
text: _app.value
onClicked: {
_app.valueChanged.connect(aButton.onValueChanged);
_app.value = _app.value + 1;
}
function onValueChanged (val) {
aButton.text = "New value: " + val;
}
}
2) in HPP:
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
int value();
void setValue(int i);
signals:
void valueChanged(int);
private:
int m_iValue;
3) in CPP:
int class::value()
{
return m_iValue;
}
void class::setValue(int i)
{
// name is same as HPP WRITE Q_PROPERTY statement
m_iValue = i;
emit valueChanged(m_iValue);
}
So, what happens is that, in QML, the onClick method CONNECTS the signal with a QML Function; which means, now we're listening for a value change, and when it does, that function will be called. THEN, we change the value... since the Q_PROPERTY set the write value to a function called setValue, setValue is called with the new value; internally, m_iValue is changed, and an emit occurs, which tells whoever is listening to valueChanged that there's a new value.
Hey, my QML is listening to that! (via the _app.valueChanged.connect script). So, the QML object (the Button) that was listening to that, has it's onValueChanged function called, with the new value (because of the emit valueChanged(m_iValue).
Please tell me i've figured this out??!?!

If you are using Q_PROPERTY macro, there's no need to bind onValueChanged signal with a function explicitly to change button's text. And also you need not emit valueChanged signal with m_iValue. Make below mentioned changes in corresponding files
QML:
Button {
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
id: aButton
text: _app.value
onClicked: {
_app.value = _app.value + 1
}
}
HPP:
signals:
void valueChanged();
CPP:
emit valueChanged();

Related

How to pass a QML callback to c++ invokable function and execute it in the GUI thread?

I have a c++ Q_INVOKABLE function that starts an asynchronous operation (image downloading). I want to pass a QML callback to the function for when the operation finishes.
I know that it could be done with QJSValue, but this way the callback is not called in the GUI thread so when I try to update some QML element, it prints an error and crashes the application: Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode(). Also, that question is 5 years old and maybe a better way has been introduced since then.
This is my QML code:
Rectangle {
// ...
Loader {
id: loader
source: "[loading animation]";
}
Component {
id: imageView
Image {
id: image
// ...
}
}
Component.onCompleted: {
ImageLoader.start_loading(post_id, function () {
loader.sourceComponent = imageView;
});
}
And the c++ code:
void ImageLoader::start_loading(const QString & id, QJSValue on_finished)
{
ImageLoaderRunnable * runnable_raw = new ImageLoaderRunnable(id, this->m_cache);
connect(runnable_raw, &ImageLoaderRunnable::finished, [on_finished]() mutable {
if (on_finished.isCallable()) {
on_finished.call();
}
});
runnable_pool.start(runnable_raw);
}
Also, this method is unsafe since the QML object can be destroyed before the callback is called.
You need to emit a signal from your C++ model, and handle the signal in the QML.
You really need to give more detail why that doesn't work for your use case.
If you have a ListView then you might need to define a role for whatever you need to pass to your view and emit dataChanged for the index of image that was loaded to update the view.
I figured it out. I just needed a way to call the slot in the GUI thread. When I pass the receiver object to the connect() method, it runs the slot in the receiver's thread. Thus, I can pass either the qml engine object or the qml component that calls the function to the connect() method and it will work.
A:
void ImageLoader::start_loading(const QString & id, QJSValue on_finished)
{
ImageLoaderRunnable * runnable_raw = new ImageLoaderRunnable(id, this->m_cache);
// m_engine -- pointer to QQmlApplicationEngine.
connect(runnable_raw, &ImageLoaderRunnable::finished, m_engine, [on_finished]() mutable {
if (on_finished.isCallable()) {
on_finished.call();
}
});
runnable_pool.start(runnable_raw);
}
B:
// c++
void ImageLoader::start_loading(const QString & id, QObject * receiver, QJSValue on_finished)
{
ImageLoaderRunnable * runnable_raw = new ImageLoaderRunnable(id, this->m_cache);
connect(runnable_raw, &ImageLoaderRunnable::finished, receiver, [on_finished]() mutable {
if (on_finished.isCallable()) {
on_finished.call();
}
});
runnable_pool.start(runnable_raw);
}
// qml
Component.onCompleted: {
ImageLoader.start_loading(post_id, this, function () {
loader.sourceComponent = imageView;
});
}

How to hook up to the onClick event of a QML item from C++ side

Scenario:
I have a Qt app which runs on Qt 5.9.4 commercial edition. Its a QtQuick and QML based application which is running on iOS and Android.
I have a QML item on the UI like this:
SomeItem {
text: qsTr("Some Item")
objectName: "someitem"
visible: false
onClicked: {
console.log("Some item was clicked")
}
}
I have a C++ function which can easily control the properties of SomeItem.
void MyCppClass::Func() {
QQuickItem *someItem = qml_engine->rootObjects()[0]->findChild<QQuickItem*>("someitem");
someItem->setVisible(true); // this works
// How to listen to someItem's onClick event here
}
Question:
I want to listen to the onClick event of someItem in a C++ method or a lambda without changing anything in the QML. Basically hook up to the onClick signal signal of someItem from C++ side itself. How can I do it?
The method to use to interact can be dangerous in the general case because the life cycle of the items depends on QML, so make sure it does not happen. Going to your request and I assume that MyCppClass inherits from QObject or a child class you must create a slot and use the old connection syntax:
*.h
class MyCppClass: public QObject
{
...
private slots:
void on_clicked();
...
};
*.cpp
void MyCppClass::Func() {
QQuickItem *someItem = qml_engine->rootObjects()[0]->findChild<QQuickItem*>("someitem");
if(!someItem)
return;
someItem->setVisible(true); // this works
connect(someItem, SIGNAL(clicked()), this, SLOT(on_clicked()));
}
void MyCppClass::on_clicked(){
qDebug()<<"on clicked";
}

Cannot connect c++ signal to QML [duplicate]

I have a QML file containing this:
Text {
id: testData
onTaskClicked:{
testData.text = task.name
}
}
The catch is this taskClicked signal. It is emitted by another widget (C++) and needs to be relayed to QML.
This is similar to this SO question, except that the solution posted there doesn't work (why is written below).
The C++ code:
ctxt->setContextProperty(QLatin1Literal("holiday"), m_model);
ctxt->setContextProperty(QLatin1Literal("bgcolor"), color);
view->setResizeMode(QQuickView::SizeRootObjectToView);
auto mainPath = QStandardPaths::locate(QStandardPaths::DataLocation,
QLatin1Literal("taskview.qml"));
view->setSource(QUrl::fromLocalFile(mainPath));
ctxt->setContextProperty(QLatin1Literal("viewer"), m_view);
m_view is a QListView subclass that emits the taskClicked(HolidayTask* task) signal (from the .h file):
Q_SIGNALS:
void taskClicked(HolidayTask* task);
color and m_model are registered in QML and are used elsewhere. The object from the signal is already registered in QML. view is my QQuickView.
First I tried the solution presented in the question above:
auto root = view->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData");
connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement,
SLOT(taskClicked(HolidayTask* task);
However, myElement is always null (and I get a runtime warning about a non existent slot).
If I try to set the view (the QListView) pointer as a context property of the QML view, it still doesn't work.
In all cases, I get also:
QML Connections: Cannot assign to non-existent property "onTaskClicked"
What could I possibly be doing wrong here?
EDIT to clarify some details: HolidayTask is a custom QObject subclass, and the signal taskClicked is defined in C++ (in a QListView subclass)
EDIT2: We're getting close, but no cigar:
auto root = quickView->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData"));
connect(m_view, SIGNAL(taskClicked(HolidayTask*)),
myElement, SIGNAL(taskClicked(HolidayTask* task)));
and
Text {
id: testData
objectName: "testData"
signal taskClicked(HolidayTask task)
onTaskClicked: {
testData.text = task.name
console.log("CLICk!")
}
}
yields
QObject::connect: No such signal QQuickText_QML_0::taskClicked(HolidayTask* task) in /home/lb/Coding/cpp/holiday-planner/src/mainwindow.cpp:178
QObject::connect: (receiver name: 'testData')
More details: HolidayTask, my custom QObject subclass, is registered in the code as
qmlRegisterType<HolidayTask>("HolidayPlanner", 1, 0, "HolidayTask");
Minimal QML with the data:
import QtQuick 2.0
import QtQml 2.2
import HolidayPlanner 1.0
Rectangle {
id: container
objectName: "container"
color: bgcolor
Text {
id: testData
objectName: "testData"
signal taskClicked(HolidayTask task)
onTaskClicked: {
testData.text = task.name
console.log("CLICK")
}
}
}
EDIT3: The final, working code is (see answers on why)
connect(m_view, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)),
myElement, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)));
This worked only through the use of objects with full namespaces. Otherwise the signature will not match in QML.
However, myElement is always null (and I get a runtime warning about
a non existent slot).
You are trying to find the child based on id, whereas it is based on the objectName property. You would need to set the objectName property to the desired to actually find it.
Also, you do not seem to declare the signal in your QML Text item. I am not sure if it is a custom C++ item or a built-in one. You have not shared enough code unfortunately to understand that bit. Either way, declare your signal as per documentation.
Therefore, try this code out:
Text {
id: testData
objectName: "testData"
// ^^^^^^^^^^^^^^^^^^^
signal taskClicked (HolidayTask task)
// ^^^^^^^^^^^^^^^^^^^
onTaskClicked:{
testData.text = task.name
}
}
Once that is done, you are almost ready. You need to have your HolidayTask registered to QML for sure, and you also need to change the connect syntax in your main.cpp as follows:
connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement, SIGNAL(taskClicked(HolidayTask* task)));
In short, you need to trigger your QML signal handler this way, and not via SLOT.
Also, note that your connect syntax is broken as it is missing the closing brackets at the end. That needs to be fixed.
I would even consider removing the pointer assignment and use value or reference based passing for this.
You can connect a signal from C++ to QML by:
view->rootContext()->setContextProperty("testData",this);
QObject::connect(this,SIGNAL(taskClicked(HolidayTask* task)),(QObject *)view->rootObject(),SLOT(onTaskClicked(HolidayTask* task)));
When your signal is named taskClicked, The slot in QML should be onTaskClicked.
Also in QML you should name the object testData by:
objectName: "testData"
QML Connections: Cannot assign to non-existent property "onTaskClicked"
The error tells you that your Text item do not have signal taskClicked or property onTaskClicked!
You need to declare a slot inside your text item. To do that, you simply declare a function:
Text {
id: testData
objectName: "testData" // as Laszlo said
function onTaskClicked( task ) {
testData.text = task.name;
}
}
But that also won't work because you create a SLOT( onTaskClicked(QVariant) ) instead of SLOT(taskClicked(HolidayTask*)). In order to exchange data with QML you need to change your signal to SIGNAL(taskClicked(QVariant)):
Q_SIGNALS:
void taskClicked(QVariant task);
And emit it with:
emit taskClicked( QVariant::fromValue( task ) );
Remember that in order to be able to use HolidayTask it must be a QObject that is registered with qmlRegisterType.
You can also simply call that qml function.
If you are not able to use QVariant you can declare a signal inside you Text object:
Text {
id: testData
objectName: "testData" // as Laszlo said
signal taskClicked ( HolidayTask task )
onTaskClicked: {
testData.text = task.name;
}
}
And then connect from a C++ SIGNAL to qml SIGNAL:
auto root = view->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData");
connect(m_view, SIGNAL(taskClicked(HolidayTask*), myElement,
SIGNAL(taskClicked(HolidayTask*));

Exchange values between C++ and QML

How do i send a value from main.cpp into Qml file within my qt quick project
transform: Rotation {
id: needleRotation
origin.x: 5; origin.y: 65
angle: -120 + VALUE*2
}
I need the value from Cpp frequently for a speedometer made with qt quick 2.0
I guess the property is produced by some object. In that case you can exploit Q_PROPERTY (see here).
Following what is shown in the link I provided you can rewrite your class as follows:
class DataProvider : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
void setValue(qreal newVal) { // <--- do your stuff to update the value
if (newVal != m_value) {
m_value = newVal;
emit valueChanged(); // <--- emit signal to notify QML!
}
}
qreal value() const {
return m_value;
}
signals:
void valueChanged(); // <--- actual signal used as notification in Q_PROPERTY
private:
qreal m_value; // <--- member value which stores the actual value
};
Here we defined a property value with the corresponding getter and setter (value and setValue resp.) The setter method emits the notification signal which is fundamental to notify QML when the value is changed.
Now, to expose the object to QML (and hence its property) just register it as a context property; just write in your main:
DataProvider data;
engine.rootContext()->setContextProperty("data", &data); // ALWAYS before setting the QML file...
Now the DataProvider instance data can be used through the name data inside QML. Simply rewrite your QML like this:
transform: Rotation {
id: needleRotation
origin.x: 5; origin.y: 65
angle: -120 + data.value * 2
}
Each time you call setValue() in your C++ code and a change occurs to the value, a notification is issued and the binding revaluated.
Q_PROPERTY is the answer.
For general inf on properties: http://qt-project.org/doc/qt-4.8/qml-extending.html.
Look for Q_PROPERTY in this article: http://qt-project.org/doc/qt-5/properties.html.
The second article is a must for C++/QML development (read the whole article).
And more recent and structured info: http://qt-project.org/doc/qt-5/qml-extending-tutorial-index.html
I read the second and that still works but it makes sense to revisit with new docs.
If you want to expose single value to QML directly from main() function, use QQmlContext::setContextProperty. Place this before engine.load(...) call:
engine.rootContext()->setContextProperty("VALUE", 10.0);
Note: You might want to adopt some sort of naming convention to distinguish context properties from local variables and properties. For example, I start all context property names with underscore like this: _value.

Post HTTP request from qt5 using qml

Hi everyone,
I am trying to send the http post request from my qt app. I have read alot and still struggling to get some concepts of signals and slots. Would be nice if somebody can help me out from here..
here is my qml code snippet:
TextField { id: u_name; placeholderText: userText(); Layout.fillWidth: true; style: StyleTextField {} }
TextField { id: p_text; echoMode: TextInput.Password; Layout.fillWidth: true; style: StyleTextField {} }
Button {
id: signInButton
text: "Sign In";
style: StyleButton {}
Layout.fillWidth: true;
//Layout.alignment: Qt.AlignTop;
signal esLoginClicked()
onClicked: {
if (u_name.text.length) Settings.userText = u_name.text;
if (p_text.text.length) Settings.passText = p_text.text;
signInButton.esLoginClicked().connect(esLogin(u_name.text, p_text.text));
page_stack.pop();
}
}
Here I am trying to get username and password from user and want to pass it to slot "esLogin" that I have declared in my header file using signal esLoginCLicked() which I have created here only. My header files looks like this...
Q_OBJECT
Q_PROPERTY(QString userText READ userText WRITE setUserText NOTIFY userTextChanged)
Q_PROPERTY(QString passText READ passText WRITE setPassText NOTIFY passTextChanged)
public:
static esQuickSettings *instance(void);
public:
QString userText(void);
QString passText(void);
// void esLoginClicked(void);
// void esLoginClicked(const QString& userText, const QString passText);
public:
void setUserText(const QString& user);
void setPassText(const QString& passt);
void esLogin(const QString& userText, const QString& passText);
signals:
void userTextChanged(void);
void passTextChanged(void);
but somehow I am not able to make it work and missing some key concept here to make signal and slot work.
P.S: I want to take input from QML and put in slot which will have the definition in cpp file respective to header.
There are (at least) two ways to address this issue, but I will only let you know one of them based on the comment discussion.
Connect the QML signal to the C++ slot.
main.qml
...
Button {
id: signInButton
// This is necessary for finding this nested item in C++
objectName: "SignInButtonObjectName"
...
}
...
main.cpp
...
QQmlEngine engine;
QQmlComponent component(&engine, "main.qml");
QObject *object = component.create();
QObject *childObject = object->findChild<QObject*>("SignInButtonObjectName");
Foo foo;
QObject::connect(childObject, SIGNAL(esLoginClicked(const QString&, const QString&)), &foo, SLOT(esLogin(const QString&, const QString&)));
...
The other approach would be to call the C++ slot in your qml code when the signal happens to be emitted which is probably even simpler. In that case, you would make the method below either Q_INVOKABLE or even better: a slot.
void esLogin(const QString& userText, const QString& passText);
Then, you would need to make sure that this method is exposed to qml via context properties, namely: you would make the class a context property which would be available to qml for calling like foo.esLogin() in your desired qml signal handler.