Virginia | 2020-05-09 13:51:39 UTC | #1
When I was working on the QAbstractTableModel Tutorial and had just added the block that formats strings into things like dates, floats, and integers, I accidentally omitted the first line of the data method block, if role == Qt.DisplayRole:
.
The code still ran, but the output looked rather strange and I was wondering why this happened. This was the code that I had just added, but accidentally omitted the first line:
def data(self, index, role):
if role == Qt.DisplayRole: #omitting this accidentally caused the error
# Get the raw value
value = self._data[index.row()][index.column()]
# Perform per-type checks and render accordingly
if isinstance(value, datetime.date):
# Render time to YYY-MM-DD.
return value.strftime("%Y-%m-%d")
if isinstance(value, float):
# Render float to 2 dp
return "%.2f" % value
if isinstance(value, str):
# Render strings with quotes
return '"%s"' % value
# Default (anything not captured above: e.g. int)
return value
This is what the output looked like:
I am very curious as to why this happened - there are check boxes, check marks, and a square filled in.
Thank you for your insights!
Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!
martin | 2022-08-08 21:15:37 UTC | #2
Hey @Virginia welcome to the forum. This is a fantastic question because it gets into the details of how this all works.
The data
method works by receiving an index
and role
and responding with instructions for the view to perform. The index
says where and the role
says what.
In the tutorial we cover in detail roles for outputting data (Qt.DisplayRole
) and also colours (e.g. Qt.BackgroundRole
) but there are other roles too, including one for showing checkboxes --- Qt.CheckStateRole
(there is a complete table in the tutorial).
When you nest your code under if role == Qt.DisplayRole
you ensure that you only return data for that specific role. Qt is still sending them to your method, but they are being silently ignored (functions return None by default).
When you omit that line, your method is now responding to all roles it receives with the answers that you have defined for Qt.DisplayRole
-- including Qt.CheckStateRole
So why do some checkboxes show empty, some ticked and some filled with a square?
When your method is called with Qt.CheckStateRole
Qt is expected a Qt.CheckState
as an answer. Qt.CheckState is an Qt enum which contains the following values.
Value | State | |
---|---|---|
Qt.Unchecked | 0 | The item is unchecked. |
Qt.PartiallyChecked | 1 | The item is partially checked. Items in hierarchical models may be partially checked if some, but not all, of their children are checked. |
Qt.Checked | 2 | The item is checked. |
Note that these enums are just friendly ways to keep track of "magic numbers" that have special meaning in specific circumstances. But the value of Qt.Unchecked
is literally just 0
, if you test Qt.Unchecked == 0
you'll find it is True
.
So if you created the following data
method, and responded to every Qt.CheckStateRole
with 2 you'll find that all fields are checked.
def data(self, index, role):
if role == Qt.CheckStateRole:
return 2
If you returned 1 for them all they would all appear "partially checked" --
def data(self, index, role):
if role == Qt.CheckStateRole:
return 1
--- which just so happens to appear as a square in the checkbox.
Coming back to your screenshot, we can now explain why certain boxes are checked and others aren't.
The checked cells are, labeled by (row, column).
- (1, 3) -- this cell has a value of 2, which is equal to
Qt.Checked
- (2, 1) -- this cell has a value of 1, which is equal to
Qt.PartiallyChecked
All the rest are returning something other than 0, 1, 2 but not None
and are defaulting to an unchecked state.
Hope that clears it up?
Virginia | 2020-05-09 19:19:20 UTC | #3
Thank you very much @martin ! This is exactly the kind of information I was looking for - an explanation of what happens in the background, and it provides insight into how Model Views work in general. It's fascinating, and very helpful - I'm sort of glad I made this error because this might not have occurred to me otherwise.
Eolinwen | 2020-05-12 16:08:15 UTC | #4
@martin Great explanation that I hope will be in the next version of the book. I keep it in a corner because it was a question that tapped my mind. :slight_smile:
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.