I have QTableView with QAbstractTableModel and multiple QStyledItemDelegates.
I set these delegates by setStyledItemForColumn.
In this case, my app crashes.
Crash happens when I push 1 key, or try to expand the gui to right.
But if I use one of them, my app goes well.
I think this is a kind of Qt bug.
Do you know somewhat?
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()
It is not a Qt bug but it is a bug of your own code.
First of all it is recommended that you run your code from the CMD / console so that you get error information, if you do you will see that the error message is:
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)
The error clearly indicates that the setCurrentText method expects a string but is receiving a TableItem. Why do you receive a TableItem? Well with your code items[index.row()][index.column()]
returns a TableItem, assuming you want to get the text "word" then you must use:
def setEditorData(self, editor, index):
model = index.model()
items = model.items
item = items[index.row()][index.column()]
text = item.word
editor.setCurrentText(text)
In both cases (setItemDelegate or setItemD) it causes the error.
But the error still persists when the window is resized since it is caused by the other delegate. Since you are partially overriding a delegate then the other party continues to use the generic information, for example expect index.data(Qt.DisplayRole)
to return a string but in your case return a TableItem:
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
In conclusion, the OP have not correctly used the default roles, causing delegates who use this information to obtain the incorrect data.
Considering all of the above, I have corrected many errors that I have not mentioned previously because many are trivial or are out of topic, obtaining the following code:
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()