Understanding QAbstractListModel in python/QML program and C++ equivalent



  • Hi everyone !

    I have to change a QML human machine interface from python to C++. But I'm new to python AND qml so I'm struggling as the things are getting tough.

    I'll ask my questions before showing the code :

    there's a python class derived from QQAbstractListModel that looks like containing some QtQuick Items. (code below) It's PresetModel. First, I kind of understand the setPresetBase method but... that way to put everything in a table like it's nothing... how can I convert it into C++ ? I guess parameters is a QList ? So I could store my items as QVariant in a QList ? If so, then what ?

    How can I share such a thing with QML ? I see that they just put it in setContextProperty. I tried to do a simple one and do the same, but I couldn't get the data(index) from my QAbstractListModel, or call a method called apply...

    i see that they defined apply as a pyqt slot. What is the equivalent in C++ ? I tried to define it as a public slot : but it changed nothing...
    I'm lost.
    Could you explain me how to create and use a QAbstractListModel such as this on but in C++ ?

    Thanks in advance. Here is the code (not everything, just what I think being useful)

    class PresetModel( QtCore.QAbstractListModel ):
        NameRole = QtCore.Qt.UserRole + 1
    
        _roles = {
            NameRole: b"name",
            }
    
        def __init__( self, view, parent=None ):
            super().__init__( parent )
            self._preset_list = []
            self._preset_base = None
            self.view = view
            self._name_id = 0
    
        def rowCount( self, parent=QtCore.QModelIndex() ):
            return len( self._preset_list )
    
        def data( self, index, role=QtCore.Qt.DisplayRole ):
            try:
                preset_item = self._preset_list[index.row()]
            except IndexError:
                return QVariant()
            if role == self.NameRole:
                return preset_item.name
            return QVariant()
    
        def roleNames( self ):
            return self._roles
    
        def setPresetBase( self ):
            object = self.view.rootObject()
            items = object.childItems()
            parameters = []
            for i in items:
                if type(i) == QtQuick.QQuickItem and i.objectName() != "" and i.objectName() != "EditPanel" and i.objectName() != "EditGrid":
                    parameters.append( [ i.objectName(), i.x(), i.y(), i.z(), i.width(), i.height(), i.isVisible() ] )
            self._preset_base = Preset( "base", parameters )
    
        @QtCore.pyqtSlot( int )
        def removePreset( self, index ):
            if len( self._preset_list ) > 0:
                preset = self._preset_list[ index ]
                self.beginRemoveRows( QtCore.QModelIndex(), index, index )
                self._preset_list.remove( preset )
                self.endRemoveRows()
                self.savePresetsToFile()
    
        @QtCore.pyqtSlot( int )
        def applyPreset( self, index ):
            if index != -1 and len( self._preset_list ) > index:
                preset = self._preset_list[index]
            else:
                preset = self._preset_base
            for i in preset.config:
                item = self.view.rootObject().findChild( QtQuick.QQuickItem, i[0] )
                if item != None:
                    item.setProperty( "x", i[1] )
                    item.setProperty( "y", i[2] )
                    item.setProperty( "z", i[3] )
                    item.setProperty( "width", i[4] )
                    item.setProperty( "height", i[5] )
                    item.setProperty( "visible", i[6] )
    

    The class is instanciated like this

    self.preset_model = preset_model.PresetModel( view )
            self.view.rootContext().setContextProperty( 'presetModel', self.preset_model )
    
        @QtCore.pyqtSlot( QtCore.QVariant )
        def onStatusViewChanged( self, status ):
            if status == QtQuick.QQuickView.Ready:
                self.preset_model.setPresetBase()
                self.preset_model.loadPresetsFromFile()
    


  • @Hodenbrecher said in Understanding QAbstractListModel in python/QML program and C++ equivalent:

    they defined apply as a pyqt slot. What is the equivalent in C++ ? I tried to define it as a public slot : but it changed nothing...

    Not that I'm proficient with Python (yet...) but please remember that a slot (in C++, QML, Python, whatever) is meant to be called/activated by a signal being emitted...
    That said, you may need to check the whole Python code for "connect" entries like these:

    buttonX.clicked.connect(buttonX_clicked())
    

    or

    QObject.connect(buttonY, SIGNAL("clicked()"), buttonY_clicked)
    


  • @Hodenbrecher

    . that way to put everything in a table like it's nothing... how can I convert it into C++ ? I guess parameters is a QList ? So I could store my items as QVariant in a QList ? If so, then what ?

    You could create a custom data type to store all variables of QQuickItem, then define a list of this custom data type

    // definition
    typedef struct ObjectDataType{
        QString objectName;
        qreal x, y, z, width, height;
        bool visible;
    } ObjectDataType;
    
    // usage
    QList<ObjectDataType> parameters;
    parameters.append({objName, xValue, yValue, zValue, wValue, hValue, vBoolean});
    

    How can I share such a thing with QML ? I see that they just put it in setContextProperty. I tried to do a simple one and do the same, but I couldn't get the data(index) from my QAbstractListModel, or call a method called apply...

    Are you passing the correct parameters ? Take a look at data function signature.

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const

    Can you share a snippet of your code to understand how you are trying to access the data from QML ?


    i see that they defined apply as a pyqt slot. What is the equivalent in C++ ? I tried to define it as a public slot : but it changed nothing...

    Have you defined Q_OBJECT macro on your class definition ? It is necessary to allow the Signal and Slot feature on c++

    https://doc.qt.io/qt-5/qobject.html#Q_OBJECT



  • @KillerSmath Thanks for your answer

    The struct is a good idea. What I started to do is a QVector<QVariant>. So I could use it like this:

    //CTOR of the dataModel I will store in the QList
    Preset(QString& air_name, QVector<QVariant>& air_config);
    
    //definition of the QList in PresetModel
    QList<Preset> m_preset_list
    

    what do you think about ? Is the struct more efficient or convenient ?


    i'm using the right definitio, copied it fro the doc.
    Here the use of the class. It's used to personalize the interface by moving the items into the main window.

    GridView {
        id: container
        width: 300; height: 200
        clip: true
    //    [...]
        onCurrentIndexChanged: presetModel.applyPreset( currentIndex )
    //    [...]
        model: presetModel
        delegate: delegatePreset
        highlight: highlight
        remove: Transition {
            ParallelAnimation {
                NumberAnimation { property: "scale"; to: 0; duration: 500 }
                NumberAnimation { properties: "x,y"; to: 100; duration: 500 }
            }
        }
        currentIndex: -1
    }
    

    I have to say that I don't understand it yet... I just know that I have to create PresetModel. Else I can't launch the QML application.
    What is the model, delegate... I don't know if I need to understand it to write the C++

    So I think that I have to use

    PresetModel* johnnyBoy = new PresetModel(myView);
    QQmlContext::setContextProperty("presetModel", johnnyBoy);
    

    but heard about passing it as a QVariant containing the QList... Maybe I missunderstood


    Yes, everything that needs to be defined as a Q_OBJECT is.
    What I am not sure of is if I have to define apply as a

    public slot: 
    

    or as a

    Q_INVOKABLE
    

    ?


    Subsidiary question
    what is this doing and how to write it in C++ ? I kind of understand the role of the role but this is too much for me and the doc is not very helpful

    class PresetModel( QtCore.QAbstractListModel ):
        NameRole = QtCore.Qt.UserRole + 1      #+1 ??? The docs says it's value is 0x011
    
        _roles = {                             #so I guess its an int...
            NameRole: b"name",                 #b"name" ????
            }
    #[...]
        def data( self, index, role=QtCore.Qt.DisplayRole ):
              try:
                  preset_item = self._preset_list[index.row()]#ok
              except IndexError:
                  return QVariant()
              if role == self.NameRole:#not ok
                  return preset_item.name     #Probably used to find the QQuickItem linked later
              return QVariant()
    

    Maybe I should create a new topic specific to this ?

    Thanks a lot !



    [edit] here is my code for data

    QVariant PresetModel::data(const QModelIndex & air_index, int ai_role) const
    {
    	if (air_index.row() < 0 || air_index.row() >= m_preset_list.size())
    		return QVariant();
    	const Preset& wr_preset_item = m_preset_list[air_index.row()];//definition : QList<Preset> m_preset_list;
    	if (ai_role == m_nameRole)
    		return wr_preset_item.getName();//in CTOR : m_nameRole = Qt::UserRole + 1;
    	return QVariant();
    }
    


  • @Hodenbrecher
    Your Python to C++ translation looks broadly correct. However, a couple of comments, if you want to understand/improve:

    • NameRole = QtCore.Qt.UserRole + 1: the + 1 just ensures it's unique. I can't recall, but somewhere in the docs something talks about starting your own from QtCore.Qt.UserRole + 1 instead of from QtCore.Qt.UserRole. The value will actually be 0x0101 rather than what you wrote. An int should be fine (Qt::ItemDataRole is actually an enum, but I don't think you can extend its values in C++.)

    • b"name": Python "b string". Byte literal, producing bytes type rather than str type. Whatever that NameRole is used for is it perhaps supposed to be a perhaps a ByteArray rather than a QString? Alternatively, if your original is Python 2 not 3 it might have something to do with Python char type vs Qt QString/C++ char [], I don't know, but I imagine you'll soon know if it's wrong.

    • //in CTOR : m_nameRole = Qt::UserRole + 1;: Note that in Python, anything inside a class but not inside a def --- your NameRole & _roles --- are not instance variables. They are class variables. Your choice of name m_nameRole and setting it in constructor are therefore not great. Your NameRole should therefore be something like a const int defined in the class declaration, and similarly with _roles = ... (can't do that in my C# but I think you can in C++), rather than anything in constructor.



  • @JonB Thank you very much, that was helpful.

    Note that in Python, anything inside a class but not inside a def --- your NameRole & _roles --- are not instance variables. They are class variables.

    I'm not sure to understand. Do you mean that NameRole and _roles should be static variables in my C++ ?


    Whatever that NameRole is used for is it perhaps supposed to be a perhaps a ByteArray

    That's logical. We set the name as a byte array and use the numerical values of the ASCII char to use it's value.
    But I still don't understand this :

    _roles = {
            NameRole: b"name",
            }
    

    if _roles is a byte array maybe the synthax to set it's value is using the { } but then... nameRole : b"name"... So all the PresetModel instances (probably only one thought) will have their _role set to the value corresponding to "name" ? That's weird, i think I don't get it

    Thank you very much for your help


    another subsidiary question

    @QtCore.pyqtSlot()
        def onPresetChanged( self ):
            row = 0
            for r in self._preset_list:
                if r == self.sender:
                    row = self._preset_list.index( r )#index is part of a QAbstractListItem. But i think _preset_list is a QList or a QVector. and r a Preset object
                    break
            self.dataChanged.emit( self.index(row, 0), self.index(row, 0), [self.NameRole] )
    

    in the for loop the code assigns to row ( an int probably) a _preset_list.index(r) which means that _preset_list is derived from QAbstractItemModel ? and then there's r in the parameters. r is an element from _preset_list, right ? A Preset object.



  • @Hodenbrecher said in Understanding QAbstractListModel in python/QML program and C++ equivalent:

    I'm not sure to understand. Do you mean that NameRole and _roles should be static variables in my C++ ?

    Yes. static, or better still const since you don't want to alter them after they are set. My C++ is a bit ropey, but you can have something like:

    class PresetModel
    {
        static const int s_nameRole = Qt::UserRole + 1;
    }
    

    The important thing is that this value is available outside/independent of any instance (via PresetModel::s_nameRole), and other code might require that.

    if _roles is a byte array

    It isn't. roles is (like) a C++ struct, hence the { ... }. It is the NameRole member which is the byte array. You can do something more nowadays in C++ with these, but in C this would be something like:

    struct {
        byteArray NameRole;
    } _roles = {
        "name"
    };
    

    Again because it's at the class level you'll want something like static struct ...: it's to be shared by all instances. This is adding some kind of "name role" to this class, in addition to the in-built Qt::ItemDataRole ones. I don't think all on its own the _roles is used/does anything: I am expecting you to find it referenced somewhere in code (via PresetModel._roles) to "declare" a new role. Doubtless it will have something to do with https://doc.qt.io/qt-5/qabstractitemmodel-obsolete.html#setRoleNames (now defunct) & https://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames, q.v..



  • @Hodenbrecher

    What I am not sure of is if I have to define apply as a
    public slot:
    or as a
    Q_INVOKABLE

    Slot and Invokable macros are used to different situation.

    • Slot is used to allow a function to receive signals.
    • Q_INVOKABLE allow the qml to directly call a function

    Have you seen the QAbstractItemModel Qt Example ? It will improve your understanding of how to forward a custom c++ model to QML.

    https://doc.qt.io/qt-5/qtquick-models-abstractitemmodel-example.html


Log in to reply