Listview delegate update from two sources
-
I have to create an application as some of my study-work.
I've never used Qt framework seriously before.
Part of this is an application which monitors some rfid readers.
As you can see in the image, I successfully read the nodes from a Yaml-file when the app starts and managed to display the List.However: I shall implement that the nodes can be checked/unchecked either by hitting the "Select All" /"Select None" buttons or by using the individual checkboxes.
When I do that, i can see that the listmodel and my datamodel is updated properly. But the listview is only updated once by the buttons.
The print-statement in my data-implementation indicates, that the "selected" - property is never passed to the Qml after a button is clicked.
I see this behaviour in every single listview/tableview of my application and really need to understand where my mistake is.
I couldn't find similar problemd which helped by google.
Can anybody help?I'm using Pyside 6.5.1 with QtQuick2.
snippet from my QML mainwindow:
import QtQuick 2.9 import QtQuick.Controls 2.5 import QtQuick.Controls.Material import QtQuick.Layouts 1.3 import QtQuick.Window 2.2 . . . ListView{ id: listView anchors.fill: parent model: rfidModel delegate: RfidDelegate{ width: contentRect.width idVal: model.idVal selected: model.selected tagTextA: model.tagId tagTextB: model.productID tagTextC: model.cupSize nameText: model.name readerIpAdress: model.ipAddr readerPort: model.ipPort endpointIpAdress: model.endPointipAddr endpointPort: model.endPointipPort endpointModbusAddress: model.endPointModbus locked: true } }
part of the delegate:
property bool selected: false CheckBox{ id: isSelected text: "selected" checked: selected onCheckedChanged: { selected = isSelected.checked rfidController.selectNode(idVal, selected) } }
From my QAbstractLisModel - class implementation i will post only the data and setData methods:
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any: """ Returns Data from viewmodel. :param index: Used to index into the model :type index: QModelIndex :param role: Used to index into the model :type role: int :return: returns data from viewmodel at given index and role """ if 0 <= index.row() < self.rowCount(): node = self.rfidData[index.row()] field = self.roleNames().get(role) #print("field: "+ str(field), "role: " + str(role)) if field: return getattr(node, field.decode()) def setData(self, index: QModelIndex, value: Any, role: int) -> bool: """ Writes data to an index and returns true if success :param index: Index at which data shall be changed :type index: QModelIndex :param value: New value to be written at index :type value: int for any ID, string for products names and bool for pallet existence :param role: Rolename to be written to :return: returns False if writing was not successful. Otherwise, it returns the old value. """ role = Qt. DisplayRole + role if index.row() >= self.rowCount() or not index.isValid(): return False roleNames = self.roleNames() field = roleNames.get(role) print(str(field)) if field: setattr(self.rfidData[index.row()], field.decode(), value) self.dataChanged.emit(index, index) return True print("field not found for role " + str(role)) return False
and finally from my controller class RfidController:
@Slot() def selectAll(self): """ Marks all RFID-Nodes as selected. """ nodes = [node.idVal for node in self.rfidViewModel.rfidData] for node in nodes: self.selectNode(node, True) @Slot() def selectNone(self): """ Marks all RFID-Nodes as selected. """ nodes = [node.idVal for node in self.rfidViewModel.rfidData] for node in nodes: self.selectNode(node, False) @Slot(int, bool) def selectNode(self, id: int, selected: bool): """ marks RFID-Node with id as selected. :returns: None """ rows = self.rfidViewModel.rowCount() for i in range(rows): print("i: " + str(i)) node = self.rfidViewModel.rfidData[i] if node.idVal == id: oldVal = node.selected index = self.rfidViewModel.index(i, 0) self.rfidViewModel.setData(index, selected, 13) newVal = node.selected print(f"Data changed from {oldVal} to {newVal} in index {index.row()}")
-
Hi,
I might be wrong be you are emitting dataChanged without any of the roles your delegate uses hence there's nothing for it to update. An empty roles means that all roles should be reloaded however, the ones you use for your delegate are likely unknown and thus not checked.
-
@SGaist
Thank you for your reply!
I already tried following:role = Qt. DisplayRole + role self.dataChanged.emit(index, index, role)
self.dataChanged.emit(index, index, roleNames.keys())
self.dataChanged.emit(index, index, [role])
I also tried some combinations of Qt.Userrole but in that case the list view won't load any data.
I'm asking myself if it is possible that the binding gets destroyed when the button is klicked. Because:
--> List loads fine
.--> Select All is clicked, ListView updates fine
--> Select None: model updates, Listview doesn't
--> Select All doesn't work either.Also: when the model is loaded with checked flags the behaviour is vice versa.
I read something in the internet which indicates that it may happen. But found not enough to get into the error. -
I found out, that the selected property had no function connected to the property changed.
I implemented this so that the checkbox.checked is set depending on the property.
property bool selected: false onSelectedChanged: { console.log("new value: "+selected + " field: " + isSelected.checked) isSelected.checked = selected console.log("after setting: checkbox.checked: = " + isSelected.checked) } CheckBox{ id: isSelected text: "selected" checked: false onCheckedChanged: { selected = isSelected.checked rfidController.selectNode(idVal, selected) } }
Now i found out: when the delegate falls out of the cacheBuffer and has to be reloaded, the value is shoewed correctly.
Interestingly, i can use the button "Select All" to mark all delegates without problems... but they would also only be showed unchecked if I scroll them out of the view.I set ListViews cacheBuffer to 0 which showed clearly this behaviour: everything gets updated when it's newly loaded except the first delegate.
Why is that? I never had an issue with updating model and ListView in C++.
-
I found the solution:
I replaced the delegate with a simple Text and it worked.
Then I rmeoved all functions in the original delegate which were related to the properties and assigned the properties directly to the TextFields, CheckBoxes etc.
Then it also worked but i got an binding-loop error.I resolved this error and now it works.
I learned my lesson this time.
-