簡體   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