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!
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.
data method works by receiving an
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
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.
|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
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
- (2, 1) -- this cell has a value of 1, which is equal to
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:
To support developers in [[ countryRegion ]] I give a [[ localizedDiscount[couponCode] ]]% discount with the code [[ couponCode ]] — Enjoy!
For [[ activeDiscount.description ]] I'm giving a [[ activeDiscount.discount ]]% discount with the code [[ couponCode ]] — Enjoy!