[英]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
的新实例,这是非常错误的,原因有两个:
paint()
和sizeHint()
的调用非常频繁,这意味着您将创建数百(或数千)次新的小部件,即使只是用鼠标悬停项目或调整视图大小时也是如此; 您的代码存在的另一个问题是您正在使用错误的父self.parent()
创建编辑器。 这是错误的,原因有二:
createEditor()
已经提供了父级,它是视图的视口(不是视图;);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.