System setting and update through QML GUI and slots
-
Hi,
I am implementing a touch screen for a vehicle that will display status of the vehicle, as well as the ability to change it, for elements such as temperature. I am trying to implement this by passing in the application data in a data object, and then updating this object as changes are made, as well as sending information to the vehicle telling it to make the same changes. I am trying to do this by implementing a class for the data, which emits a signal whenever the data is changed, so that the same information can then be sent to the vehicle. I am not sure if this is the correct route to take, so any suggestions or comments on my method would be appreciated.
I have implemented it as below:
In main.cpp
@
#include <QtGui/QApplication>
#include "mainwindow.h"
#include "cardata.h"int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
carData data;
w.rootContext()->setContextProperty("data", &data);
w.show();
return a.exec();
}
@In carData.h
@
class carData : public QObject
{
Q_OBJECT
Q_PROPERTY(QString setTemperature READ getTemperature NOTIFY temperatureChanged)
public:
explicit carData(QObject *parent = 0);
int getTemperature();signals:
void temperatureChanged(int newValue);
public slots:
Q_INVOKABLE void setTemperature(int value);private:
int temperature;};
@
In carData.cpp
@
#include "cardata.h"carData::carData(QObject *parent) :
QObject(parent)
{
temperature = 0;
connect(this, SIGNAL(changeOfTemperature()), this, SLOT(setTemperature()));
}int carData::getTemperature(){
return temperature;
}void carData::setTemperature(int newValue)
{
temperature = newValue;
emit changeOfTemperature(newValue);
}
@In main.qml
@
Item {
//car properties passed in to be displayed
property int temperature:data.temperature()
id: weatherContent
Rectangle {
id: rect
width: 100
height: 100
color: "red"
Text {
anchors.centerIn: parent
text: "Temperature: " + data.getTemperature()
font.pointSize: 25
color: "white"
}
MouseArea {
anchors.fill: parent
onClicked: data.setTemperature(temperature + 1)
}
}
Connections{
target: data
onChangeOfTemperature: console.log("Insert function to send car information here")
}
}
@When I try to run it, I get an error saying "TypeError: Result of expression 'data.setTemperature' [undefined] is not a function."
I am not sure what these errors are caused by, and I would greatly appreciate any advice on whether this method will work, and whether the connections is implemented correctly, since I haven't been able to see that.Thanks in advance
-
Your Q_PROPERTY is probably incorrect. The car data class should probably be declared as:
@
class carData : public QObject
{
Q_OBJECT
Q_PROPERTY(int temperature READ getTemperature WRITE setTemperature NOTIFY temperatureChanged)
public:
explicit carData(QObject *parent = 0);
int getTemperature();
void setTemperature(int value);signals: void temperatureChanged(int newValue); private: int temperature; };
@
You then need to change the usages of "data.temperature()" to "data.temperature" and "data.setTemperature(value)" to "data.temperature = value" in your QML file.
Cheers,
Chris. -
chriadam is correct. On a more general note, I would not give this object a property setter. This object is supposed to be the API to use from the QML side of things, and it seems strange to me to offer a setTemperature to a car passenger, especially since it seems to be controlling the outside temperature, not the airco :-)
-
Can you clarify whether you think that this specific property should not have a setter, or that the entire API doesn't make sense for this control situation? I am planning on adding many more methods (some which will have only setters, and some which will have getters and change signals) to the carData object so that it will keep track of sensors received from the car, and be able to set things like lights or inside temperature. Does this seem sensible?
Edit 1:
Also, wouldn't removal of the lines
@
public slots:
Q_INVOKABLE void setTemperature(int value);
@
Affect the connection?
@
connect(this, SIGNAL(changeOfTemperature()), this, SLOT(setTemperature()));
@Edit 2:
Also, by implementing chriadim's change, aren't I just accessing and changing the private variable temperature every time I get it or update it? I'm a bit confused since the variabe is private, as to whether or not I'm calling the functions and the syntax just changes when in QML. Currently it just says its undefined. If I'm not calling the functions, won't the signals not be sent to the car when I set temperature? -
I think that you should separate the API on the C++/car side from the QML/display side. That way, you can make sure you only expose the features to the QML that should be settable from by the user through your interface or that need to be displayed. So, if the user can set the temperature using the interface, by all means expose it via a property setter. However, if it is only a display feature in the QML, the interface you expose to it should not have a setter for it.
That means that the interface you expose to your QML will probably only a thin wrapper around the API object that is available for the rest of your application, only forwarding a bunch of signals, slots and properties.
-
Thanks, that makes sense. Can you comment on the syntax of the function calls and the corrollary calls in QML, and their signal connections?
-
I just wanted clarification on what exactly chriadim's change does, and how the syntax changes from c++ to QML. When I access the data in the QML, chiadrim has me access it using data.temperature instead of data.getTemperature. I am confused as to what is happening here. I am using a call that is different from what I define in c++. Is there some automatically different syntax for accessing these functions in QML (Such as data.getTemperature() becomes data.temperature)?
In addition, if I delete the slot declaration for setTemperature(), is the connection still made? So, if I am calling data.temperature = newValue to update temperature instead of data.setTemperature(newValue) does the signal get emitted?
Edit:
I am wondering particularly because it returns undefined whenever I call data.temperature even though I define the private variable temperature to be 0 in my implementation of carData. I would presume that I should actualy be accessing it as I define the READ and WRITE in my Q_Property: as data.getTemperature and data.setTemperature(newValue); but this does not seem to work and change things; it still comes back as undefined.Edit: please edit and add to your previous post instead of posting a new one if you are the last poster in the topic; Andre
-
Using Q_PROPERTY does change the way that you access something in QML/JS compared to C++, yes.
In particular: if the Q_PROPERTY statement defines a property (e.g., temperature) as having a READ function called getTemperature and a WRITE function called setTemperature, then an mutation (ie, initialisation or assignment) in QML gets "routed" to the setTemperature function and an access (ie, read) in QML gets "routed" to the getTemperature function.For ease of comparison, here are the equivalent operations:
Access:
C++: int temp = data.getTemperature();
QML: property int temp: dataObjectId.temperature
QML/JS: var temp = dataObjectId.temperatureMutation:
C++: data.setTemperature(newTemp);
QML: temperature: newTemp // ***
QML/JS: dataObjectId.temperature = newTempIn the above examples, C++ means C++ code, QML means QML object declaration syntax, and QML/JS means within a JavaScript expression within a QML object declaration.
// * * * note: property initialisation like this will only work if the object type is Data (or whatever has the "temperature" property).
Now, you say that "it returns undefined whenever I call data.temperature" --> the real question is: what is "it", and what is "data" and why do you say you "call data.temperature" (remember, it's not a function - it's a property).
Cheers,
Chris./edit: clarifications, and removed forum formatting for asterisks :-/
-
moderator note: I have merged your consecutive posts into a single post. If you want to add to a previous post, especially if you are the last one posting in the topic, please considder if it may be better to edit your previous post instead of creating a new one.
The approach described by chriadam is not just a matter of a different style. It matches much better with the declarative paradigm around which QML is build. Instead of describing the steps to get somewhere, you describe what is, and you let the system make the adaptations to make that happen.
So, instead of explicitly declaring what needs to happen when your temperature changes, you describe using property bindings how your display and the value of the temperature are related. If the API temperature changes, the sytem will make sure that the display is modified accordingly. When working on your application, try to see if you can avoid javascript to bind the C++ part to the QML. Sometimes, that will seem very difficult at first, but you need to get used to the way of thinking first. Once you're used to it, you'll find it quite natural.
Properties and property binding is the key to this approach.
-
Ok, I understand the Q_property syntax and interaction better, but I am still having a bit of trouble understanding the access to the temperature property. I have been reading lots of examples and documentation on this, but it is all just becoming muddled. I think I am misunderstanding either Q_Property or the use of setContextProperty.
Right now, I create the temperature property with read and write access in a Q_Property:
@
class carData : public QObject {
Q_PROPERTY(int temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged)
@
Then in main.cpp I create a carData object, and pass it into the setContextProperty as I have seen done in several examples.
@
MainWindow w;
carData *data = new carData();
w.rootContext()->setContextProperty("data", data);
@
I am confused by this part because the documentation has examples where objects are passed in and then used to access data (see background color example: http://apidocs.meego.com/1.2/qt4/qtbinding.html ), and also where properties within the QML are set such as in:
@
C++ { w.rootContext()->setContextProperty("information", 3);}
QML{ text: information }
@
I want to set temperature with all the property's read/write abilities, but I am not sure exactly how I should do this. Initially, I just had it set in the QML like this:
@
text: "Temperature: " + data.temperature
@
But, this returns undefined. Seeing that setContextProperty is setting a property, I also thought to try in QML something like this:
@
property int temperature: data.temperature
@
...but it simply sets temperature to 0, even when I have the return value statically defined as "2" etc, so I assume it is just setting 0 as the default value for temperature since data.temperature is not defined.So, I am wondering, how do I correctly pass in the data object and access the temperature property in QML?
EDIT:
Problem Solved.The problem was that by passing in the context as ("data", data), when referencing data in QML it wasn't correctly identifying the property. By changing what was passed in to ("carData", data), and setting property int temperature: carData.temperature, it works