使用自定义模型和委托时提醒 QDataWidgetMapper 发生变化

Alerting QDataWidgetMapper to changes when using a custom Model & Delegate

我正在使用带有 dataclasses 的子类 QAbstractTableModel 作为项目。每个 dataclass 都包含一个带有 list 的字段“field1”,我想在列表视图中显示它并让它在我编辑时自动更改或在列表视图中添加一个项目。

为此,我将自定义委托设置为 QDataWidgetMapper,它将检索并设置来自 dataclass 的值。这按照我想要的方式工作。

我的问题是我想通过按下按钮向该列表视图添加其他项目,并让 QDataWidgetMapper 自动将它们添加到模型中。

这是我目前拥有的:

import sys
import dataclasses
from typing import List, Any
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


@dataclasses.dataclass()
class StorageItem:

    field1: List[str] = dataclasses.field(default_factory=list)


class StorageModel(QAbstractTableModel):

    def __init__(self, parent=None):
        super().__init__(parent)

        test = StorageItem()
        test.field1 = ['Item °1', 'Item °2']
        self._data: List[StorageItem] = [test]

    def data(self, index: QModelIndex, role: int = ...) -> Any:
        if not index.isValid():
            return

        item = self._data[index.row()]
        col = index.column()

        if role in {Qt.DisplayRole, Qt.EditRole}:
            if col == 0:
                return item.field1
            else:
                return None

    def setData(self, index: QModelIndex, value, role: int = ...) -> bool:

        if not index.isValid() or role != Qt.EditRole:
            return False

        item = self._data[index.row()]
        col = index.column()

        if col == 0:
            item.field1 = value

        self.dataChanged.emit(index, index)
        print(self._data)
        return True

    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        return Qt.ItemFlags(
            Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
        )

    def rowCount(self, parent=None) -> int:
        return len(self._data)

    def columnCount(self, parent=None) -> int:
        return len(dataclasses.fields(StorageItem))


class TestDelegate(QStyledItemDelegate):

    def __init__(self, parent=None):
        super().__init__(parent)

    def setEditorData(self, editor: QWidget, index: QModelIndex) -> None:
        if isinstance(editor, QListView):
            data = index.model().data(index, Qt.DisplayRole)
            editor.model().setStringList(data)
        else:
            super().setEditorData(editor, index)

    def setModelData(
            self, editor: QWidget,
            model: QAbstractItemModel,
            index: QModelIndex
    ) -> None:

        if isinstance(editor, QListView):
            data = editor.model().stringList()
            model.setData(index, data, Qt.EditRole)
        else:
            super().setModelData(editor, model, index)


class CustomListView(QListView):

    item_added = pyqtSignal(name='itemAdded')

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setModel(QStringListModel())

    def add_item(self, item: str):
        str_list = self.model().stringList()
        str_list.append(item)
        self.model().setStringList(str_list)
        self.item_added.emit()


class MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super().__init__(parent)

        cent_widget = QWidget()
        self.setCentralWidget(cent_widget)

        # Vertical Layout
        v_layout = QVBoxLayout()
        v_layout.setContentsMargins(10, 10, 10, 10)

        # Listview
        self.listview = CustomListView()
        v_layout.addWidget(self.listview)

        # Button
        self.btn = QPushButton('Add')
        self.btn.clicked.connect(lambda: self.listview.add_item('New Item'))
        v_layout.addWidget(self.btn)

        cent_widget.setLayout(v_layout)

        # Set Mapping
        self.mapper = QDataWidgetMapper()
        self.mapper.setItemDelegate(TestDelegate())
        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)
        self.mapper.setModel(StorageModel())
        self.mapper.addMapping(self.listview, 0)
        self.mapper.toFirst()

        self.listview.itemAdded.connect(self.mapper.submit)


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()


if __name__ == '__main__':
    main()

目前,我正在使用自定义 ListView 中的信号 itemAdded 手动提交 QDataWidgetMapper.

有没有办法在 CustomListView 中执行此操作而不使用自定义信号? 委托人以某种方式知道列表视图中的数据何时被编辑。添加新项目时如何触发相同的机制?

TL; DR; 不能。


submitPolicy QDataWidgetMapper::AutoSubmit表示失去焦点时模型将被更新。当调用委托的 commitData 或 closeEditor 信号时,模型也会更新,这在按下某些特定键时默认发生。

更好的实现是创建一个信号,每次在 QListView 模型中进行更改时都会发出该信号并将其连接到提交,而不仅仅是添加元素的方法。另外最好使用自定义 qproperty。

class CustomListView(QListView):
    items_changed = pyqtSignal(name="itemsChanged")

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setModel(QStringListModel())
        self.model().rowsInserted.connect(self.items_changed)
        self.model().rowsRemoved.connect(self.items_changed)
        self.model().dataChanged.connect(self.items_changed)
        self.model().layoutChanged.connect(self.items_changed)

    def add_item(self, item: str):
        self.items += [item]

    @pyqtProperty(list, notify=items_changed)
    def items(self):
        return self.model().stringList()

    @items.setter
    def items(self, data):
        if len(data) == len(self.items) and all(
            x == y for x, y in zip(data, self.items)
        ):
            return
        self.model().setStringList(data)
        self.items_changed.emit()
# Set Mapping
self.mapper = QDataWidgetMapper()
self.mapper.setModel(StorageModel())
self.mapper.addMapping(self.listview, 0, b"items")
self.mapper.toFirst()

self.listview.items_changed.connect(self.mapper.submit)