I have a subclass of
QAbstractListModel that is the model for a QML
ListView (using PySide6). Each row of the list has a checkbox in it, and when the user checks/unchecks a box, it updates a boolean in that row of the listmodel using my override of
setData(), which works as expected. I also have
Buttons that should select/clear all of the checkboxes in the list.
My model subclass provides the following method to select all, which can be called when the user hits the
Button or when other things happen in the application:
def select_all(self): for i in range(self.rowCount()): row = self._rows[i] row['selected'] = True # Emitting this for each row works... self.dataChanged.emit(self.index(i), self.index(i), ) # ... whereas emitting just one signal for ALL rows does NOT work # self.dataChanged.emit(self.index(0), self.index(self.rowCount()), )
As you can see from the comments, I need to emit
dataChanged for each row in order for the checkboxes in the
ListView to be updated. Emitting the signal once and using the
bottomRight parameters does not update the state of the checkboxes in the
ListView (but the model data is correctly updated).
According to the documentation, the
dataChanged signal provided by
QAbstractItemModel (and inherited by
QAbstractListModel) has this caveat:
If the items are of the same parent, the affected ones are those between topLeft and bottomRight inclusive. If the items do not have the same parent, the behavior is undefined.
It seems likely that I’m running into the scenario where the rows in my model do NOT have the same parent, and therefore, "the behavior is undefined." I suppose that makes sense, because I never do anything to establish a parent/child relationship for any of my rows. I also saw this answer which implies that Qt behaves differently when the
bottomRight indices are the same. So, I would like to understand this better, and I have a few questions:
- Am I correct that this parent concept is the reason this does work when emitted for each row and does not work for all rows?
- Is the concept of a parent/child relationship only meaningful for tree-like models that would extend
QAbstractListModel? Does it make any kind of sense for a list?
- If it does make sense for a list, then what would be the "parent" and what would be the "child?" How would I configure a subclass of
dataChangedcan be emitted once to update multiple rows?
While the base implementation of Qt item views just updates indiscriminately the view whenever the
bottomRight indexes doesn’t match (so you can just provide two "random", but still sibling indexes), the indexes must not only share a common parent (which for one and two dimensional models is always an invalid index), but also both valid.
With this line, the second index is not valid:
self.dataChanged.emit(self.index(0), self.index(self.rowCount()), )
This is because the indexes are always 0-based, and the index of the last row is actually
rowCount - 1. While they theoretically are siblings, since they share the same parent (the parent of a root index is invalid, as it is the parent of an invalid index), they are not both valid.
So, the correct syntax is:
self.dataChanged.emit(self.index(0), self.index(self.rowCount() - 1), ) ^^^^
Note that the
roles argument is optional and already defaults to an empty list (QVector in C++), so unless you actually want to specify the changed roles, you don’t need to provide that.
Answered By – musicamante
Answer Checked By – Willingham (BugsFixing Volunteer)