在 QStandardItemModel 中存储非字符串值
Storing non-string value in QStandardItemModel
这是一个 MRE:
import sys, datetime
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMinimumSize(1000, 800)
self.main_splitter = QtWidgets.QSplitter(self)
self.main_splitter.setOrientation(QtCore.Qt.Horizontal)
self.setCentralWidget(self.main_splitter)
self.tree_view = MyTreeView(self.main_splitter)
self.right_panel = QtWidgets.QFrame(self.main_splitter)
self.right_panel.setStyleSheet("background-color: green");
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, *args):
super().__init__(*args)
self.setModel(MyTreeModel())
self.setColumnWidth(1, 150)
self.header().setStretchLastSection(False)
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
class MyTreeModel(QtGui.QStandardItemModel):
def __init__(self, *args):
super().__init__(*args)
item0_0 = QtGui.QStandardItem('blip')
item0_1 = QtGui.QStandardItem('2000-01-01')
self.invisibleRootItem().appendRow([item0_0, item0_1])
item1_0 = QtGui.QStandardItem('bubble')
item1_1 = QtGui.QStandardItem('')
self.invisibleRootItem().appendRow([item1_0, item1_1])
def setData(self, index, value, role=QtCore.Qt.EditRole):
original_value = value
# if role == QtCore.Qt.EditRole:
# if index.column() == 1:
# if value.strip() == '':
# value = None
# else:
# try:
# value = datetime.date.fromisoformat(value)
# except ValueError as e:
# print(f'**** value could not be parsed as date: |{value}| - value not changed')
# return False
return_val = super().setData(index, value, role)
if role == QtCore.Qt.EditRole:
item = self.itemFromIndex(index)
assert item.text() == original_value, f'item.text() |{item.text()}| original_value |{original_value}|'
return return_val
# def data(self, index, role=QtCore.Qt.DisplayRole):
# value = super().data(index, role)
# if role == QtCore.Qt.DisplayRole:
# if index.column() == 1:
# value = '' if value == None else str(value)
# elif role == QtCore.Qt.EditRole:
# if index.column() == 1:
# if value == '':
# value = None
# return value
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
这里的第二列是日期列:只应接受有效日期(或空字符串)。
如果您取消对注释掉的行的注释,您将看到我尝试做的事情:我的想法是在模型的第二列中存储的不是 str
,而是 datetime.date
。修改 setData()
也意味着我必须修改 data()
(请注意,在任何编辑之前,您最初可能会找到一个字符串)。
这个想法是,每当用户修改日期(按 F2 在第 2 列编辑,输入新的有效日期,或者没有日期,按 Enter),存储的值应该是 datetime.date
(或 None
)。但令我惊讶的是,如果我尝试这样做,这个 assert
会失败:模型数据似乎已被 super().setData(index, value, role)
正确更新,但令人困惑的是 item.text()
并没有改变:虽然明显用户可以看到它已经改变了。
将这些注释掉的行注释掉后,assert
不会失败。
这是怎么解释的?我发现很难相信不建议在 QStandardItemModel
中存储字符串以外的数据,所以我认为我在项目的“更新”方面做错了。
我尝试插入以下内容(在 super().setData(...)
之后):
if role == QtCore.Qt.EditRole and index.column() == 1:
item = self.itemFromIndex(index)
item.setText(original_value)
...但这不起作用:item.setText(...)
触发对 setData()
的另一个调用(使用字符串)和多个令人困惑的 EditRole
和 DataRole
调用data()
,实际上传递的是字符串值和 None
值。
我还推测委托人可能在这里发挥作用...默认情况下,委托人的编辑器是 QLineEdit
,看来委托人的方法 setModelData
负责调用模型的 setData
方法。
但是,通过在 之前和调用 super().setData(...)
之后测试 item.text()
的值,可以确定确实 super()
调用更改项目中的文本。但显然,如果 value
是一个字符串!
除非另行通知,否则我假设除非您在此处完全重新实现数据存储机制,否则您必须满足于在 QStandardItemModel
.
中仅存储字符串
您可以使用 QDate,而不是不必要地使事情复杂化,它具有与 datetime.date 相同的功能并且由 Qt 本机处理。
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
DATE_FORMAT = "yyyy-MM-dd"
class MyDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QDateTimeEdit):
editor.setDisplayFormat(DATE_FORMAT)
editor.setCalendarPopup(True)
return editor
class MyTreeModel(QtGui.QStandardItemModel):
def __init__(self, *args):
super().__init__(*args)
item0_0 = QtGui.QStandardItem("blip")
dt0 = QtCore.QDate.fromString("2000-01-01", DATE_FORMAT)
print(dt0.toPyDate(), dt0.toString(DATE_FORMAT))
item0_1 = QtGui.QStandardItem()
item0_1.setData(dt0, QtCore.Qt.DisplayRole)
self.invisibleRootItem().appendRow([item0_0, item0_1])
item1_0 = QtGui.QStandardItem("bubble")
item1_1 = QtGui.QStandardItem()
dt1 = QtCore.QDate()
assert dt1.isNull()
assert not dt1.isValid()
item1_1.setData(dt1, QtCore.Qt.DisplayRole)
self.invisibleRootItem().appendRow([item1_0, item1_1])
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, *args):
super().__init__(*args)
model = MyTreeModel()
self.setModel(model)
self.setColumnWidth(1, 150)
self.header().setStretchLastSection(False)
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
delegate = MyDelegate()
self.setItemDelegate(delegate)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMinimumSize(1000, 800)
self.main_splitter = QtWidgets.QSplitter(self)
self.main_splitter.setOrientation(QtCore.Qt.Horizontal)
self.setCentralWidget(self.main_splitter)
self.tree_view = MyTreeView(self.main_splitter)
self.right_panel = QtWidgets.QFrame(self.main_splitter)
self.right_panel.setStyleSheet("background-color: green")
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
这是对 eyllanesc 出色回答的补充。我真的真的真的很想输入一个 空日期 !
事实上,我并不喜欢这个 QDateTimeEdit
+ 弹出式解决方案,所以我选择只为我的编辑器使用 QLineEdit
,数据值为 QDate
。空白字符串然后转换为空 QDate
.
但我首先使用了 QDateEdit
,并希望能够设置一个空日期值。在这个问题上有一些挫败感。可能有一个使用验证器的更优雅的解决方案,但这个虽然很老套,但似乎可以完成这项工作:
class MyDateEdit(QtWidgets.QDateEdit):
def __init__(self, parent):
super().__init__(parent)
self.null_date = False
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Delete):
self.null_date = True
press_return_key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Enter, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(self, press_return_key)
return
super().keyPressEvent(event)
class MyDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
print(f'parent {parent} type {type(parent)}')
editor = super().createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QDateTimeEdit):
# use the subclass instead:
editor = MyDateEdit(parent)
editor.setDisplayFormat(DATE_FORMAT)
editor.setCalendarPopup(True)
return editor
def setModelData(self, editor, model, index):
if editor.null_date:
# as far as I'm aware, this is pretty much all that setModelData does*
model.setData(index, None)
else:
super().setModelData(editor, model, index)
开始编辑后按“删除”,编辑会话立即结束,在模型中将值设置为 None。添加一个确认消息框可能是件好事。
* source code 共 setModelData
这是一个 MRE:
import sys, datetime
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMinimumSize(1000, 800)
self.main_splitter = QtWidgets.QSplitter(self)
self.main_splitter.setOrientation(QtCore.Qt.Horizontal)
self.setCentralWidget(self.main_splitter)
self.tree_view = MyTreeView(self.main_splitter)
self.right_panel = QtWidgets.QFrame(self.main_splitter)
self.right_panel.setStyleSheet("background-color: green");
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, *args):
super().__init__(*args)
self.setModel(MyTreeModel())
self.setColumnWidth(1, 150)
self.header().setStretchLastSection(False)
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
class MyTreeModel(QtGui.QStandardItemModel):
def __init__(self, *args):
super().__init__(*args)
item0_0 = QtGui.QStandardItem('blip')
item0_1 = QtGui.QStandardItem('2000-01-01')
self.invisibleRootItem().appendRow([item0_0, item0_1])
item1_0 = QtGui.QStandardItem('bubble')
item1_1 = QtGui.QStandardItem('')
self.invisibleRootItem().appendRow([item1_0, item1_1])
def setData(self, index, value, role=QtCore.Qt.EditRole):
original_value = value
# if role == QtCore.Qt.EditRole:
# if index.column() == 1:
# if value.strip() == '':
# value = None
# else:
# try:
# value = datetime.date.fromisoformat(value)
# except ValueError as e:
# print(f'**** value could not be parsed as date: |{value}| - value not changed')
# return False
return_val = super().setData(index, value, role)
if role == QtCore.Qt.EditRole:
item = self.itemFromIndex(index)
assert item.text() == original_value, f'item.text() |{item.text()}| original_value |{original_value}|'
return return_val
# def data(self, index, role=QtCore.Qt.DisplayRole):
# value = super().data(index, role)
# if role == QtCore.Qt.DisplayRole:
# if index.column() == 1:
# value = '' if value == None else str(value)
# elif role == QtCore.Qt.EditRole:
# if index.column() == 1:
# if value == '':
# value = None
# return value
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
这里的第二列是日期列:只应接受有效日期(或空字符串)。
如果您取消对注释掉的行的注释,您将看到我尝试做的事情:我的想法是在模型的第二列中存储的不是 str
,而是 datetime.date
。修改 setData()
也意味着我必须修改 data()
(请注意,在任何编辑之前,您最初可能会找到一个字符串)。
这个想法是,每当用户修改日期(按 F2 在第 2 列编辑,输入新的有效日期,或者没有日期,按 Enter),存储的值应该是 datetime.date
(或 None
)。但令我惊讶的是,如果我尝试这样做,这个 assert
会失败:模型数据似乎已被 super().setData(index, value, role)
正确更新,但令人困惑的是 item.text()
并没有改变:虽然明显用户可以看到它已经改变了。
将这些注释掉的行注释掉后,assert
不会失败。
这是怎么解释的?我发现很难相信不建议在 QStandardItemModel
中存储字符串以外的数据,所以我认为我在项目的“更新”方面做错了。
我尝试插入以下内容(在 super().setData(...)
之后):
if role == QtCore.Qt.EditRole and index.column() == 1:
item = self.itemFromIndex(index)
item.setText(original_value)
...但这不起作用:item.setText(...)
触发对 setData()
的另一个调用(使用字符串)和多个令人困惑的 EditRole
和 DataRole
调用data()
,实际上传递的是字符串值和 None
值。
我还推测委托人可能在这里发挥作用...默认情况下,委托人的编辑器是 QLineEdit
,看来委托人的方法 setModelData
负责调用模型的 setData
方法。
但是,通过在 之前和调用 super().setData(...)
之后测试 item.text()
的值,可以确定确实 super()
调用更改项目中的文本。但显然,如果 value
是一个字符串!
除非另行通知,否则我假设除非您在此处完全重新实现数据存储机制,否则您必须满足于在 QStandardItemModel
.
您可以使用 QDate,而不是不必要地使事情复杂化,它具有与 datetime.date 相同的功能并且由 Qt 本机处理。
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
DATE_FORMAT = "yyyy-MM-dd"
class MyDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QDateTimeEdit):
editor.setDisplayFormat(DATE_FORMAT)
editor.setCalendarPopup(True)
return editor
class MyTreeModel(QtGui.QStandardItemModel):
def __init__(self, *args):
super().__init__(*args)
item0_0 = QtGui.QStandardItem("blip")
dt0 = QtCore.QDate.fromString("2000-01-01", DATE_FORMAT)
print(dt0.toPyDate(), dt0.toString(DATE_FORMAT))
item0_1 = QtGui.QStandardItem()
item0_1.setData(dt0, QtCore.Qt.DisplayRole)
self.invisibleRootItem().appendRow([item0_0, item0_1])
item1_0 = QtGui.QStandardItem("bubble")
item1_1 = QtGui.QStandardItem()
dt1 = QtCore.QDate()
assert dt1.isNull()
assert not dt1.isValid()
item1_1.setData(dt1, QtCore.Qt.DisplayRole)
self.invisibleRootItem().appendRow([item1_0, item1_1])
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, *args):
super().__init__(*args)
model = MyTreeModel()
self.setModel(model)
self.setColumnWidth(1, 150)
self.header().setStretchLastSection(False)
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
delegate = MyDelegate()
self.setItemDelegate(delegate)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMinimumSize(1000, 800)
self.main_splitter = QtWidgets.QSplitter(self)
self.main_splitter.setOrientation(QtCore.Qt.Horizontal)
self.setCentralWidget(self.main_splitter)
self.tree_view = MyTreeView(self.main_splitter)
self.right_panel = QtWidgets.QFrame(self.main_splitter)
self.right_panel.setStyleSheet("background-color: green")
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
这是对 eyllanesc 出色回答的补充。我真的真的真的很想输入一个 空日期 !
事实上,我并不喜欢这个 QDateTimeEdit
+ 弹出式解决方案,所以我选择只为我的编辑器使用 QLineEdit
,数据值为 QDate
。空白字符串然后转换为空 QDate
.
但我首先使用了 QDateEdit
,并希望能够设置一个空日期值。在这个问题上有一些挫败感。可能有一个使用验证器的更优雅的解决方案,但这个虽然很老套,但似乎可以完成这项工作:
class MyDateEdit(QtWidgets.QDateEdit):
def __init__(self, parent):
super().__init__(parent)
self.null_date = False
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Delete):
self.null_date = True
press_return_key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Enter, QtCore.Qt.NoModifier)
QtWidgets.QApplication.sendEvent(self, press_return_key)
return
super().keyPressEvent(event)
class MyDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
print(f'parent {parent} type {type(parent)}')
editor = super().createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QDateTimeEdit):
# use the subclass instead:
editor = MyDateEdit(parent)
editor.setDisplayFormat(DATE_FORMAT)
editor.setCalendarPopup(True)
return editor
def setModelData(self, editor, model, index):
if editor.null_date:
# as far as I'm aware, this is pretty much all that setModelData does*
model.setData(index, None)
else:
super().setModelData(editor, model, index)
开始编辑后按“删除”,编辑会话立即结束,在模型中将值设置为 None。添加一个确认消息框可能是件好事。
* source code 共 setModelData