QAbstractTableModel - checkbox not showing checked
-
I have a set of QAbstractTableModels that display data from database tables which all have a checkbox in the first column (indicating which rows have been selected). This code has worked flawlessly now for over a year - until very recently.
Now the code still runs (without any errors) and from debugging, the checkboxes in the appropriate rows respond as if they were checked, but no actual check is visible in their checkboxes. If I click on one of the selected rows, the check suddenly appears in the checkbox but the row doesn't initiate any update or such. From everything I can determine, the checkboxes are correctly checked - they simply are not displaying correctly.
There have been no coding changes at all (I've even restored code from backups to verify) - but there has been environment updates. These updates include version 12.3.1 on my Mac as well as various Python package updates (PySide6 6.3, Pandas 1.4.2, etc).
The really interesting part is if I run the exact same code in my Fedora 34 VM, everything responds as normal and the checks appear in the appropriate checkboxes (without requiring any additional clicks). Its only on my Mac that I am having this issue.
I have tried to load previous versions of the Python Packages, but so far no change in behavior. I have experimented with updating the code to force the "refresh" of the QTableView and/or the QAbstractTableModel by editing a dataChanged event - but no change in behavior at all. At this point, I'm stumped as to how to get the UI back to a working condition.
Thanks,
TB -
@Todd-Banister
Hi. This sounds like something which you could reproduce in 20-odd lines of standalone code. You could also try it with just, say, aQStandardItemModel
or even aQTableWidget
? That's what I would try.Then post the code, a screenshot of how it doesn't show right, and a precise description of MacOS version and Qt version. (My thought would be it should not be related to Python/Pandas/PySide.)
I am not a Mac user, but plenty people here are and they might see how it looks for them/advise.
-
I continued to look into the issue today and found that my Mac and my Fedora VM actually had some different versions of some packages.
I used pyenv to create a clean version of Python 3.10.4 and installed all the same Python packages that are in my Fedora VM (which isn't having the issue). The issue went away.
I then started upgrading the packages one by one and testing. I left PySide6 as the last one to test. I then started upgrading PySide6 to each individual higher version and testing. I found that if I used PySide6 version 6.2.3 the code worked as expected. Once I upgraded PySide6 to version 6.2.4 - the checkboxes suddenly stopped showing the checks. I downgraded back to 6.2.3 and the issue went away again.
So what changed in PySide6 from 6.2.3 to 6.2.4?
-
@Todd-Banister
Like I suggested, have you considered that the change might be in the Qt version and not in the PySide version? That would be my guess, and where I would start looking for changes. Of course if you had C++ you could verify that the change is in Qt and not in PySide, from 6.2.3 to 6.2.4. https://code.qt.io/cgit/qt/qtreleasenotes.git/about/qt/6.2.4/release-note.md -
Just to complement @JonB answer, you can check the 6.2.4 changes here: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/doc/changelogs/changes-6.2.4 if none of those things sound like could generate your problem, it could be a Qt problem.
PS: If you want to use PySide 6.3.0 please uninstall your current installation and install it again. The change in the package structure has an issue when upgrading from 6.2.x -
@Todd-Banister I have the same problem on my Mac! I checked that the value saved in the Qt.CheckStateRole was correct, but the QTableView only rendered everything as Qt.Unchecked. What's even worse in my case is this issue made my setData() only worked for checking the unchecked item while couldn't uncheck those items actually have value of Qt.Checked. See below screenshot, when I click the checkbox of the second row, the value of "included" column will be changed into "Yes", but the checkbox remained unchecked; I click the checkbox in row[0] and row[2], nothing happen.
my model code is:class pandasModel(QtCore.QAbstractTableModel): def __init__(self, data:pd.DataFrame): super(pandasModel, self).__init__() self._data = data def rowCount(self, index): return self._data.shape[0] def columnCount(self, index): return self._data.shape[1] def data(self, index, role): row = index.row() column = index.column() value = self._data.iloc[row, column] if role == QtCore.Qt.DisplayRole: if value == True: return "Yes" elif value == False: return "No" else: return str(value) if role == QtCore.Qt.CheckStateRole and column == 2: if value == True: return QtCore.Qt.Checked else: return QtCore.Qt.Unchecked def setData(self, index, value, role): row = index.row() column = index.column() if role == QtCore.Qt.CheckStateRole and column == 2: if value == QtCore.Qt.Checked: self._data.iloc[row, column] = True self.dataChanged.emit(index, index) elif value == QtCore.Qt.Unchecked: self._data.iloc[row, column] = False self.dataChanged.emit(index, index) return True return False def flags(self, index): flags = super().flags(index) if index.column() == 2: flags |= QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable return flags def headerData(self, section, orientation, role): # section is the index of the column/row. if role == QtCore.Qt.DisplayRole: if orientation == QtCore.Qt.Horizontal: return str(self._data.columns[section]) elif orientation == QtCore.Qt.Vertical: return str(self._data.index[section])
I haven't try rewriting the delegate, maybe there was something wrong with the default delegate of this version of the QTableView. BTW, I'm using excatly the same versions of PySide6(6.3).
-
Maybe you can use the "QStandardItem" to store the checkstate:
def __init__(self, data:pd.DataFrame): super(pandasModel, self).__init__() self._data = data #adding the checkstate. In your model:column 2 self.col = 2 col = self.col for row in range(len(self._data.iloc[:,col])): value = self._data.iloc[row,col] item = QStandardItem(value) item.setCheckable(True) item.setCheckState(QtCore.Qt.Checked if value else QtCore.Qt.unChecked self._data.iloc[row,col] = item
for func data:
... if role == QtCore.Qt.DisplayRole: if column == self.col: value1 = value.text() value = "Yes" if value1 == 'True' else "No" return str(value) ... if role == QtCore.Qt.CheckStateRole: if column == self.col: if self._data.iloc[index.row(),index.column()].checkState() == Qt.Checked: return QtCore.Qt.Checked else: return QtCore.Qt.Unchecked ... if role == Qt.ItemDataRole.EditRole: if column == self.col: value = "Yes" if value.text() == 'True' else "No" return str(value)
You can modify "setData" according to func "data"
You may modify func 'flags' as following:
def flags(self, index): flags = super().flags(index) if index.column() == self.col: flags |= QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable | ItemIsEditable return flags
-
For future readers, this issue was addressed here https://bugreports.qt.io/browse/PYSIDE-1930 and will be fixed in 6.3.2