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)
-
. 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++ -
@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 apublic 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 helpfulclass 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 fromQtCore.Qt.UserRole + 1
instead of fromQtCore.Qt.UserRole
. The value will actually be0x0101
rather than what you wrote. Anint
should be fine (Qt::ItemDataRole
is actually anenum
, but I don't think you can extend its values in C++.) -
b"name"
: Python "b string". Byte literal, producingbytes
type rather thanstr
type. Whatever thatNameRole
is used for is it perhaps supposed to be a perhaps aByteArray
rather than aQString
? Alternatively, if your original is Python 2 not 3 it might have something to do with Python char type vs QtQString
/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 aclass
but not inside adef
--- yourNameRole
&_roles
--- are not instance variables. They are class variables. Your choice of namem_nameRole
and setting it in constructor are therefore not great. YourNameRole
should therefore be something like aconst 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 stillconst
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 theNameRole
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-builtQt::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 (viaPresetModel._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.. -
What I am not sure of is if I have to define apply as a
public slot:
or as a
Q_INVOKABLESlot 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