Why did the output on the QAbstractTableModel look so strange when I accidentally omitted "if role == Qt.DisplayRole:"?

Heads up! You've already completed this tutorial.

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:

python
    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:

image|642x487

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!

The complete guide to packaging Python GUI applications with PyInstaller.
[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]


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.

python
def data(self, index, role):
    if role == Qt.CheckStateRole:
        return 2

If you returned 1 for them all they would all appear "partially checked" --

python
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.

Screenshot

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:


Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Why did the output on the QAbstractTableModel look so strange when I accidentally omitted "if role == Qt.DisplayRole:"? was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt.