Custom C++ QML element with arguments
-
Hi,
for my ListView in QML i want to use a model which gets its data from a database. I had a solution similar to this:
MyModel my_model { database }; QQmlApplicationEngine engine {}; engine.rootContext()->setContextProperty("my_model", &my_model);
with
class MyModel : public QAbstractListModel { Q_OBJECT public: explicit MyModel(Database& my_database, QObject* parent = nullptr); private: Database& my_database; ...
Note that database is not default constructible and also not coypable. In my main function (or rather Application class) I create one instance and pass it down to other function / classes that need it.
It works this way, but I don't quite like it to have the model registered as a context property for these reasons:
- The Model is created at startup time and not when it is needed in the qml
- It is globally available in qml even though it should be used for a specific ListView. I can also not reuse it (or if I do, multiple ListViews use the same model, which might be problematic.
Instead I want to create the model in qml itself
ListView { model: MyModel {} }
But I don't how MyModel then gets the instance of the database. It seems (although not explicitly mentioned in the docs) that all qml types defined in C++ need to be default constructible.
My preferred solution would be to give Qt a factory function that tells QT how it should construct my Object. It seems this is possible for Singelton Objects, but not normal ones (https://doc.qt.io/qt-6/qqmlengine.html#qmlRegisterSingletonType).
The only solution I can think of is to make the database globally available by making it a singelton. But that's something I would like to avoid. I would like to have control which part can access which functionality.
I also had the idea (which is less ideal) to define a required property:
Q_PROPERTY(Database database MEMBER database REQUIRED)
and then make my database available as a context property and pass it explicitly via the property. But it seems like Qt won't automatically inject database in the constructor. This means my member variable database needs to be default constructible again, which it isn't.
I use the latest QT Version with the CMake API. Any help would be appreciated.
-
@Leon_2001 said in Custom C++ QML element with arguments:
ListView {
model: MyModel {}
}Hi. If I got it right, you have to declare QML_ELEMENT in your class declaration to make this class instanciatable in QML. I haven't reached the point where I instantiate my C++ classes from QML so please take it with a bit of caution, but I don't think I'm far from truth.
class MyModel : public QAbstractListModel { Q_OBJECT QML_ELEMENT public: explicit RecipeListModel(Database& my_database, QObject* parent = nullptr); private: Database& my_database; }
https://doc.qt.io/qt-6/qtqml-cppintegration-definetypes.html should be a good start, especially chapter "Registering an Instantiable Object Type"
-
@ankou29666 Yeah I know this stuff. I did it like this actually. But the problem is the non default constructor. Because QML doesn't know how to instantiate the object because of database parameter.
So I get an error message which is something like this: `MyModel cannot be instantiated because it doesn't have a default constructor" or something like this:
class MyModel : public QAbstractListModel { Q_OBJECT QML_ELEMENT public: explicit MyModel(QObject* parent = nullptr); }
This works completly fine. So the issue is really: How do I get my instance of the database without using Singeltons or global variables.
-
@Leon_2001 I just figure out that you would have to
- either instantiate your database object from within QML as well,
- or instantiate it from C++ then pass the database to QML with engine.rootContext()->setContextProperty("my_db", &database);
-
@ankou29666 said in Custom C++ QML element with arguments:
or instantiate it from C++ then pass the database to QML with engine.rootContext()->setContextProperty("my_db", &database);
this is basically what I was trying, but well it's not that easy. See the last part of my original question. Somehow it seems not to be possible to use a reference property with Q_PROPERTY or something like this.
I'm now trying to do it with a pointer instead. But well this seems all really complicated for such a simple thing.
-
@Leon_2001 maybe my example in this thread helps you:
https://forum.qt.io/topic/135166/displaying-a-c-class-via-qml-in-qtquick2/2?_=1679297980431Note: depending on your classes and whether you want to create them from QML instead of C++, you should take a look at the different register methods:
- qmlRegisterUncreatableType
- qmlRegisterType
- qRegisterMetaType
-
This has been a question of mine for over a year. I resorted to having a factory class that has methods to instantiate various view models (and pass in dependencies) and return the pointer to that view model. This factory would be exposed to QML via singleton or contextProperty at the start of my program. Any QML view can decide which view models it wants and doesn't deal with dependency injection.
Here is an example:
class ViewModelFactory : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON public: Q_INVOKABLE HomeViewModel* createHomeViewModel(QObject* parent = nullptr) { // Inject dependencies from ApplicationModels auto& appModels = ApplicationModels::instance(); return new HomeViewModel(appModels.smartHomeService(), appModels.userService(), parent); } };
Now, the above example makes ApplicationModels a singleton and passes it to the view model, but you could imagine a world where the factory can manage the dependencies without them being singletons.
It took a lot of thinking and I gathered this approach by looking at how QtWidgets does things: a very hierarchical object structure where the MainWindow class would contain everything within it and proctor instantiation, DI, and more.
QML just has access to what is publicly exposed by the ApplicationModel/factory, and no longer needs access to dependencies for ViewModels to use their desired view model.
Note: I have not tested this yet (my fear being that I do NOT know the impact of calling factory methods on init of a Qml component)