0

I've prepared a minimal reproducible example of this problem. There are two TreeView delegates: one for checkboxes and one for progress bars. If I add any one of these delegates, but not the other one, the program doesn't crash. But if I add both, it crashes (upon trying to show the rows and the delegates). It gives the Segmentation Fault error when crashing.

delegates.py

from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QEvent, QPoint, QRect
from PySide6 import QtCore, QtGui


class CheckBoxDelegate(QtWidgets.QStyledItemDelegate):
    """
    A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
    """
    def __init__(self, parent = None):
        QtWidgets.QStyledItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        """
        Important, otherwise an editor is created if the user clicks in this cell.
        """
        return None

    def paint(self, painter, option, index):
        checked = bool(index.model().data(index, Qt.DisplayRole))
        check_box_style_option = QtWidgets.QStyleOptionButton()
        if (index.flags() & Qt.ItemIsEditable):
            check_box_style_option.state |= QtWidgets.QStyle.State_Enabled
        else:
            check_box_style_option.state |= QtWidgets.QStyle.State_ReadOnly
        if checked:
            check_box_style_option.state |= QtWidgets.QStyle.State_On
        else:
            check_box_style_option.state |= QtWidgets.QStyle.State_Off
        check_box_style_option.rect = self.getCheckBoxRect(option)
        QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_CheckBox, check_box_style_option, painter)

    def editorEvent(self, event, model, option, index):
        if not (index.flags() & Qt.ItemIsEditable):
            return False
        # Do not change the checkbox-state
        if event.type() == QEvent.MouseButtonRelease or event.type() == QEvent.MouseButtonDblClick:
            if event.button() != Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
                return False
            if event.type() == QEvent.MouseButtonDblClick:
                return True
        elif event.type() == QEvent.KeyPress:
            if event.key() != Qt.Key_Space and event.key() != Qt.Key_Select:
                return False
        else:
            return False
        # Change the checkbox-state
        self.setModelData(None, model, index)
        return True

    def getCheckBoxRect(self, option):
        check_box_style_option = QtWidgets.QStyleOptionButton()
        check_box_rect = QtWidgets.QApplication.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
        check_box_point = QPoint (option.rect.x() +
                                option.rect.width() / 2 -
                                check_box_rect.width() / 2,
                                option.rect.y() +
                                option.rect.height() / 2 -
                                check_box_rect.height() / 2)
        return QRect(check_box_point, check_box_rect.size())

    def setModelData (self, editor, model, index):
        newValue = not bool(index.model().data(index, Qt.DisplayRole))
        model.setData(index, newValue, Qt.EditRole)


class ProgressBarDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
            super().__init__(parent)

    def paint(self, painter, option, index):
        # Get the data for the item
        progress = index.data(QtCore.Qt.ItemDataRole.UserRole)

        # Draw the progress bar
        painter.save()
        rect = option.rect
        rect.setWidth(int(rect.width() * progress))
        painter.fillRect(rect, QtGui.QColor("#00c0ff"))
        painter.restore()

mainwindow.py

from ui_form import Ui_MainWindow
from PySide6.QtCore import QThread, SIGNAL, Slot
from PySide6 import QtGui, QtCore
from PySide6.QtWidgets import QApplication, QMainWindow

from delegates import CheckBoxDelegate, ProgressBarDelegate


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.row_data = []
        self.model = QtGui.QStandardItemModel()
        self.ui.actionExit.triggered.connect(self.exit)

    def reinit_model(self):
        self.model.clear()
        self.rootItem = self.model.invisibleRootItem()
        self.model.setHorizontalHeaderLabels(['Checkbox', 'Title', 'Progress'])
        self.ui.treeView.setModel(self.model)

    def get_row_data(self):
        row_data = []
        for i in range(3):
            title = f"Col 1 Row {i}"
            row_data.append([title])
        return row_data

    def reset_row_data(self):
        self.row_data.clear()
        self.row_data = self.get_row_data()

    class Show_list(QThread):
        def __init__(self, AppWindow):
            QThread.__init__(self)
            self.AppWindow = AppWindow

        def run(self):
            self.AppWindow.reset_row_data()

    def populate_window_list(self):
        self.reinit_model()
        for title_link in self.row_data:
            item = [QtGui.QStandardItem(),
                    QtGui.QStandardItem(title_link[0]),
                    QtGui.QStandardItem()]
            progress = 0.5
            item[2].setData(progress, QtCore.Qt.ItemDataRole.UserRole)
            self.rootItem.appendRow(item)
        self.ui.treeView.show()
        cb_delegate = CheckBoxDelegate()
        pb_delegate = ProgressBarDelegate()
        self.ui.treeView.setItemDelegateForColumn(0, cb_delegate)
        # self.ui.treeView.setItemDelegateForColumn(2, pb_delegate)
        for i in range(3):
            self.ui.treeView.resizeColumnToContents(i)

    @Slot()
    def show_list(self):
        self.thread = self.Show_list(self)
        self.connect(self.thread, SIGNAL("finished()"),
                     self.populate_window_list)
        self.thread.start()

    def exit(self):
        QApplication.quit()

main.py

import sys
from mainwindow import MainWindow
from PySide6.QtWidgets import QApplication


if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = MainWindow()
    widget.reinit_model()
    getRowDataButton = widget.ui.getRowDataButton
    getRowDataButton.clicked.connect(widget.show_list)
    widget.show()
    sys.exit(app.exec())

Here's also ui_form.py.

I've tried to add cb_delegate.deleteLater() and pb_delegate.deleteLater(), but it didn't help. The program loaded the list and crashed immediately.

sequence
  • 744
  • 1
  • 8
  • 16
  • 1
    Change to `cb_delegate = CheckBoxDelegate(self.ui.treeView)` and `pb_delegate = ProgressBarDelegate(self.ui.treeView)`. Also, the `self.connect()` syntax is obsolete, use `self.thread.finished.connect(self.populate_window_list)`. Finally, you should avoid accessing and modifying objects from external threads if they are potentially being accessed in the main thread too. Use signals for that. – musicamante Dec 26 '22 at 18:55
  • @musicamante Thanks a lot, this works! Your comment looks like it's worth being a full answer. – sequence Dec 26 '22 at 20:26

0 Answers0