python-3.x pyqt QAbstractItemDelegate未在QListView中正确显示

juud5qan  于 2022-12-24  发布在  Python
关注(0)|答案(1)|浏览(140)

我试着弄清楚代理在pyqt中是如何工作的,并写了下面的代码来尝试它们。但是我不明白为什么paint方法看起来不能正确地排列项目中的NameAge小部件。有人能给我建议吗?

import sys

from PyQt5.QtCore import (
    Qt, QAbstractListModel, QModelIndex)
from PyQt5.QtWidgets import (
    QApplication, QWidget, QListView, QAbstractItemDelegate,
    QHBoxLayout, QLineEdit, QSpinBox, QVBoxLayout)

class NameAge(QWidget):
    def __init__(self, name='', age=0, parent=None):
        super().__init__(parent)

        self.name_edit = QLineEdit(name)
        self.age_spinbox = QSpinBox()
        self.age_spinbox.setValue(age)

        layout = QHBoxLayout()
        layout.addWidget(self.name_edit)
        layout.addWidget(self.age_spinbox)

        self.setLayout(layout)

class NameAgeModel(QAbstractListModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.data = data

    def rowCount(self, parent=QModelIndex()):
        return len(self.data)

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return self.data[index.row()]
        elif role == Qt.EditRole:
            return self.data[index.row()]
        
    def setData(self, index, value, role):
        if role == Qt.EditRole:
            self.data[index.row()] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled

class NameAgeDelegate(QAbstractItemDelegate):
    def setEditorData(self, editor, index):
        name, age = index.data()
        editor.name_edit.setText(name)
        editor.age_spinbox.setValue(age)

    def setModelData(self, editor, model, index):
        name = editor.name_edit.text()
        age = editor.age_spinbox.value()
        model.setData(index, (name, age), Qt.EditRole)

    def createEditor(self, parent, option, index):
        name, age = index.data(Qt.DisplayRole)
        name_age = NameAge(name, age, self.parent())
        name_age.setGeometry(option.rect)
        return name_age

    def paint(self, painter, option, index):
        name, age = index.data(Qt.DisplayRole)
        name_age = NameAge(name, age, self.parent())
        name_age.setGeometry(option.rect)
        name_age.render(painter, option.rect.topLeft())

    def sizeHint(self, option, index):
        name, age = index.data(Qt.DisplayRole)
        name_age = NameAge(name, age, self.parent())
        return name_age.sizeHint()

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

class MainWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.list_view = QListView()
        self.list_view.setItemDelegate(NameAgeDelegate(self.list_view))
        self.list_view.setModel(NameAgeModel(
            [('Mark', 38), ('John', 30), ('Jane', 25)]))

        layout.addWidget(self.list_view)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_widget = MainWidget()
    main_widget.show()
    sys.exit(app.exec_())

任何建议将不胜感激抱歉,因为找不到很多人试图这样做的例子。
谢谢,马克

knpiaxh1

knpiaxh11#

具体的问题是,您将targetOffset与几何体沿着使用,而您应该只设置大小并平移绘图工具:

name_age.resize(option.rect.size())
        painter.save()
        painter.translate(option.rect.topLeft())
        name_age.render(painter)
        painter.restore()

不幸的是,这只是错误问题的正确解决方案,因为您面临的问题要大得多:你 * 不断地 * 创建NameAge的新示例,这是非常错误的,原因有二:
1.对paint()sizeHint()的调用极其频繁,这意味着您将创建数百(或数千)次新的小部件,即使只是用鼠标悬停项目或调整视图大小;
1.创建具有父控件意味着只要父控件存在,它们就将存在;有了你的代码,你可以很容易地得到 * 数千 * 个未使用的示例;
代码中的另一个问题是使用了错误的父级self.parent()创建编辑器。这是错误的,原因有两个:

  • createEditor()已经提供了父视图,即视图的viewport(不是视图!);
  • 委托构造函数的父参数是可选的,甚至可以不是QWidget而是普通的QObject(例如QApplication);这意味着:
  • 如果委托的父对象是QObject而不是QWidget,那么您的代码将崩溃,因为QWidget只接受另一个QWidget或None作为父对象;
  • 如果父窗口是None,则编辑器将显示为新的顶层窗口;

现在,避免以上所有取决于您的需要。
我怀疑您只是想让编辑器即使在用户不编辑它的时候也显示出来,所以正确的解决方案是使用一个 * 持久编辑器 *,这可以通过调用openPersistentEditor()来实现。
对于QListView这样的简单视图,这是非常简单的(QTableView或QTreeView可能需要对每列使用不同的委托):只需确保每次向模型添加新行时视图都打开一个持久编辑器,这可以通过将rowsInserted信号连接到调用openPersistentEditor()的函数来轻松完成。
然后,为了获得正确的行为(包括大小提示),您必须基于索引保留编辑器的引用。注意,基本索引(QModelIndex示例)是“volatile”的,正如文档所指出的:
注意:应立即使用模型索引,然后将其丢弃。调用更改模型结构或删除项的模型函数后,不应依赖索引来保持有效。如果需要随时间保留模型索引,请使用QPersistentModelIndex。
因此,为了在索引和编辑器之间有正确的引用,必须在字典中使用QPersistentModelIndex,并最终删除键/值对,以防编辑器被破坏。
还要注意,编辑器可能会更改条目的默认大小提示(特别是在新索引 * 之后 * 创建的条目),为此,必须在创建编辑器时发出sizeHintChanged
另一个需要注意的重要方面是QObject支持 *user属性 *,这是任何对象的默认属性,在Qt中使用该属性可以轻松设置对象最重要的“方面”。
例如,QLineEdit将其text属性设置为用户1,而QSpinBox使用value.Qt委托自动设置和获取这些“用户属性”,并在阅读或写入模型时应用它们。
如果你为你的编辑器正确地实现了一个qt属性,这会简化一些事情。唯一需要注意的是,属性通常是 basic 对象(所以,不同类型的元组,就像你的例子一样,是不受支持的)。解决方案是只实现需要的,在这个例子中,就是setModelData()

import sys

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class NameAge(QWidget):
    changed = pyqtSignal()
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.name_edit = QLineEdit()
        self.age_spinbox = QSpinBox()

        layout = QHBoxLayout()
        layout.addWidget(self.name_edit)
        layout.addWidget(self.age_spinbox)

        self.setLayout(layout)

        self.name_edit.installEventFilter(self)
        self.age_spinbox.installEventFilter(self)

        # alternatively, you can skip the whole event filter aspect, and just
        # connect to the textChanged and valueChanged of the above widgets to
        # the changed signal; this will potentially update the model whenever
        # the value of those widgets is changed by the user.

    @pyqtProperty(object, user=True)
    def data(self):
        return self.name_edit.text(), self.age_spinbox.value()

    @data.setter
    def data(self, data):
        if data is None:
            return
        try:
            name, age = data
            self.name_edit.setText(name)
            self.age_spinbox.setValue(age)
        except (ValueError, TypeError) as e:
            raise e

    def setData(self, data):
        self.data = data

    def eventFilter(self, obj, event):
        if (
            event.type() == event.FocusOut
            and not self.isAncestorOf(QApplication.focusWidget())
        ):
            self.changed.emit()
        return super().eventFilter(obj, event)

class NameAgeModel(QAbstractListModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.data = data

    def rowCount(self, parent=QModelIndex()):
        return len(self.data)

    def data(self, index, role=Qt.DisplayRole):
        if role in (Qt.DisplayRole, Qt.EditRole):
            return self.data[index.row()]
        
    def setData(self, index, value, role):
        if role == Qt.EditRole:
            self.data[index.row()] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled

class NameAgeDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.editors = {}

    def setModelData(self, editor, model, index):
        model.setData(index, editor.data, Qt.EditRole)

    def createEditor(self, parent, option, index):
        name_age = NameAge(parent=parent)

        pIndex = QPersistentModelIndex(index)
        if pIndex in self.editors:
            self.editors[pIndex].deleteLater()
        self.editors[pIndex] = name_age

        name_age.changed.connect(lambda: self.commitData.emit(name_age))
        name_age.destroyed.connect(lambda: self.editors.pop(pIndex))

        self.sizeHintChanged.emit(index)
        return name_age

    def sizeHint(self, option, index):
        editor = self.editors.get(QPersistentModelIndex(index))
        if editor:
            return editor.sizeHint()
        return super().sizeHint(option, index)

    def updateEditorGeometry(self, editor, option, index):
        # required to avoid some quirks for custom editors when committing data
        editor.setGeometry(option.rect)

class MainWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.list_view = QListView()
        self.list_view.setItemDelegate(NameAgeDelegate(self.list_view))
        model = NameAgeModel([('Mark', 38), ('John', 30), ('Jane', 25)])
        self.list_view.setModel(model)

        layout.addWidget(self.list_view)

        for row in range(model.rowCount()):
            self.list_view.openPersistentEditor(model.index(row, 0))

        model.rowsInserted.connect(lambda p, row, _: 
            self.list_view.openPersistentEditor(model.index(row, 0)))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_widget = MainWidget()
    main_widget.show()
    sys.exit(app.exec_())

相关问题