Issue
I am trying to sort my QTableWidget columns by the values stored in the user role of each QTableWidgetItem, but I am unable to do so. I have enabled sorting with self.setSortingEnabled(True)
, and I have set the data in each QTableWidgetItem with item.setData(Qt.DisplayRole, f'M - {r}')
and item.setData(Qt.UserRole, r)
. However, when I try to sort the columns by the values stored in the user role, it sorts the columns by the values stored in the display role instead.
Here is a minimal working example of my code:
from random import randint
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget, QWidget, QGridLayout, \
QTableWidgetItem, QPushButton
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setSortingEnabled(True)
def populate(self):
self.clear()
self.setColumnCount(3)
self.setRowCount(200)
for row in range(500):
for column in range(3):
r = randint(0, 1000)
item = QTableWidgetItem()
item.setData(Qt.DisplayRole, f'M - {r}')
item.setData(Qt.UserRole, r)
self.setItem(row, column, item)
class MainApp(QMainWindow):
def __init__(self):
super().__init__()
self.table = Table()
self.button = QPushButton('Roll')
self.button.clicked.connect(self.table.populate)
layout = QWidget()
self.setCentralWidget(layout)
grid = QGridLayout()
layout.setLayout(grid)
grid.addWidget(self.button)
grid.addWidget(self.table)
if __name__ == '__main__':
app = QApplication([])
main_app = MainApp()
main_app.showMaximized()
app.exec()
Additionally, I tried using EditRole, but the values that appear in the table are not the values from DisplayRole. For example, in the code below, I set item.setData(Qt.DisplayRole, f'M - {r}'), but even though r is an integer, the display role value is a string ('M - {r}'). I was hoping that sorting by UserRole or EditRole would sort based on the integer value of r, but that doesn't seem to be the case.
item.setData(Qt.DisplayRole, f'M - {r}')
item.setData(Qt.EditRole, int(r))
Solution
Sorting is always based on Qt.DisplayRole
.
Trying to use the EditRole
is pointless, as the setData()
documentation points out:
Note: The default implementation treats
Qt::EditRole
andQt::DisplayRole
as referring to the same data.
The Qt.UserRole
is a custom role that could be used for anything (and containing any type) and by default is not used for anything in Qt. Setting a value with the UserRole
doesn't change the sorting, because Qt knows nothing about its existence or how the value should be used.
Since you are using strings for the sorting, the result is that numbers are not sorted as you may think: for instance "120" is smaller than "13", because "12" comes before "13".
The only occurrence in which the DisplayRole
properly sorts number values is when it is explicitly set with a number:
item.setData(Qt.DisplayRole, r)
Which will not work for you, as you want to show the "M - " prefix. Also, a common mistake is trying to use that in the constructor:
item = QTableWidgetItem(r)
And while the syntax is correct, its usage is wrong, as the integer constructor of QTableWidgetItem is used for other purposes.
If you want to support custom sorting, you must create a QTableWidgetItem subclass and implement the <
operator, which, in Python, is the __lt__()
magic method:
class SortUserRoleItem(QTableWidgetItem):
def __lt__(self, other):
return self.data(Qt.UserRole) < other.data(Qt.UserRole)
Then you have to create new items using that class. Note that:
- you should always try to use existing items, instead of continuously creating new ones;
- as explained in the
setItem()
documentation, you should always disable sorting before adding new items, especially when using a loop, otherwise the table will be constantly sorted at each insertion (thus making further insertion inconsistent); - you're using the a range (500) inconsistent with the row count (200);
- you should also set an item prototype based on the subclass above;
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setSortingEnabled(True)
self.setItemPrototype(SortUserRoleItem())
def populate(self):
self.setSortingEnabled(False)
self.setColumnCount(3)
self.setRowCount(200)
for row in range(200):
for column in range(3):
r = randint(0, 1000)
item = self.item(row, column)
if not item:
item = SortUserRoleItem()
self.setItem(row, column, item)
item.setData(Qt.DisplayRole, 'M - {}'.format(r))
item.setData(Qt.UserRole, r)
self.setSortingEnabled(True)
Note that, as an alternative, you could use a custom delegate, then just set the value of the item as an integer (as shown above) and override the displayText()
:
class PrefixDelegate(QStyledItemDelegate):
def displayText(self, text, locale):
if isinstance(text, int):
text = f'M - {text}'
return text
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setItemDelegate(PrefixDelegate(self))
# ...
def populate(self):
# ...
item = self.item(row, column)
if not item:
item = QTableWidgetItem()
self.setItem(row, column, item)
item.setData(Qt.DisplayRole, r)
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.