Need some guidance on using signals to communicate between QML and C++
-
First off, here is the context of the application: basic calculations need to be done using values in TextFields. Button labels for the TextFields are used to indicate value to be derived. For a simple, contrived example say you have a function y=x+1, and you want to have two fields for y and x and two buttons to indicate whether you are solving for y or x. So a calculator of sorts but for which value to be solved can be any of the operands.
BTW this is intended for a Symbian Belle application.
@
Page {
Button {
signal calcYSignal
id: yButton
text: "y"
onClicked: yButton.calcYSignal()
}TextField {
id: yTextField
text: "0"
}Button {
signal calcXSignal
id: xButton
text: "x"
onClicked: xButton.calcXSignal()
}TextField {
id: xTextField
text: "0"
}
}@So the approach I am taking is to create a calcXY C++ class with public slots into which QML signals for each button would tie into. So that when a user presses the Y button, the C++ class would take the value of x and calculate y, or conversely if a user presses the X button, calcXY would take the value of y and calculate x. However, I am not really clear on the best way to reference the QML component.
I am trying to follow the example given in " 4.7: section Using QML Bindings in C++":http://doc.trolltech.com/4.7-snapshot/qtbinding.html as a guideline (Refer in particular to the section labelled "Receiving Signals").
@ QDeclarativeView view(QUrl::fromLocalFile("MyItem.qml"));
QObject *item = view.rootObject();@However, I am having a problem right off the bat with the cast of *item in line 2. The error I get is:
@error: cannot convert 'QGraphicsObject*' to 'QObject*' in initialization@
So, what I am hoping to get is a minimal example, which illustrates the signals and slots mechanism between QML and C++; one that shows how a button press can invoke a slot function in C++ which will then take the values in TextFields on the page to derive a result and then display it back on one of the TextField.
I imagine that this should be simple, standard fare but I cannot seem to get a template that works. The actual calculator is much more sophisticated, but the same principles would apply. Any help would be greatly appreciated.
Thanks.
-
Create your QObject-derived C++ class which does the calculations (and has slots to accomplish your desired tasks) and expose it to QML through setContextProperty() (as described in the 2nd example "here":http://qt-project.org/doc/qt-4.8/qtbinding.html#calling-functions )
Then, if your object is exposed as "myclass" and your slot is "calcX()" calling the slots in your C++ file is as simple as:
@
Button {
id: xButton
text: "x"
onClicked: myclass.calcX()
}
@(If you still wish to use signals emitted from your QML and connect them in C++ to C++ slots, that is also documented in the link above in the subsequent section, "receiving signals".)
-
Thank you for the prompt reply. I was attempting to use the examples in the same document albeit referenced from the trolltech.com site; I just happened to find that version first in my search.
I will give your example a closer look as the casting problem I described happened precisely when following the "Receiving Signals" section.
I have also found another "example":http://www.developer.nokia.com/Community/Wiki/CS001625_-_Connecting_Qt_signal_to_QML_function where dynamic_cast was used:
@QObject rootObject = dynamic_cast<QObject>(view.rootObject());@
I'll report back when I try these various methods.
Thanks again.
-
Your method worked wonderfully once I figured out that I could use QmlApplicationViewer just like a QDeclarativeView:
@
QmlApplicationViewer viewer;
viewer.setMainQmlFile(QLatin1String("qml/calcXY/main.qml"));
viewer.rootContext()->setContextProperty("calcXYObject, &calcXYClass);
viewer.showExpanded();
@I was confused initially as to how to setContextProperty to the root of the "Page" that was relevant for my function invocations, but then I figured out that handing the root of the entire QML tree should suffice.
So I suppose that I can set up all the various Calc classes (for the different functions to be calculated) using separate setContextProperty calls in main.cpp.
-
With the signal approach, things are complicated by the fact that if you use Qt Components the delayed instantiation of the page makes it difficult to set the signal up in main.cpp. Or am I missing something? The above example with the signal approach would read something like:
@
CalcXY calcXY;QObject rootObject = dynamic_cast<QObject>(viewer.rootObject());
QObject::connect(rootObject, SIGNAL(calcXYSignal()),
&calcXY, SLOT(calcXYSlot()));
@but then I get the following error at runtime:
@
Object::connect: No such signal PageStackWindow_QMLTYPE_13::calcXYSignal()
@which makes sense since calcXYSignal isn't declared in main.qml, which is the start page of the PageStack. It is declared in another QtComponent, calcxy.qml. One can easily imagine a set of such qml files: calcxy.qml, calcab.qml, calccd.qml, etc., each dealing with a specific function.
Is there a way of making this signals example work with Qt Components which are instantiated as needed and so I am presuming one cannot search the QML tree to get the appropriate node?
-
I'm not sure what hoops would have to be jumped through to dynamically search through the QML as needed. It may be possible, but I'm much too tired to think through it at the moment. At any rate, that's partially why I prefer to expose the C++ code to the QML. In my mind, the QML is much more dynamic and prone to change (either in the short-term, like loading and unloading of pages/components, or in the long-term via page redesign and layout changes and such) whereas the business logic (written in C++) is typically more stable and less likely to change as much. This way, your program only has to rely on a looser coupling between the C++ and QML than you'd have to have in order to have the C++ code know more details about the interface. I prefer to think of it as the solid, structured C++ code presenting itself to the QML, which is much more dynamic, fluid, and arbitrary.
Granted, there are always other options and different designs and philosophies, but this is just my opinion based on my experience.
-
Your approach makes a lot of sense now that I have experimented a bit. The C++ object is a singleton and so the namespace will be consistent to the various Qt Components. I see now why you prefer to expose the C++ slots to the dynamic QML code.
This will be the approach that I will take for my project.
Thanks for your (late night) help. It certainly removed the logjam in me proceeding with development.
-
It was my pleasure! I'm glad to help. If you have any other issues, feel free to ask! (And welcome to the forums, btw!)
One more thing, if you feel your issue is solved, please edit the initial post and change the title to add [Solved] to the beginning. Thanks!
-
Okay, I thought that the flip side—that is getting and setting the values of QML components from C++—would be easier to get through given the link provided above, but I am still stuck at a basic conceptual level.
My problem is two-fold:
- Say I have a Button and its associated TextField as a child:
@
Button {
id: xButton
x: 45
y: 231
text: "x"
onClicked: calcXY.calcX()Item { property alias x: xTextField.text TextField { id: xTextField /* anchors { left: parent.right; leftMargin: 10; verticalCenter: parent.verticalCenter } */ y: 136 width: 126 height: 50 text: "120" } } }
@
I need to be able to access the value of the TextField in my C++ code. Again using the link given above, the example points to using a property. However, the example doesn't really go into how to access an existing property. So I have attempted to use the example given "here":http://stackoverflow.com/questions/9062189/how-to-modify-a-qml-text-from-c.
The second problem listed below remains, but my first question is whether an intermediate Item wrapper is required at all? Is there no way to directly manipulate the text "property" in the TextField?
And if the Item wrapper is required, then what is the best way to preserve the anchor relationship between xButton and xTextField? The code given above binds the anchors to Item, which isn't what I want.
- My second problem is more fundamental: how does one get the root of the QML stack anywhere other than in main where the viewer is created and set:
@
QmlApplicationViewer viewer;viewer.setMainQmlFile(QLatin1String("qml/CalcXY/main.qml")); QObject *rootObject = viewer.rootObject();
@
In my CalcXY class, I need the root object if I am to use the example to affect the property x:
@
void CalcXY::calcXSlot()
{
qDebug() << "Property value:" << QDeclarativeProperty::read(rootObject, "x").toInt();
}
@In other words I need to have rootObject to navigate the QML tree. Is this a global variable? Do I need to set it up as a global in main, or is there a better method? How do I make rootObject visible inside the CalcXY object?
Thanks.
-
It would be simpler to just let the calcX slot accept a parameter. Then you could just send the data into C++ when you call the slot. Again, this decouples the C++ code from the QML.
As in:
@
Button {
...
onClicked: calcXY.calcX(xTextField.text);
}
@The calcX slot could then emit its result via a signal which the QML code could reference and respond to.
Typically, if you find yourself needing your C++ code to dig down into the QML, there's probably some redesign that can be done.
Additionally, there's no need to wrap your TextField in an Item. TextField, itself, IS an Item.
Is there any particular reason you choose to have the TextField as a child of the Button? From the commented-out anchor code, it appears you intend them to be laid out next to each other on-screen. I would make them siblings:
@
Button {
id: xButton
...
}TextField {
id: xTextField
...
}
@(An item doesn't need to be a child in order to allow access to its properties.)
-
I had initially handcoded the QML with the TextField and Buttons as siblings, but in one of my manipulations with Designer, I think the TextFields (there are three Button/TextField combinations) adopted child relationships.
I'll give your approach a try right now.
Thanks.
-
Nearly there. I have the signal mechanism working from C++ being caught via Connections in the QML.
Just a quick question: how does one trap when the user has finished editing a TextField, i.e., by leaving the field. In my case such an event would indicate that the other values may be calculated based on this change. I have tried 'onActiveFocusChanged' but that triggers upon both entry and exit of the TextField. 'onTextChanged' triggers an event for every character stroke. My other guess 'onDataChanged' was reported as invalid by the compiler. I also cannot seem to find a comprehensive list of these for the Qt Components for Symbian. In the Docs they are listed for Qt classes.
Essentially I have a (linear) function of 3 variables. When one of the values changes (the event that I would like to trap for indicated by the user leaving the field), the other two values will get updated to keep the function consistent. It is this 'value change' that I am trying to trap inside QML:
@
onActiveFocusChanged: calcXYObject.setX(xTextField.text)/* If x has been recalculated because either y or z were changed. */ Connections { target: calcXYObject onXHasChanged: console.log("x has changed") }
@
This code triggers both on entry into the x TextField as well as exit from the same.
Thanks.