繁体   English   中英

pyqt QAbstractItemDelegate 在 QListView 中显示不正确

[英]pyqt QAbstractItemDelegate not displaying correctly in QListView

我试图了解代理在 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_())

问题截图

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

谢谢,马克

具体问题是您将targetOffset与几何一起使用,而您应该只设置大小并平移画家:

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

不幸的是,这只是错误问题的正确解决方案,因为您遇到了一个更大的问题:您不断创建NameAge的新实例,这是非常错误的,原因有两个:

  1. paint()sizeHint()的调用非常频繁,这意味着您将创建数百(或数千)次新的小部件,即使只是用鼠标悬停项目或调整视图大小时也是如此;
  2. 使用父级创建小部件意味着只要父级存在,它们就会存在; 使用您的代码,您可以轻松获得数千个未使用的实例;

您的代码存在的另一个问题是您正在使用错误的父self.parent()创建编辑器。 这是错误的,原因有二:

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

现在,避免上述所有情况取决于您的需要。

我怀疑您只是希望即使用户没有编辑它也能显示编辑器,因此正确的解决方案是使用持久性编辑器,这可以通过调用openPersistentEditor()来实现。

对于像 QListView 这样的简单视图,这非常简单(QTableView 或 QTreeView 可能需要对每一列使用不同的委托):只需确保每次向模型添加新行时视图都打开持久性编辑器,这很容易完成通过将rowsInserted信号连接到调用openPersistentEditor()的函数。

然后,为了获得正确的行为(包括大小提示),您必须根据索引保留对编辑器的引用。 请注意,基本索引(QModelIndex 实例)是“易变的”,如文档所述:

注意:模型索引应立即使用,然后丢弃。 在调用更改模型结构或删除项目的模型函数后,您不应依赖索引保持有效。 如果您需要随时间保留模型索引,请使用 QPersistentModelIndex。

因此,为了在索引和编辑器之间有正确的引用,您必须在字典中使用QPersistentModelIndex ,并最终删除键/值对以防编辑器被破坏。

另请注意,编辑器可能会更改项目的默认大小提示(尤其是在新索引之后创建的项目)。 为此,您必须在创建编辑器时发出sizeHintChanged

另一个需要注意的重要方面是 QObjects 支持用户属性,这是任何对象的默认属性。 该属性在 Qt 中用于轻松设置该对象最重要的“方面”。

例如,QLineEdit 将其text属性设置为用户一,而 QSpinBox 使用value Qt 委托自动设置和获取那些“用户属性”,并在读取或写入模型时应用它们。

如果你为你的编辑器正确地实现了一个 qt 属性,这会简化事情。 唯一要注意的是属性通常是基本对象(因此,不支持不同类型的元组,如您的情况)。 解决方案是只实现需要的东西,在本例中是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_())

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM