How the heck do I have QML instantiate an instance of a C++ class?
By: Brad
Hey Brad I have a model class that I need different views to have different instances of, how the heck do I do that?
In my previous post How to use Qt’s QSortFilterProxyModel I mentioned that one could instantiate an instance of a C++ class in your main.cpp then register it as a context using the setContextProperty() method. This is one way but what if your situation is that you have 10 or 20 different views all requiring their own instance, do you really want to instantiate 20 classes and set them all as global context?
Use Case
Lets say you have a menu which has a number of sub menus. The sub menu entries are dynamic per business logic so you don’t just hard code it in the QML script but rather populate it via a model. The ViewModel that you use to populate your sub menus is a single list where each entry is a sub menu. Each sub menu entry has two roles: DisplayRole and ParentMenuRole. The DisplayRole defines the text to show for that sub menu item and the ParentMenuRole defines what main menu item this sub menu item should be filed under.
The main menu view will be hard coded in the QML script but the sub menu views will be populated by the ViewModel where each sub menu view will have its own ViewModel allowing it to filter out items whose ParentMenuRole does not match that views parent menu.
Solution: Step 1
So per the above diagram we’ll have two sub menu views one will show only the entries which have their ParentMenuRole set to Home and the other view will show only the entries which have their ParentMenuRole set to Edit. To do this we’ll use the same technique used in my tutorial How to use Qt’s QSortFilterProxyModel, where we will extend the QSortFilterProxyModel class and override the filterAcceptsRow()
method so that we can implement our own custom filter logic. Except in this case our filter logic is to accept any entries who’s ParentMenuRole matches the parent menu we’re interested in. The parent menu that the proxy model should accept will be defined in a member variable called m_subMenu
which will be set by the view since it will know who its parent is. Lets call our custom QSortFilterProxyModel SubMenuProxyModel.
#include "SubMenuProxyModel.h" bool SubMenuProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const { bool ret( false ); if ( this->sourceModel() != nullptr ) { auto index = this->sourceModel()->index( source_row, 0, source_parent ); if ( index.isValid() ) { auto valueRole = index.data( SubMenuViewModel::SubMenuViewModel_Roles::SubMenuViewModel_Roles_ParentMenu ); if ( valueRole.isValid() ) { bool ok( false ); auto value = valueRole.toInt( &ok ); if ( ok ) { if ( SubMenuViewModel::SubMenuViewModel_SubMenuType( value ) == m_subMenu ) { ret = true; } } } } } return ret; }
Now if each sub menu view uses one of these SubMenuProxyModel’s as their model then we can show a list of sub menu entries for a given main menu item. Of course this goes back to the original question how do I instantiate each one of these SubMenuProxyModel’s in the first place and make them accessible to the sub menu views?
In this use case we could go the setContextProperty()
root as there are only two but that just feels wrong and won’t scale very well.
Solution: Step 2 – Enter the qmlRegisterType()
method
qmlRegisterType
is a template function used to register C++ types with the QML engine. It will register the type within the library imported from uri which has the version number composed from versionMajor and versionMinor.
#include <QtQml> template<typename T> int qmlRegisterType( const char* uri, int versionMajor, int versionMinor, const char* qmlName );
This means if a QML script imports library uri with version versionMajor.versionMinor when the QML parser hits the keyword qmlName it will view it as the C++ type provided in the template.
We can use this method to register our C++ type with the QML engine so that we can instantiate a new instance of the SubMenuProxyModel within a QML script.
In our main.cpp include the QtQml header then add a call to qmlRegisterType
to register our SubMenuProxyModel under version 1.0 of our proxymodels tutorial library.
#include <QtQml> qmlRegisterType<SubMenuProxyModel>("ca.imaginativethinking.tutorial.proxymodels", 1, 0, "SubMenuProxyModel");
Now we can reference this C++ class type with the keyword SubMenuProxyModel
(we could use any keyword here we want but for convenience lets just use the class name) from within a QML script.
In our main.qml script we can import our library then instantiate our C++ class just like it was any other QML component. Assuming we exposed a setter method as a slot so that we can set the m_subMenu
member variable from the QML script side we can create two instances of the SubMenuProxyModel
, one for each sub menu list view, and tell each one a unique sub menu type to filter on.
import ca.imaginativethinking.tutorial.proxymodels 1.0 SubMenuProxyModel { id: homeMenuModel Component.onCompleted: { // SubMenuTypes is an Enum we exposed in our main.cpp file using // the qRegisterMetaType<type>() template function (see sample code in the // tutorial app you can download via the link at the end of the tutorial) homeMenuModel.setFilter( SubMenuTypes.SubMenuType_Home ) } } SubMenuProxyModel { id: editMenuModel Component.onCompleted: { editMenuModel.setFilter( SubMenuTypes.SubMenuType_Edit ) } }
If we use these two models in each of the ListViews we’ll get a nice multiple tiered navigation menu were we can dyanmically choose which sub menu items a user can see via logic in the backend.
So there you have it that is how you can instantiate an instance of a C++ class right in the QML script.
You can download a sample application which illustrate the above here:
qmlRegisterType Sample App
Thank you I hope you have enjoyed and found this tutorial helpful. Feel free to leave any comments or questions you might have below and I’ll try to answer them as time permits.
Until next time think imaginatively and design creatively