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.
Related
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
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).
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;
}
Is it possible to load an .ui class generated by uic, dynamically by class name? I need to decide which UI class to load dynamically. I do not have that information at compile time. I do not want to use QUiLoader. Instead, I want to combine the direct approach here with QMetaType object instantiation by string.
I.e.
add the UI file to FORMS in project file,
declare the UI classes for QMetaType usage Q_DECLARE_METATYPE(Ui::planar_break) or Q_DECLARE_METATYPE(Ui_planar_break)
then form a string class name dynamically depending on user action: "Ui::planar_break" or "Ui_planar_break"
and invoke the below function to get the UI widget pointer for usage?
QWidget* initiateClassByName(QString name){
int id = QMetaType::type(name.toLatin1());
QWidget* widget=nullptr;
if (id != QMetaType::UnknownType) {
widget=static_cast< QWidget* > (QMetaType::create(id));
//QMetaType::destroy(id, myClassPtr);
//myClassPtr = 0;
}
return widget;
}
I am trying to increase the performance, compared to loading a dozen or so UI files (stored in Qt resource files) dynamically every time a specific dialog is instantiated. When I do this I seem to get a QMetaType::UnknownType every time. Ideas? Thanks.
(Hm, not sure why my function wasn't showing as code block here, until I made it a quotation.)
uic creates C++ code. If you really want to dynamically create a widget/dialog out of an xml file at runtime, you need to use the Qt Ui Tools. The class QUiLoader might be what you are looking for. If you do this, you can query the created QWidget through QWidget::findChild You can interact with the UI items through QObject::findChild(), provided you give your widgets distinct and meaningful object names.
Essentially, based on discussion had in #qt irc channel on Freenode I think what I am asking is actually not feasible.
My understanding is that even if I could get the C++ headers compiled and the classes registered using perhaps also qRegisterMetaType(), and perhaps even get them instantiated using QMetaType, to actually get the UI built I would have to call setupUi() on the instance.
Since UI files do not implement a common interface that includes setupUi, and I don't know compile-time which class I am instantiating, calling setupUi becomes impossible.
I want to extend the example called "Object ListModel Example" from Qt documentation
(you can get it on http://qt-project.org/doc/qt-4.8/declarative-modelviews-objectlistmodel.html).
I am trying to add a simple GUI functionality: a menu item that changes the content
(i.e. name) of the first data item in the model. Something like this:
MenuItem {
text: "Item 123"
onClicked: {
myModel.setProperty(0,"name","Item 123") //this gives me error
}
}
I am able to create a menu in QML but I cannot find the correct way to make changes in the model.
Btw, what is a difference between setContextProperty and qmlRegisterType (only the first one is used in this example but many other examples include the second one).
That kind of model is really not suitable for modification. There is no way for the view to be notified of changes. A better option is to use a QAbstractItemModel: http://qt-project.org/doc/qt-4.8/declarative-modelviews-abstractitemmodel.html
A simpler way to use a QAbstractItemModel is via QStandardItemModel: http://qt-project.org/doc/qt-4.8/qstandarditemmodel.html
setContextProperty() adds a single named property to the context. qmlRegisterType() registers a QObject-derived type with the QML engine, allowing it to instantiate that type. For example, the QDeclarativeItem type is registered with the engine as "Item", which is how the engine knows what to create when Item {} appears in QML code.