[英]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.