Search code examples
pythonmodel-view-controllermodelpyqtpyqt5

PyQt5 QAbstractItemModel crashes when I try to make a tree model from db entities


I tried to implement an own model representing a tree for making it possible for a QT view to access my database entities (its not possible for me to use QT's SQL support so I have to deal with models). It worked perfectly as I only used a table but by now I am struggling with adapting it for a tree model.

There are entities of one type 'A' and every of them has any amount of child entities (another entity type / table) here called 'B'.

In the beginning the tree view displayed nothing but when I used a table view for the same model at least empty cells were displayed. Now the program shuts more or less down direct after start.

I want to use my own delegates (a delete button in first column that works for the old table model) and I don't want to use QStandardItemModel because I would have to manage an item for each column per entity.

I've researched and was pretty sure that it would work if I implement:

  • columnCount
  • rowCount
  • index
  • parent
  • data
  • flags
  • headerData

where the first act different depending on the parent index so that for example rowCount for top level (QModelIndex()) returns the count of A entities and for any index of an A entity the number of sub B entities.

I think the main problem is in my overloads of index and parent. I use the internal pointer of the index to store the parent index which I return in the parent method.

I tried a very simple example with a specified amount of rows and columns where the data method returns for Qt.DisplayRole only a string of formatted row and column. When I start my example for a few seconds a table is shown with my data and then the application shuts down without traceback. Process finished with exit code -1073741819 (0xC0000005) Regardless if I run the program in terminal or in PyCharm no traceback is generated.

QAbstractItemModelTest also didn't displayed anything.

import typing
import sys

from PyQt5.QtWidgets import QApplication, QWidget, QTreeView, QTableView, QHBoxLayout
from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel

# Logging also doesn't help 
import logging
logger = logging.getLogger(__name__)
logger.addHandler(stream := logging.StreamHandler())
logger.setLevel(logging.DEBUG)
stream.setLevel(logging.DEBUG)


class Model(QAbstractItemModel):
    def __init__(self, parent):
        super(Model, self).__init__(parent)

    def columnCount(self, parent: QModelIndex = ...) -> int:
        # only for tests
        return 1

    def rowCount(self, parent: QModelIndex = ...) -> int:
        # only for tests
        return 2

    def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
        # maybe this is method is false
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        return self.createIndex(row, column, parent)

    def parent(self, child: QModelIndex) -> QModelIndex:
        # calling child.parent() causes immediate shutdown
        if not child.isValid():
            return QModelIndex()
        return child.internalPointer() or QModelIndex()

    def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
        if role == Qt.DisplayRole:
            return f"{index.row()} {index.column()}"
        pass

    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        return super(Model, self).flags(index) | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
        # For displaying needed headers
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return f"Header {section}"
    pass


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.model = Model(self)

        self.table = QTableView(self)
        self.tree = QTreeView(self)  # the real needed
        self.table.setModel(self.model)
        # self.tree.setModel(self.model)  causes not even displaying the window

        self.main_layout = QHBoxLayout()
        self.main_layout.addWidget(self.table)
        self.main_layout.addWidget(self.tree)
        self.setLayout(self.main_layout)


# Standard startup
if __name__ == "__main__":
    app = QApplication(sys.argv)
    wdg = Window()
    wdg.show()
    sys.exit(app.exec_())

Versions in venv:

  • Python: 3.10.8
  • pip: 23.2.1
  • PyQt5 5.15.9
  • PyQt5-Qt5: 5.15.2
  • PyQt5-sip: 12.12.2

This is only a simple example and I don't know if the rest works if this works. I thought a QT tree model would be easier. I also tried to adopt https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html but I didn't find something that was in an iluminating way different.


Solution

  • The solution was to use an own tree item class with references to the other items like it is used in the documentation https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html. I first didn't understand why an own class is needed and why this isn't done by QT but as the comment explains it doesn't work to use the internalPointer to save an index. Therefore an own class is better.