具有多个 StyledItemDelegateForColumn 的 QAbstractTableModel 和 QTableView 使我的应用程序崩溃

QAbstractTableModel & QTableView with more than one StyledItemDelegateForColumn crashes my app

我有带有 QAbstractTableModel 和多个 QStyledItemDelegates 的 QTableView。

我通过 setStyledItemForColumn 设置这些委托。

在这种情况下,我的应用程序崩溃了。

按 1 个键或尝试向右展开 gui 时发生崩溃。

但如果我使用其中之一,我的应用程序运行良好。

我认为这是一种 Qt 错误。

你知道多少?

from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtSql
import os
import PySide2
import sys
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
class IconDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super(IconDelegate, self).initStyleOption(option, index)
        if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
            s = option.decorationSize
            s.setWidth(option.rect.width())
            option.decorationSize = s
class Delegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(Delegate, self).__init__(parent=None)
    def initStyleOption(self, option, index):

#        super(IconDelegate, self).initStyleOption(option, index)
        if index.column() == 6:
            if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
                s = option.decorationSize
                s.setWidth(option.rect.width())
                option.decorationSize = s
    def createEditor(self, parent, option, index):
        editor = QtWidgets.QComboBox(parent)

        return editor
    def setEditorData(self, editor, index):
        model = index.model()
        items = model.items
        text = items[index.row()][index.column()]
        editor.setCurrentText(text)
    def setModelData(self, editor, model, index):

        items = model.items
#
class TableView(QtWidgets.QTableView):
    def __init__(self, parent=None):
        super(TableView, self).__init__(parent=None)
        delegate = Delegate()
        self.setItemDelegate(delegate)
        #Here is the crash point
#        self.setItemDelegateForColumn(6, delegate)
#        self.setItemDelegateForColumn(11, IconDelegate())
        self.tableModel = TableModel(2, 15)
        self.setModel(self.tableModel)
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_1:
            self.tableModel.insertRows(0)

class TableItem(object):
    def __init__(self,  parent=None):        
        self.root = False

        self.word = ""
        self.alignment = QtCore.Qt.AlignCenter

        self.rule = ""
        self.foregroundcolor = QtGui.QColor(QtCore.Qt.black)
        self.backgroundcolor = QtGui.QColor(QtCore.Qt.white)
        self.font = QtGui.QFont("Meiryo", 14)
class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, row = 0, column = 0, parent = None):
        super(TableModel, self).__init__(parent = None)        
        self.items = [[TableItem() for c in range(column)] for r in range(row)]
        self.root = QtCore.QModelIndex()        
    def rowCount(self, parent=QtCore.QModelIndex()):        
        return len(self.items)
    def columnCount(self, parent=QtCore.QModelIndex()):       
        return 15
    def data(self, index, role = QtCore.Qt.DisplayRole):    
        if not index.isValid():
            return None
        row = index.row()
        column = index.column()        
        if role == QtCore.Qt.DisplayRole:
            item = self.items[row][column]
            return item
    def headerData(self, section, orientation, role = QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Orientation.Horizontal:
            if role == QtCore.Qt.DisplayRole:
                return alphabet[section]
        return super(TableModel, self).headerData(section, orientation, role)
    def flags(self, index):        
        return QtCore.Qt.ItemFlag.ItemIsEditable|QtCore.Qt.ItemFlag.ItemIsEnabled|QtCore.Qt.ItemFlag.ItemIsSelectable
    def setData(self, index, value, role=QtCore.Qt.EditRole):        
        if role == QtCore.Qt.EditRole:       
            row, column = index.row(), index.column()

            self.items[row][column]  = value
            self.dataChanged.emit(index, index)
            return True
        elif role == QtCore.Qt.DisplayRole:             
            row, column = index.row(), index.column()
            self.items[row][column]  = value
            self.dataChanged.emit(index, index)
            return True
        elif role == QtCore.Qt.FontRole:
            string = value.toString()
            s = string.split(",")
            font = s[0]

            self.dataChanged.emit(index, index)
            return True
    def insertRows(self, position, rows=1, index=QtCore.QModelIndex()):   
        self.beginInsertRows(QtCore.QModelIndex(), position, position+rows-1)
        for row in range(rows):        
            self.items.insert(position+row, [TableItem() for c in range(self.columnCount())])
        self.endInsertRows()
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
        self.emit(QtCore.SIGNAL("layoutChanged()"))      
        return True
    def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
        self.beginRemoveRows(QtCore.QModelIndex(), position, position+rows-1)
        for row in range(rows):
            self.items = self.items[:position] + \
                        self.items[position + rows:]
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
        self.emit(QtCore.SIGNAL("layoutChanged()"))      
        return True

def main():
    if QtWidgets.QApplication.instance() is not None:
        app = QtWidgets.QApplication.instance()
    else:
        app = QtWidgets.QApplication([])
    mainwindow = TableView()
    mainwindow.show()
    sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
    main()

说明

这不是 Qt 错误,而是您自己的代码的错误。

首先,建议您从 CMD/控制台 运行 您的代码,以便获得错误信息,如果这样做,您将看到错误消息是:

Traceback (most recent call last):
  File "main.py", line 38, in setEditorData
    editor.setCurrentText(text)
TypeError: 'PySide2.QtWidgets.QComboBox.setCurrentText' called with wrong argument types:
  PySide2.QtWidgets.QComboBox.setCurrentText(TableItem)
Supported signatures:
  PySide2.QtWidgets.QComboBox.setCurrentText(str)

该错误清楚地表明 setCurrentText 方法需要一个字符串,但正在接收一个 TableItem。为什么会收到 TableItem?那么你的代码 items[index.row()][index.column()] return 是一个 TableItem,假设你想获取文本 "word" 那么你必须使用:

def setEditorData(self, editor, index):
    model = index.model()
    items = model.items
    <b>item = items[index.row()][index.column()]
    text = item.word</b>
    editor.setCurrentText(text)

在这两种情况下(setItemDelegate 或 setItemD)都会导致错误。

但是当 window 调整大小时错误仍然存​​在,因为它是由另一个代表引起的。由于您部分覆盖了委托,因此另一方继续使用通用信息,例如期望 index.data(Qt.DisplayRole) 到 return 一个字符串,但在您的情况下 return 一个 TableItem:

def data(self, index, role = QtCore.Qt.DisplayRole):    
    if not index.isValid():
        return None
    row = index.row()
    column = index.column()        
    <b>if role == QtCore.Qt.DisplayRole:
        item = self.items[row][column]
        return item</b>

总之,OP 没有正确使用默认角色,导致使用此信息的代表获取了不正确的数据。

解决方案

综上所述,我纠正了很多以前没有提到的错误,因为很多是微不足道的或者是题外话,得到如下代码:

from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtSql
import os
import PySide2
import sys

dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, "plugins", "platforms")
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = plugin_path

alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]


class IconDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super(IconDelegate, self).initStyleOption(option, index)
        if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
            s = option.decorationSize
            s.setWidth(option.rect.width())
            option.decorationSize = s


class Delegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        if option.features & QtWidgets.QStyleOptionViewItem.HasDecoration:
            s = option.decorationSize
            s.setWidth(option.rect.width())
            option.decorationSize = s

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QComboBox(parent)
        return editor


#
class TableView(QtWidgets.QTableView):
    def __init__(self, parent=None):
        super(TableView, self).__init__(parent=None)
        delegate = Delegate(self)
        # self.setItemDelegate(delegate)
        # Here is the crash point
        self.setItemDelegateForColumn(6, delegate)
        icon_delegate = IconDelegate(self)
        self.setItemDelegateForColumn(11, icon_delegate)
        self.tableModel = TableModel(2, 15)
        self.setModel(self.tableModel)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_1:
            self.tableModel.insertRows(0)


class TableItem(object):
    def __init__(self, parent=None):
        self.root = False

        self.word = ""
        self.alignment = QtCore.Qt.AlignCenter

        self.rule = ""
        self.foregroundcolor = QtGui.QColor(QtCore.Qt.black)
        self.backgroundcolor = QtGui.QColor(QtCore.Qt.white)
        self.font = QtGui.QFont("Meiryo", 14)


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, row=0, column=0, parent=None):
        super(TableModel, self).__init__(parent=None)
        self.items = [[TableItem() for c in range(column)] for r in range(row)]

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.items)

    def columnCount(self, parent=QtCore.QModelIndex()):
        if self.items:
            return len(self.items[0])
        return 0

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        row = index.row()
        column = index.column()
        if 0 <= row < self.rowCount() and 0 <= column < self.columnCount():
            item = self.items[row][column]
        if role == QtCore.Qt.DisplayRole:
            text = item.word
            return text
        elif role == QtCore.Qt.EditRole:
            return item

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Orientation.Horizontal:
            if role == QtCore.Qt.DisplayRole and section < len(alphabet):
                return alphabet[section]
        return super(TableModel, self).headerData(section, orientation, role)

    def flags(self, index):
        return (
            QtCore.Qt.ItemFlag.ItemIsEditable
            | QtCore.Qt.ItemFlag.ItemIsEnabled
            | QtCore.Qt.ItemFlag.ItemIsSelectable
        )

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            row, column = index.row(), index.column()
            self.items[row][column].word = value
            self.dataChanged.emit(index, index)
            return True
        elif role == QtCore.Qt.DisplayRole:
            row, column = index.row(), index.column()
            self.items[row][column].word = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def insertRows(self, position, rows=1, index=QtCore.QModelIndex()):
        self.beginInsertRows(QtCore.QModelIndex(), position, position + rows - 1)
        for row in range(rows):
            self.items.insert(
                position + row, [TableItem() for c in range(self.columnCount())]
            )
        self.endInsertRows()
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
        self.emit(QtCore.SIGNAL("layoutChanged()"))
        return True

    def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
        self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1)
        for row in range(rows):
            self.items = self.items[:position] + self.items[position + rows :]
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
        self.emit(QtCore.SIGNAL("layoutChanged()"))
        return True


def main():
    if QtWidgets.QApplication.instance() is not None:
        app = QtWidgets.QApplication.instance()
    else:
        app = QtWidgets.QApplication([])
    mainwindow = TableView()
    mainwindow.show()
    sys.exit(QtWidgets.QApplication.exec_())


if __name__ == "__main__":
    main()