在qstandardemmodel中存储非弦值

发布于 2025-01-25 19:23:52 字数 4132 浏览 2 评论 0原文

这是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.date.date模型。修改setData()也意味着我必须修改data()(nb考虑到您最初可能会在任何编辑之前找到字符串的事实)。

这个想法是,每当用户修改日期(按第二列中F2进行编辑,输入新的有效日期,或没有日期,请按Enter),存储的值应为dateTime.dateTime.date.date (或)。但是令我惊讶的是,如果我尝试执行此操作,此断言失败:模型数据似乎已通过super()。SetData(index,value,value,crom),但是item.text()令人困惑地没有更改:尽管显然可以看到它 已更改。

通过评论了这些评论的线路,断言不会失败。

这是什么解释?我发现很难相信不建议将数据存储在qstandardItemmodel中,所以我认为我在项目的“更新”方面做错了什么。

我尝试插入以下(super()。SetData(...)):

if role == QtCore.Qt.EditRole and index.column() == 1:
    item = self.itemFromIndex(index)
    item.setText(original_value)

... >触发另一个调用setData()(带有字符串)和多个混淆editroledatarole呼叫data()代码>,实际上交付字符串值和值。

我还推测,代表可能在这里扮演角色...默认情况下,代表的编辑是qlineedit,看来该代表的方法的方法setmodeldata负责调用模型的setData方法。

但是,通过测试item.text() 和调用和呼叫super()。setData(...)之后的值。可以确定确实是super()调用会更改项目中文本的调用。但是,显然,如果value是字符串!

直到另行通知,我假设除非您在此处完全重新实现数据存储机制,否则您必须满足于仅存储字符串在QstandardItemModel中。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏末染殇 2025-02-01 19:23:52

您可以使用与DateTime.date具有相同功能的QDATE,而不是不必要的事情。

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_()

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_()
翻身的咸鱼 2025-02-01 19:23:52

这是对Eyllanesc的好答案的补充。我真的很想能够输入 null Date

实际上,我不喜欢此qdateTimeEdit +弹出解决方案,因此我选择的只是为我的编辑器使用qlineEdit,带有qdate数据值。然后,一个空白字符串转换为null 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)

一旦您开始编辑,按“删除”并立即结束编辑,将值设置为模型中的无。确认的消息框可能是一件好事。

* 源代码 of setmodeldata

This is by way of a supplement to eyllanesc's great answer. I really really really want to be able to enter a null date!

In fact I'm not keen on this QDateTimeEdit + popup solution, so I've chosen just to use a QLineEdit for my editor, with QDate data values. A blank string then translates to a null QDate.

But I first used a QDateEdit, and wanted to be able to set a null date value. There is a bit of frustration out there on this subject. There may be a more elegant solution using a validator but this, hacky though it is, seemed to do the job:

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)

Pressing "Delete" once you've started editing and the editing session ends instantly, setting the value to None in the model. A message box of confirmation might be a nice thing to add.

* source code of setModelData

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文