1

我正在使用从QAbstractTableModel子类化的自定义模型,我的数据是 dataclasses 的列表

我用一个QListView和两个QLineEdits设置了一个简单的 GUI ,如下所示:

带有列表视图和两个行编辑的简单 GUI

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


@dataclasses.dataclass()
class StorageItem:

    field1: str = 'Item °1'
    field2: int = 42


class StorageModel(QAbstractTableModel):

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

        self._data: List[StorageItem] = [StorageItem()]

    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
            elif col == 1:
                return item.field2
            else:
                return None

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

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

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

        if col == 0:
            item.field1 = value
        elif col == 1:
            item.field2 = value

        self.dataChanged.emit(index, index)
        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 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)

        self.model = StorageModel()

        # Listview
        self.listview = QListView()
        self.listview.setModel(self.model)
        v_layout.addWidget(self.listview)

        # Horizontal Layout
        h_layout = QHBoxLayout()
        h_layout.setContentsMargins(*[0]*4)

        self.field1 = QLineEdit()
        h_layout.addWidget(self.field1)

        self.field2 = QLineEdit()
        h_layout.addWidget(self.field2)

        v_layout.addLayout(h_layout)
        cent_widget.setLayout(v_layout)

        # Set Mapping
        mapper = QDataWidgetMapper()
        mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)
        mapper.setModel(self.model)
        mapper.addMapping(self.field1, 0)
        mapper.addMapping(self.field2, 1)
        mapper.toFirst()

        # self.field1.textChanged.connect(lambda: mapper.submit())


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


if __name__ == '__main__':
    main()

我试图实现这一点,每当我更改第一个QLineEdit的内容时,列表视图也会更新。
通过阅读QDataWidgetMapper的文档,我知道只要当前小部件失去焦点,就应该更新模型,但事实并非如此。无论我在编辑字段中输入什么,模型的setData方法都不会被调用。 即使我在列表视图中编辑项目,行编辑的内容也不会改变。

我发现当我将文本字段的textChanged信号连接到映射器的submit方法时,一切正常,但是dataChanged方法被调用了 3 次,我不明白为什么。
更奇怪的是,现在每当我在列表视图中编辑项目时,文本字段的内容都会更新,尽管连接到textChanged信号(至少我认为如此)只是一种单向连接。

我究竟做错了什么?我显然遗漏了一些东西,因为如果这是正确的方法, QDataWidgetMapperSubmitPolicy将完全没用。

4

1 回答 1

1

您的映射器__init__一返回就会被删除,因为它没有持久引用。这是 PyQt 中 Qt 对象的一个​​常见错误,通常是由于即使没有python引用,添加到父级或布局的小部件也会持久化,但事实是,将小部件添加到布局实际上会创建持久引用 (在 Qt 术语中,父小部件获得“所有权”),从而防止垃圾收集。

只需使其成为实例成员或添加父参数:

    self.mapper = QDataWidgetMapper()
    # alternatively (which is "safer" from the Qt point of view):
    mapper = QDataWidgetMapper(self)
    # but since you will probably need further access in any case:
    self.mapper = QDataWidgetMapper(self)
于 2021-04-16T12:46:38.777 回答