Reputation: 231
I am implementing a QAbstractListModel in class in python for use in QML. I've defined two custom roles in the model. In the model I've also implemented a Slot function 'get' to return data from a specified index and role. When I pass the role back to the 'get' function, I receive an integer different than what I defined for the role in my model.
I've tried passing the roles and index back from QML to a 'get' function defined in my Model. The index works as expected, but the roles return values different than what I've defined in my model.
gui.py
main()
def main():
# create the application instance
app = QApplication(sys.argv)
# create a QML engine
engine = PoaGUI()
# instantiate Route class
# route = Route()
# add example routes for routemodel
routelist = [
{'stringinput' : 'Route 1', 'selected' : True},
{'stringinput' : 'Route 2', 'selected' : False},
{'stringinput' : 'Route 3', 'selected' : True},
{'stringinput' : 'Route 4', 'selected' : False}
]
# instantiate ListModel class
routemodel = ListModel(routelist)
# register the python type bindings to QML engine
engine.rootContext().setContextProperty('RouteModel', routemodel)
# load main QML file and start app engine
engine.load('view.qml')
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
QAbstractListModel
class ListModel(QAbstractListModel):
# create new user roles as class variables
StringInput = Qt.UserRole + 0
Selected = Qt.UserRole + 1
# ADD USERROLE FOR SELECTED TO COMPLETE
def __init__(self, datain=[], parent=None):
super().__init__(parent)
self._list = datain
def rowCount(self, parent=QModelIndex()):
return len(self._list)
def data(self, index=QModelIndex, role=Qt.DisplayRole):
#store QModelIndex passed to data
row = index.row()
if index.isValid() and role == self.StringInput:
return self._list[row]['stringinput']
if index.isValid() and role == self.Selected:
return self._list[row]['selected']
else:
return None
def roleNames(self):
return {
self.StringInput: b'stringinput',
self.Selected: b'selected'
}
@Slot(int, int, result='QVariant')
def get(self, row, role):
# show difference between role supplied
# from QML vs definition in model
print(role)
print('Selected: ' + str(self.Selected))
print('StringInput: ' + str(self.StringInput))
if role == self.StringInput:
print('stringinput')
return self._list[row]['stringinput']
elif role == self.Selected:
print('selected')
return self._list[row]['selected']
else:
return None
@Slot(int, bool)
def modSelected(self, row, selval):
# set index of the row changes
ix = self.index(row, 0)
self._list[row]['selected'] = selval
# communicate that changes were made
self.dataChanged.emit(ix, ix, self.roleNames())
view.qml
ListView Implementation
CustomComp.CustomList {
id: routelist
model: RouteModel
keyNavigationWraps: true
listwidth: 300
listheight: 600
delegate: CustomComp.SingleListDelegate {}
Layout.alignment: Qt.AlignCenter
}
Delegate for ListView
import QtQuick 2.12
import QtQuick.Controls 2.12
Rectangle {
id: singleItemList
property int delegateIndex: index
property bool altcolor: false
property string altcolorcode: "#242526"
property string highlightcolorcode: "#242526"
width: parent.width; height: 20
color: (singleItemList.altcolor) ? ((index % 2 == 0) ? altcolorcode:"#000000") : "#000000"
border.color: "#ffcc00"
Text {
id: listText
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 14
color: "#ffcc00"
// delegate directly uses provided string
text: stringinput
}
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: {
singleItemList.ListView.view.currentIndex = index
console.log(singleItemList.ListView.view.currentIndex)
singleItemList.delegateSelect()
}
}
function delegateSelect() {
if (singleItemList.ListView.view.model.get(index, selected)) {
singleItemList.color = "#000000"
singleItemList.ListView.view.model.modSelected(index, false)
// singleItemList.ListView.view.model.set(index, {"selected": false})
}
else {
singleItemList.color = highlightcolorcode
singleItemList.ListView.view.model.modSelected(index, true)
// singleItemList.ListView.view.model.set(index, {"selected": true})
}
// console.log(singleItemList.ListView.view.model.get(index, selected))
}
}
Having two custom roles, when I pass a role back to my model from QML, I receive integers on the order of 0 and 1. I used Qt.UserRole to define my role numbers for the model I generated in Python. Therefore the integers for those roles as defined in my model are 256 and 257. QML seems to handle the model fine because when I provide my model to a ListView and reference one of my custom roles in the delegate to display as text, the list populates as expected. Why are the integers for my two custom roles different in Python than they are in QML? That being the case, how can I successfully return those roles for use by another function in my model?
My ultimate goal is to create functions analogous to 'get' and 'set' in a QML ListView, but do so for a model I've built in Python.
Upvotes: 3
Views: 1650
Reputation: 244212
You have an XY problem, your real goal is to edit the model values from QML. So the solution is to implement the setData()
method and edit the data with selected = foo_value
, in your particular case: selected != selected
You are misunderstanding the concept of "selected", "selected" in QML is the value of the model associated with the Selected role, that is what it means:
def roleNames(self):
return {
ListModel.StringInput: b"stringinput",
ListModel.Selected: b"selected",
}
That is, selected in QML is not the role but the value associated with the role and the index, that is, it is equivalent to:
selected = model.data(index, ListModel.Selected)
Therefore you get 1 and 0 which are the conversion of booleans true and false, respectively.
This is done to make an abstraction of the roles.
So you understand when you use the following:
QML code Python Code
foo_value = selected ~ foo_value = model.data(index, ListModel.Selected)
But when you use:
QML code Python Code
selected = foo_value ~ model.setData(index, foo_value, ListModel.Selected)
So in your case the solution is:
class ListModel(QAbstractListModel):
StringInput = Qt.UserRole + 0
Selected = Qt.UserRole + 1
def __init__(self, datain=[], parent=None):
super().__init__(parent)
self._list = datain
def rowCount(self, parent=QModelIndex()):
return len(self._list)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return QVariant()
row = index.row()
if 0 <= row < self.rowCount():
if role == ListModel.StringInput:
return self._list[row]["stringinput"]
elif role == ListModel.Selected:
return self._list[row]["selected"]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
row = index.row()
result = False
if 0 <= row < self.rowCount():
if role == ListModel.StringInput:
self._list[row]["stringinput"] = value
result = True
elif role == ListModel.Selected:
self._list[row]["selected"] = value
result = True
if result:
self.dataChanged.emit(index, index, (role,))
return result
def roleNames(self):
return {
ListModel.StringInput: b"stringinput",
ListModel.Selected: b"selected",
}
SingleListDelegate.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
Rectangle {
id: singleItemList
property int delegateIndex: index
property color highlightcolor: "#242526"
property color defaultcolor : "#000000"
width: parent.width; height: 20
color: selected ? highlightcolor : defaultcolor
border.color: "#ffcc00"
Text {
id: listText
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 14
color: "#ffcc00"
text: stringinput
}
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: {
singleItemList.ListView.view.currentIndex = index
console.log(singleItemList.ListView.view.currentIndex)
// invert the boolean
selected = !selected
}
}
}
Upvotes: 2