I have a publisher/subscriber key-value DB class in Qt/C++. The subscribers can connect by passing the key ( string ) , their QObject pointer and the property.
Whenever a value of the subscribed key changes, the properties of the subscribed QObject changes to the new value. Works in Qt/C++ fine.
Now I want to make a view in QML. Is it possible to pass from QML to C++ an object with 3 parameters:
QObject pointer of the QML object
property as string
DB-key as string
?
The preferable solution were, as if the property connects to another property:
Item{ myQmlProp: MyCppInst("myDBKey") }
EDIT
What currently works is this solution:
Item{
id:myqmlitem
myQmlProp: MyCppInst("myDBKey","myQmlProp",myqmlitem)
}
or like this:
Item{
id:myqmlitem
Component.onCompleted:{
MyCppPublisher.subscribe("myDBKey1","myQmlProp1",myqmlitem)
MyCppPublisher.subscribe("myDBKey2","myQmlProp2",myqmlitem)
}
}
Compared to the preferable solution, I have to pass the connected property name and the QML item instance explicitly. But it is ok, many thanks for the answers!
I've hoped to use QML's this-Keyword but have learned, that it is currently undefined in QML :-(
Just give the object an id and pass that id to the function, it will become a QObject * on the C++ side. Then you can use the meta system to access properties by name:
// qml
Item {
id: someitem
...
CppObj.cppFoo(someitem)
}
// c++
void cppFoo(QObject * obj) {
...obj->property("myDBKey")...
}
A reference would do as well, for example children[index].
What you could do is a function taking just your dbkey as a parameter, and return a QObject* exposing a Q_PROPERTY with a READ function and NOTIFY signal.
This way, you just have to tell with the notify signal the value has changed, and the QML will call the read function automatically.
It could be implemented like that Item{ myQmlProp: MyCppInst("myDBKey").value }.
If you know the db keys at compile time you could just add a property for each of them in your MyCppInst directly, or if you know them at the creation of your cpp class you could put them in a QQmlPropertyMap.
Usage would be like that : Item { myQmlProp: MyCppInst.myDbKey } (or MyCppInst["myDbKey"] if you need to be dynamic in the QML side).
Related
Right up-front, I'll apologize: This is a monster question, but I wanted to provide what I hope is all of the pertinent details.
I've got a QML-based GUI that I was tasked with taking over and developing from proof-of-concept to release. I believe the GUI is based on an example provided by QT (Automotive, maybe?). The GUI is being compiled for web-assembly (emscripten) and features a "back-end data-client" which communicates with our hardware controller via a socket and communicates with the GUI via signals. The GUI is accessed via web browser and communicates with the Data_Client via QWebSocket.
The GUI proof was initially created with a very "flat" hierarchy where every element is created and managed within a single ApplicationWindow object in a single "main" QML file. A Data_Client object is instantiated there and all the other visual elements are children (at various levels) of the ApplicationWindow:
ApplicationWindow {
id: appWindow
//various properties and stuff
Item {
id: clientHolder
property Data_Client client
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
layoutDirection: Qt.LeftToRight
//And so on...
The Data_Client C++ currently emits various signals in response to various things that happen in the controller application. In the main .QML the signals are handled as follows:
Connections {
target: client
onNew_status_port_data:
{
textStatusPort.text = qdata;
}
onNew_status_data_act_on:
{
imageStatusData.source = "../imagine-assets/ledGoodRim.png";
}
//and so on...
What I'm trying to do is create a ChannelStatusPanel object that holds the various status fields and handles the updates to those fields (text, images, etc.) when it receives information from the Data_Client backend. There are multiple instances of this ChannelStatusPanel contained in a MainStatusPanel which is made visible or not from the main ApplicationWindow:
Having said all of that (Phew!), I come finally to my question(s). What is the correct way to signal a specific instance of the ChannelStatusPanel object from the Data_Client with the various data items needed to drive changes to the visual elements of the ChannelStatusPanel?
I thought I was being clever by defining a ChannelStatusObject to hold the values:
Item {
id: channelStatusObject
property int channel
property int enabled //Using the EnabledState enum
property string mode
property int bitrate
property int dataActivity //Using the LedState enum
//and more...
property int packetCount
}
In the ChannelStatusPanel.qml, I then created a ChannelStatusObject property and a slot to handle the property change:
property ChannelStatusObject statusObject
onStatusObjectChanged: {
//..do the stuff
From the Data_Client C++ I will get the information from the controller application and determine which "channel" I need to update. As I see it, I need to be able to do the following things:
I need to determine which instance of ChannelStatusPanel I need to update. How do I intelligently get a reference to the instance I want to signal? Is that just accomplished through QObject::findChild()? Is there a better, faster, or smarter way?
In the Data_Client C++, do I want to create an instance of ChannelStatusObject, set the various fields within it appropriately, and then set the ChannelStatusPanel instance's ChannelStatusObject property equal to the newly created ChannelStatusObject? Alternatively, is there a mechanism to get a reference to the Panel's ChannelStatusObject and set each of its properties (fields) to what I want? In C++, something like this:
QQmlComponent component(&engine, "ChannelStatusObject.qml");
QObject *statObj= component.create();
QQmlProperty::write(statObj, "channel", 1)
QQmlProperty::write(statObj, "bitrate", 5000);
QQmlProperty::write(statObj, "enabled", 0);
//Then something like using the pointer from #1, above, to set the Panel property
//QObject *channelPanel;
QQmlProperty::write(channelPanel, "statusObject", statObj)
Is there some other, more accepted or conventional paradigm for doing this? Is this too convoluted?
I would go about this using Qt's model-view-controller (delegate) paradigm. That is, your C++ code should expose some list-like Q_PROPERTY of channel status objects, which in turn expose their own data as properties. This can be done using a QQmlListProperty, as demonstrated here.
However, if the list itself is controlled from C++ code -- that is, the QML code does not need to directly edit the model, but only control which ones are shown in the view and possibly modify existing elements -- then it can be something simpler like a QList of QObject-derived pointers. As long as you do emit a signal when changing the list, this should be fine:
class ChannelStatus : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int channel READ channel CONSTANT)
Q_PROPERTY(int enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
// etc.
};
class Data_Client : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QList<ChannelStatus*> statusList READ statusList NOTIFY statusListChanged)
// ...
};
The ChannelStatus class itself must be registered with the QML type system, so that it can be imported in QML documents. Additionally, the list property type will need to be registered as a metatype, either in the main function or as a static variable. Otherwise, only lists of actual QObject pointers are recognised and you would have to provide yours as such.
qmlRegisterUncreatableType<ChannelStatus>("LibraryName", 1, 0,
"ChannelStatus", "Property access only.");
qRegisterMetaType<QList<ChannelStatus*>>();
You then use this property of the client on the QML side as the model property of a suitable QML component, such as a ListView or a Repeater inside a container like RowLayout. For example:
import LibraryName 1.0
ListView {
model: client.statusList
delegate: Column {
Label { text: modelData.channel }
Image { source: modelData.enabled ? "foo" : "bar" }
// ...
}
}
As you can see, the model data is implicitly attached to the delegate components. Any NOTIFYable properties will have their values automatically updated.
Below is how I've setup my application.
Front-end is in QML. Back-end is in Qt C++. H/W Controller application is in C.
In Qt C++ back-end, I have QObject derived database and databasePoint classes.
database object holds a QMap of databasePoint objects.
Each databasePoint object has a unique point name, which is used as an identifier.
database object is created in main.cpp and exposed to QML as a context property.
database class has a method to return a pointer to databasePoint object.
In QML, this method is used to create databasePoint objects.
When this method is called, a databasePoint object is created and added to QMap if it doesn't already exist.
databasePoint class has read-write value properties.
These properties are used for communication between UI, back-end and controller.
In a timer, latest value is polled from controller periodically, whenever there's a change, the value property is updated.
When the value property is updated from UI, the value is written to controller.
class database : public QObject
{
public slots:
databasePoint* getDbpointObject(QString pointName);
private:
QMap<QString, databasePoint*> dbPointMap;
};
class databasePoint : public QObject
{
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
QVariant value(void);
void setValue(QVariant value);
QVariant my_value;
signals:
void valueChanged();
};
DatabasePointComponent.qml:
Item {
required property string pointName
property var pointObj: database.getDbpointObject(pointName)
}
MyScreen.qml:
DatabasePointComponent{
id: dbTEMP
pointName: "TEMP"
}
TextInput {
text: dbTEMP.pointObj.value
onAccepted: dbTEMP.pointObj.value = text
}
I'm trying to develop a Qt C++ application, with a QML frontend, but I hit a roadblock.
This is what I have so far:
A Factory class that outputs a choice of objects. These objects, that I'm going to call "controllers", control different pieces of hardware.
The Factory would be exposed to the QML layer with setContextProperty.
The controller would be chosen basically with a combo box controlling the factory.
Now, for the tricky bit. I want that the "controllers" behave in a "bring your own component" way. This means that they would have a method returning the respective QML file for their controller. That shouldn't be to hard to do, it's basically biding a Loader to a method of the Factory/Manager saying the file with the component to load into a placeholder.
But the problem is: how can this newly created component and this newly created controller know and talk to each other? This is something I did before with QWidgets, just having pointers between the classes. Quite trivial.
I tried an architecture like this before for QWidgets, but seems to not be ideal for QML.
I made this drawing of what I would ultimately like to happen:
This architecture allows for a very trivial plugin system (at least in the QWidgets world) and I would very much like to keep that. Not a massive singleton and account for every possible action...
I'd appreciate ideas!
I think this is actually very easy, if you return a QQuickItem from the C++ side. If you do so you can create it with a specific context, in which you can set your "specific hardware controller" as a property
QQmlComponent *qml_controller = new QQmlComponent(qengine, "some_file.qml");
QQmlContext *context = new QQmlContext(); //should probably give a pointer to owning object
context->setContextProperty("controller", pointer_to_hw_cont);
return qml_controller->create(context);
The Loader setSource method have additional parameter you could pass to provide initial value for some property. Something like this:
ComboBox {
model: controlerFactory.specificHWListModel
onCurrentTextChanged: {
var specificHWControler = controlerFactory.getObjectFor( currentText );
loader1.setSource(
specificHWControler.qml_file,
{ "controler": specificHWControler }
);
}
}
Loader {
id: loader1
}
The specificHWListModel cold be QStringList or some custom QAbstractListModel.
And getObjectForcould be just a invokable function.
Q_INVOKABLE QObject* getObjectFor(QString hwName);
The object returned from Q_INVOKABLE function will be managed by QQmlEngine by default if you don't set by the QQmlEngine::setObjectOwnership. Remember to register your SpecificHWControler class to QQmlEngine.
The qml_file SpecificView.ui.qml, should have property controler, and could be edited with Designer:
import SpecificHWControlerModule 1.0
Item {
property SpecificHWControler controler
}
https://doc.qt.io/qtcreator/quick-connections-backend.html
In C++ model, I have QAbstractListModelderived class called Cart which contains QList<void*>container.
In QML, I show a list of objects. When user clicks on any of them, it should create that object in C++ and add it to the cart. It will also set some properties of that object.
My question is how do I really do that in the best way?
Here is the code how this will look like in C++ alone:
Cart * cart = new Cart; // we have this object already created
// which we have exposed to QML as 'qmlcart'.
// When user clicks apple
Apple * apple = new Apple(1.99) ; // 1.99 is price
apple->setType("Red Delicious");
cart.add( apple );
// When user clicks orange
Orange * orange = new Orange(0.99) // price
orange->setType("Valencia")
cart.add( orange );
The above is straight forward in C++ world but it gets somewhat complex if the click is in QML. What is the best way create and pass information about the object? Assume we have more attributes about the object other than price and type.
Does there has to be a corresponding slot function for every type of object we create? If the object has more complex properties, say 5 various attributes, do we pass all 5 of them to slot function or there is a better way? Should we use some of factory design pattern to create object?
One particular ability I am missing with QML in use is the freedom to work with C++ created object. For example if it was all C++, I could have created the C++ object and than set all its attributes no matter how many are there and than simply add the object to the container.
But with QML, even though I have all information about the object but I can't really create and configure the class and than pass to C++ because it has to be created in C++ which can't return it and so all configuration parameters will have to be passed to the slot function as well! Is there any better way?
Unless the C++ object happens to be a QObject derived type, registered as a type to QML, you can't do that directly. Even in QML, you either need a component or a QML source wrapper to create a C++ object, for example:
\\Object.qml
import Object 1.0
Object {}
So now you have a QML source wrapper to create the C++ Object dynamically. Or using a component:
Component {
id: component
Object {}
}
You can however, have a C++ slot or Q_INVOKABLE you can call from QML, and do the object creation there.
You can set object properties in QML as well, for example:
Object {
someProperty: someValue
}
or
component.createObject(parentObj, {"someProperty" : somevalue})
it will work similarly to a constructor - all properties will be set before the object is considered to be "competed" by the runtime.
From your question it looks like you are getting ahead of yourself - do a little more learning before you rush into using QML... and there is really no need for those void * - this is not C. Don't make your life any harder.
Using Qt 5.5.1 on iOS 9 I'm trying to assign a dynamically created QAbstractListModel to the model property of a ListView:
Window {
ListView {
model: api.model()
delegate: delegate
}
Component {
id: delegate
Text { text: "Test" }
}
}
api is a C++ object assigned to the QML context with setContextProperty. The model method is a Q_INVOKABLE which returns a QAbstractListModel *. This all works, my ListView is populated with data.
The problem is when I start scrolling. Usually after the second full scroll (to the bottom, back up to the top and down again) my ListView starts to clear itself out. The debugger is telling me the QAbstractListModel is being destroyed.
I don't want to set CppOwnership on the model. Is there another way to prevent the ListView from destroying its model?
QML seems kind of broken in this regard, I've experienced completely arbitrary deletions of objects still in use in multiple scenarios. Objects with parents and referenced by the JS engine are being deleted for no apparent reason while JS garbage still takes hundreds of megabytes of memory instead of being freed. This applies to both objects returned from C++ and objects created in QML. When an object is returned from a C++ function to QML, ownership is passed to the QML engine, which makes the object vulnerable to such arbitrary deletions.
The solution is to force CPP ownership and manage the object's lifetime manually - keep in mind destroy() won't work on such objects, so you have to use a C++ function from QML to delete it.
qmlEngine.setObjectOwnership(obj, QQmlEngine::CppOwnership);
Also, as BaCaRoZzo mentioned, exposing the model as a property to api might be the appropriate form. It depends on whether the function is just an accessor to an existing object or it creates the object itself.
At any rate, keep in mind that QML object lifetime management at this point cannot and should not be trusted.
Even though I've accepted ddriver's answer I've found a solution that seems to better match what I wanted.
By dynamically loading my components and storing the model as a variable, I'm able to get QML to keep my C++ models alive and to destroy them when required, for example:
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
Component.onDestruction: model.destroy()
}
Unfortunately the model.destroy() call seems to be required. I was expecting the garbage collector to pick this up, but it doesn't seem to.
I've only tested this is toy examples so far, caveat lector.
Just to say - I can confirm the same issue on both Linux x86_64 and Android ARMv7.
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
}
Seems to be enough if you don't mind the model being destroyed later in time.
I want to add something to ddriver's answer, which is more than a comment.
This same problem came up for me. basically, i wanted to create a dynamic list view model (QAbstractListModel, in fact). The usual way is to put your models up front in main (or somewhere) like this:
QQmlContext* ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
I have one model per object in this case, so i needed a dynamic solution.
I have a QObject which creates my model for a list. The model created derives from QAbstractListModel. The model is created and given out by my QObject host with a Q_INVOKABLE.
First problem is that the type of the model so generated is not known and must be registered. The usual qmlRegisterType does not work because QAbstractListModels cannot be copied. so you must register with qmlRegisterUncreatableType.
That's the first bit. Now the model works BUT who destroys it?
Turns out both my C++ code and QML both try to destroy the object since ownership was implicitly given to QML as part of the Q_INVOKABLE accessor.
BUT just letting QML clean up was bad. I tracked when this happened and it didn't happen at all in a timely manner. Basically it wouldn't clean up unless I did quite radial things like resize the window. presumably, it would eventually clean up (garbage etc.) but i really wanted these dynamic models to be cleaned up when their host QObject goes out.
So ddriver's idea is the way. but also remember to register with qmlRegisterUncreatableType.
eg,
inline MyModel* MyHostObject::getModel()
{
if (!_model)
{
_model = new MyModel(this);
// retain ownership of this object.
QQmlEngine::setObjectOwnership(_model, QQmlEngine::CppOwnership);
}
return _model;
}
I am new to QML and have a problem in accessing a property.property of a C++ object:
C++, frequency and station both are Qt metatype registered objects:
CStation *station = new CStation(...); // QObject
CFrequency *frequency = new CFrequency(..); // QObject
QQmlContext *qmlContext = viewer.rootContext();
qmlContext->setContextProperty("myatcstation", station);
qmlContext->setContextProperty("myfrequency", frequency);
QML:
RowLayout { ....
TextField {
text: myatcstation.toQString(true)
}
}
.... text: myfrequency.toQString(true)
This works, but when I write: text: myatcstation.frequency.toQString(true) I do get TypeError: Object [object Object] has no method 'toQString'
frequency is a property of class CStation set as Q_PROPERTY(CFrequency frequency READ getFrequency)
Crosscheck in C++ works:
CFrequency test = station->property("frequency").value<CFrequency>();
-- Edit: Frank's answer --
Both classes are derived from QObject, and it is not as per textbook as they are made copyable. I am aware of the Identity vs value situation.
Basically frequency is a value object, but I have made it QObject based so I am able to use properties with it (see Any chance to use non QObject classes with QML ). In the example, toString is Q_INVOKABLE, frequency in the non-working case returns a copy of a QObject derived CFrequency object.
-- Edit: Further findings --
When I change the frequency property to return CFrequency* instead of CFrequency it does not work either. As I get TypeError: Cannot call method 'toQString' of undefined the situation seems to be the same. CFrequency alone works, but QML does not understand that myatcstation.frequency is a frequency object which has toString.
CFrequency isn't a QObject I assume, otherwise you wouldn't return it by value but by pointer. To make `toQString() callable from QML, it must be either Q_INVOKABLE or a slot, which means that CFrequency must be a QObject as well.
If a station has only one frequency, consider moving the relevant information into the station object, i.e. add the frequency information you need as properties to CStation.
To get updates when the frequency changes, consider to use a property such as Q_PROPERTY(QString frequencyAsString READ frequencyAsString NOTIFY frequencyAsStringChanged) instead of toQString(). Properties have the update mechanism "built-in" via property bindings, while there's no good way to tell QML that it should call toQString again because the frequency changed.
I solved a similar problem:
class TopObject : public QObject
{
Q_OBJECT
Q_PROPERTY(ValueObject* theValue ...
...
}
class ValueObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString value ...
...
}
In the main application:
qRegisterMetaType<ValueObject>("ValueObject");
qmlRegisterType<ValueObject>("com.name.comp", 1, 0, "ValueObject");
...->setContextProperty("topObject", new TopObject());
And in the qml code:
import com.name.comp 1.0
... {
text: topObject.theValue.value
...
It needed both, returning the property (ValueObject) as pointer and registering it with qmlRegisterType.
See also Exchange Data and Objects between C++ and QML and vice versa