Issue
Here's an 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_()
The second column here is a date column: only valid dates (or empty string) should be accepted.
If you uncomment the commented-out lines you will see what I tried to do: my idea was to store not a str
but a datetime.date
in the second column of the model. Modifying setData()
also meant I had to modify data()
(NB taking account of the fact that you may find a string initially, before any edit).
The idea is that whenever the user modifies a date (press F2 to edit in the 2nd column, enter a new valid date, or no date, press Enter), the value stored should be a datetime.date
(or None
). But to my surprise if I try to do this, this assert
fails: the model data appears to have been updated OK by super().setData(index, value, role)
, but item.text()
, confusingly, has not changed: although visibly the user can see that it has changed.
With these commented-out lines commented out, the assert
does not fail.
What's the explanation for this? I find it difficult to believe that it is not recommended to store data other than strings in a QStandardItemModel
, so I presume I am doing something wrong regarding the "updating" of the item.
I tried inserting the following (after super().setData(...)
):
if role == QtCore.Qt.EditRole and index.column() == 1:
item = self.itemFromIndex(index)
item.setText(original_value)
... but this doesn't work: item.setText(...)
triggers another call to setData()
(with a string) and multiple confusing EditRole
and DataRole
calls to data()
, which in fact deliver string values and None
values.
I also surmise that the delegate may have a role to play here... by default the delegate's editor is a QLineEdit
, and it appears that the delegate's method setModelData
is responsible for calling the model's setData
method.
However, by testing the value of item.text()
both before and after calling super().setData(...)
it is possible to ascertain that it is indeed that super()
call which changes the text in the item. But only, apparently, if value
is a string!
Until further notice, I assume that unless you completely re-implement the data storage mechanism here, you have to be content with storing only strings in a QStandardItemModel
.
Solution
Instead of unnecessarily complicating things you could use QDate which has the same functionality as datetime.date and is handled natively by 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_()
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.