如何在 PyQt5 中使用活动的 QComboBox 作为 QListView 的元素?
How to use an active QComboBox as an element of QListView in PyQt5?
我正在使用PyQt5 制作应用程序。我的一个小部件将是一个 QListView,它显示所需项目的列表,例如例如,需要烹饪特定菜肴。
对于其中的大多数,列出的项目是唯一的可能性。但对于少数项目,有不止一种选择可以满足要求。对于那些有多种可能性的人,我想在功能性 QComboBox 中显示这些可能性。所以如果用户没有全脂牛奶,他们可以点击那个项目,然后看到 2% 的牛奶也可以。
如何在 QListView 的元素中包含工作组合框?
下面是一个示例,展示了我目前的情况。它可以在 Spyder 或使用 python -i 中工作,您只需按照说明进行注释或取消注释。 “工作”是指它在 QListView 中显示所需的项目,但组合框仅显示第一个选项,并且不能用鼠标更改它们的显示。但是,我可以说例如qb1.setCurrentIndex(1) 在 python 提示下,然后当我将鼠标指针移到小部件上时,显示更新为“2% 牛奶”。我发现能够在 Spyder 或 python 解释器中与小部件交互并检查它很有帮助,但我仍然有这个问题。我知道周围有这样的 C++ 示例,但我一直无法很好地理解它们来做我想做的事。如果我们可以 post 一个有效的 Python 示例,我相信它会对我和其他人有所帮助。
from PyQt5.QtWidgets import QApplication, QComboBox, QListView, QStyledItemDelegate
from PyQt5.QtCore import QAbstractListModel, Qt
# A delegate for the combo boxes.
class QBDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.drawText(option.rect, Qt.AlignLeft, self.parent().currentText())
# my own wrapper for the abstract list class
class PlainList(QAbstractListModel):
def __init__(self, elements):
super().__init__()
self.elements = elements
def data(self, index, role):
if role == Qt.DisplayRole:
text = self.elements[index.row()]
return text
def rowCount(self, index):
try:
return len(self.elements)
except TypeError:
return self.elements.rowCount(index)
app = QApplication([]) # in Spyder, this seems unnecessary, but harmless.
qb0 = 'powdered sugar' # no other choice
qb1 = QComboBox()
qb1.setModel(PlainList(['whole milk','2% milk','half-and-half']))
d1 = QBDelegate(qb1)
qb1.setItemDelegate(d1)
qb2 = QComboBox()
qb2.setModel(PlainList(['butter', 'lard']))
d2 = QBDelegate(qb2)
qb2.setItemDelegate(d2)
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegateForRow(1, d1)
QV.setItemDelegateForRow(2, d2)
QV.show()
app.exec_() # Comment this line out, to run in Spyder. Then you can inspect QV etc in the iPython console. Handy!
您的尝试存在一些误解。
首先,将委托父级设置为组合框,然后将委托设置为列表视图不会使委托显示组合框。
此外,作为 documentation clearly says:
Warning: You should not share the same instance of a delegate between views. Doing so can cause incorrect or unintuitive editing behavior since each view connected to a given delegate may receive the closeEditor()
signal, and attempt to access, modify or close an editor that has already been closed.
在任何情况下,将组合框添加到项目列表肯定不是一个选项:视图与它没有任何关系,覆盖 data()
以显示当前组合项目是不是有效的解决方案;虽然理论上项目数据可以包含任何类型的对象,但出于您的目的,模型应该包含 数据,而不是小部件。
为了显示不同的视图小部件,您必须覆盖 createEditor()
和 return 适当的小部件。
然后,由于您可能需要在访问模型和视图时保持数据可用,因此模型应包含可用选项,并最终 return 当前选项 或 "sub-list" 视情况而定。
最后,rowCount()
必须始终 return 模型的行数,而不是索引内容的行数。
一种可能性是为内部模型的选定选项创建一个支持“当前索引”的“嵌套模型”。
然后您可以使用 openPersistentEditor()
or implement flags()
并为包含列表模型的项目添加 Qt.ItemIsEditable
。
class QBDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
value = index.data(Qt.EditRole)
if isinstance(value, PlainList):
editor = QComboBox(parent)
editor.setModel(value)
editor.setCurrentIndex(value.currentIndex)
# submit the data whenever the index changes
editor.currentIndexChanged.connect(
lambda: self.commitData.emit(editor))
else:
editor = super().createEditor(parent, option, index)
return editor
def setModelData(self, editor, model, index):
if isinstance(editor, QComboBox):
# the default implementation tries to set the text if the
# editor is a combobox, but we need to set the index
model.setData(index, editor.currentIndex())
else:
super().setModelData(editor, model, index)
class PlainList(QAbstractListModel):
currentIndex = 0
def __init__(self, elements):
super().__init__()
self.elements = []
for element in elements:
if isinstance(element, (tuple, list)) and element:
element = PlainList(element)
self.elements.append(element)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.EditRole:
return self.elements[index.row()]
elif role == Qt.DisplayRole:
value = self.elements[index.row()]
if isinstance(value, PlainList):
return value.elements[value.currentIndex]
else:
return value
def flags(self, index):
flags = super().flags(index)
if isinstance(index.data(Qt.EditRole), PlainList):
flags |= Qt.ItemIsEditable
return flags
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
item = self.elements[index.row()]
if isinstance(item, PlainList):
item.currentIndex = value
else:
self.elements[index.row()] = value
return True
def rowCount(self, parent=None):
return len(self.elements)
app = QApplication([])
qb0 = 'powdered sugar' # no other choice
qb1 = ['whole milk','2% milk','half-and-half']
qb2 = ['butter', 'lard']
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegate(QBDelegate(QV))
## to always display the combo:
#for i in range(qlist.rowCount()):
# index = qlist.index(i)
# if index.flags() & Qt.ItemIsEditable:
# QV.openPersistentEditor(index)
QV.show()
app.exec_()
我正在使用PyQt5 制作应用程序。我的一个小部件将是一个 QListView,它显示所需项目的列表,例如例如,需要烹饪特定菜肴。
对于其中的大多数,列出的项目是唯一的可能性。但对于少数项目,有不止一种选择可以满足要求。对于那些有多种可能性的人,我想在功能性 QComboBox 中显示这些可能性。所以如果用户没有全脂牛奶,他们可以点击那个项目,然后看到 2% 的牛奶也可以。
如何在 QListView 的元素中包含工作组合框?
下面是一个示例,展示了我目前的情况。它可以在 Spyder 或使用 python -i 中工作,您只需按照说明进行注释或取消注释。 “工作”是指它在 QListView 中显示所需的项目,但组合框仅显示第一个选项,并且不能用鼠标更改它们的显示。但是,我可以说例如qb1.setCurrentIndex(1) 在 python 提示下,然后当我将鼠标指针移到小部件上时,显示更新为“2% 牛奶”。我发现能够在 Spyder 或 python 解释器中与小部件交互并检查它很有帮助,但我仍然有这个问题。我知道周围有这样的 C++ 示例,但我一直无法很好地理解它们来做我想做的事。如果我们可以 post 一个有效的 Python 示例,我相信它会对我和其他人有所帮助。
from PyQt5.QtWidgets import QApplication, QComboBox, QListView, QStyledItemDelegate
from PyQt5.QtCore import QAbstractListModel, Qt
# A delegate for the combo boxes.
class QBDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.drawText(option.rect, Qt.AlignLeft, self.parent().currentText())
# my own wrapper for the abstract list class
class PlainList(QAbstractListModel):
def __init__(self, elements):
super().__init__()
self.elements = elements
def data(self, index, role):
if role == Qt.DisplayRole:
text = self.elements[index.row()]
return text
def rowCount(self, index):
try:
return len(self.elements)
except TypeError:
return self.elements.rowCount(index)
app = QApplication([]) # in Spyder, this seems unnecessary, but harmless.
qb0 = 'powdered sugar' # no other choice
qb1 = QComboBox()
qb1.setModel(PlainList(['whole milk','2% milk','half-and-half']))
d1 = QBDelegate(qb1)
qb1.setItemDelegate(d1)
qb2 = QComboBox()
qb2.setModel(PlainList(['butter', 'lard']))
d2 = QBDelegate(qb2)
qb2.setItemDelegate(d2)
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegateForRow(1, d1)
QV.setItemDelegateForRow(2, d2)
QV.show()
app.exec_() # Comment this line out, to run in Spyder. Then you can inspect QV etc in the iPython console. Handy!
您的尝试存在一些误解。
首先,将委托父级设置为组合框,然后将委托设置为列表视图不会使委托显示组合框。
此外,作为 documentation clearly says:
Warning: You should not share the same instance of a delegate between views. Doing so can cause incorrect or unintuitive editing behavior since each view connected to a given delegate may receive the
closeEditor()
signal, and attempt to access, modify or close an editor that has already been closed.
在任何情况下,将组合框添加到项目列表肯定不是一个选项:视图与它没有任何关系,覆盖 data()
以显示当前组合项目是不是有效的解决方案;虽然理论上项目数据可以包含任何类型的对象,但出于您的目的,模型应该包含 数据,而不是小部件。
为了显示不同的视图小部件,您必须覆盖 createEditor()
和 return 适当的小部件。
然后,由于您可能需要在访问模型和视图时保持数据可用,因此模型应包含可用选项,并最终 return 当前选项 或 "sub-list" 视情况而定。
最后,rowCount()
必须始终 return 模型的行数,而不是索引内容的行数。
一种可能性是为内部模型的选定选项创建一个支持“当前索引”的“嵌套模型”。
然后您可以使用 openPersistentEditor()
or implement flags()
并为包含列表模型的项目添加 Qt.ItemIsEditable
。
class QBDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
value = index.data(Qt.EditRole)
if isinstance(value, PlainList):
editor = QComboBox(parent)
editor.setModel(value)
editor.setCurrentIndex(value.currentIndex)
# submit the data whenever the index changes
editor.currentIndexChanged.connect(
lambda: self.commitData.emit(editor))
else:
editor = super().createEditor(parent, option, index)
return editor
def setModelData(self, editor, model, index):
if isinstance(editor, QComboBox):
# the default implementation tries to set the text if the
# editor is a combobox, but we need to set the index
model.setData(index, editor.currentIndex())
else:
super().setModelData(editor, model, index)
class PlainList(QAbstractListModel):
currentIndex = 0
def __init__(self, elements):
super().__init__()
self.elements = []
for element in elements:
if isinstance(element, (tuple, list)) and element:
element = PlainList(element)
self.elements.append(element)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.EditRole:
return self.elements[index.row()]
elif role == Qt.DisplayRole:
value = self.elements[index.row()]
if isinstance(value, PlainList):
return value.elements[value.currentIndex]
else:
return value
def flags(self, index):
flags = super().flags(index)
if isinstance(index.data(Qt.EditRole), PlainList):
flags |= Qt.ItemIsEditable
return flags
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
item = self.elements[index.row()]
if isinstance(item, PlainList):
item.currentIndex = value
else:
self.elements[index.row()] = value
return True
def rowCount(self, parent=None):
return len(self.elements)
app = QApplication([])
qb0 = 'powdered sugar' # no other choice
qb1 = ['whole milk','2% milk','half-and-half']
qb2 = ['butter', 'lard']
qb3 = 'cayenne pepper' # there is no substitute
QV = QListView()
qlist = PlainList([qb0, qb1, qb2, qb3])
QV.setModel(qlist)
QV.setItemDelegate(QBDelegate(QV))
## to always display the combo:
#for i in range(qlist.rowCount()):
# index = qlist.index(i)
# if index.flags() & Qt.ItemIsEditable:
# QV.openPersistentEditor(index)
QV.show()
app.exec_()