PyQt - самый простой рабочий пример комбинированного списка внутри QTableView
Справочная информация: я не могу найти полный рабочий пример комбинированного списка внутри QTableView
, Поэтому я написал этот код на основе нескольких других более надуманных примеров. Проблема, однако, в том, что в этом примере необходимо дважды щелкнуть на поле со списком, прежде чем он станет активным, а затем вы должны нажать еще раз, чтобы раскрыть его. Это не очень удобно для пользователя. Если я делаю не модель / представление вещей, используя QTableWidget
, выпадающий список выпадает на первый клик.
Вопрос: Может ли кто-нибудь взглянуть на это и сказать мне, что нужно сделать, чтобы он реагировал так же, как QTableWidget
? Кроме того, если что-то, что я делаю, не нужно, пожалуйста, укажите это тоже. Например, необходимо ли ссылаться на стиль приложения?
import sys
from PyQt4 import QtGui, QtCore
rows = "ABCD"
choices = ['apple', 'orange', 'banana']
class Delegate(QtGui.QItemDelegate):
def __init__(self, owner, items):
super(Delegate, self).__init__(owner)
self.items = items
def createEditor(self, parent, option, index):
self.editor = QtGui.QComboBox(parent)
self.editor.addItems(self.items)
return self.editor
def paint(self, painter, option, index):
value = index.data(QtCore.Qt.DisplayRole).toString()
style = QtGui.QApplication.style()
opt = QtGui.QStyleOptionComboBox()
opt.text = str(value)
opt.rect = option.rect
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt, painter)
QtGui.QItemDelegate.paint(self, painter, option, index)
def setEditorData(self, editor, index):
value = index.data(QtCore.Qt.DisplayRole).toString()
num = self.items.index(value)
editor.setCurrentIndex(num)
def setModelData(self, editor, model, index):
value = editor.currentText()
model.setData(index, QtCore.Qt.DisplayRole, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class Model(QtCore.QAbstractTableModel):
def __init__(self):
super(Model, self).__init__()
self.table = [[row, choices[0]] for row in rows]
def rowCount(self, index=QtCore.QModelIndex()):
return len(self.table)
def columnCount(self, index=QtCore.QModelIndex()):
return 2
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return self.table[index.row()][index.column()]
def setData(self, index, role, value):
if role == QtCore.Qt.DisplayRole:
self.table[index.row()][index.column()] = value
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.model = Model()
self.table = QtGui.QTableView()
self.table.setModel(self.model)
self.table.setItemDelegateForColumn(1, Delegate(self, ["apple", "orange", "banana"]))
self.setCentralWidget(self.table)
self.setWindowTitle('Delegate Test')
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
app.exec_()
5 ответов
Использование QTableWiget.setCellWidget
import sys
from PyQt4 import QtGui
app = QtGui.QApplication(sys.argv)
table = QtGui.QTableWidget(1,1)
combobox = QtGui.QComboBox()
combobox.addItem("Combobox item")
table.setCellWidget(0,0, combobox)
table.show()
app.exec()
Если кому-то интересно, ниже приведен тот же пример, модифицированный для PyQt5 и Python 3. Основные обновления включают в себя:
- Python 3:
super().__init__()
- PyQt5: большинство классов находятся в
QtWidgets
;QtGui
не нужен для этого примера Model.setData
: порядок ввода аргументов изменен на:index, value, role
, а такжеTrue
вернулся вместоNone
- Поле со списком
choices
и содержимое таблицы теперь указано внутриMain
; это делаетDelegate
а такжеModel
более общий
from PyQt5 import QtWidgets, QtCore
class Delegate(QtWidgets.QItemDelegate):
def __init__(self, owner, choices):
super().__init__(owner)
self.items = choices
def createEditor(self, parent, option, index):
self.editor = QtWidgets.QComboBox(parent)
self.editor.addItems(self.items)
return self.editor
def paint(self, painter, option, index):
value = index.data(QtCore.Qt.DisplayRole)
style = QtWidgets.QApplication.style()
opt = QtWidgets.QStyleOptionComboBox()
opt.text = str(value)
opt.rect = option.rect
style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt, painter)
QtWidgets.QItemDelegate.paint(self, painter, option, index)
def setEditorData(self, editor, index):
value = index.data(QtCore.Qt.DisplayRole)
num = self.items.index(value)
editor.setCurrentIndex(num)
def setModelData(self, editor, model, index):
value = editor.currentText()
model.setData(index, QtCore.Qt.DisplayRole, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class Model(QtCore.QAbstractTableModel):
def __init__(self, table):
super().__init__()
self.table = table
def rowCount(self, parent):
return len(self.table)
def columnCount(self, parent):
return len(self.table[0])
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return self.table[index.row()][index.column()]
def setData(self, index, value, role):
if role == QtCore.Qt.EditRole:
self.table[index.row()][index.column()] = value
return True
class Main(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
# set combo box choices:
choices = ['apple', 'orange', 'banana']
# create table data:
table = []
table.append(['A', choices[0]])
table.append(['B', choices[0]])
table.append(['C', choices[0]])
table.append(['D', choices[0]])
# create table view:
self.model = Model(table)
self.tableView = QtWidgets.QTableView()
self.tableView.setModel(self.model)
self.tableView.setItemDelegateForColumn(1, Delegate(self,choices))
# make combo boxes editable with a single-click:
for row in range( len(table) ):
self.tableView.openPersistentEditor(self.model.index(row, 1))
# initialize
self.setCentralWidget(self.tableView)
self.setWindowTitle('Delegate Test')
self.show()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
app.exec_()
Если вы пытаетесь настроить, когда представление отображает редактор, вам нужно изменить триггер редактирования, как определено в QAbstractItemView. По умолчанию это редактирование на doubleClick, но я думаю, что вы ищете QAbstractItemView.CurrentChanged. Установите это, вызывая myView.setEditTrigger()
Вы можете попробовать что-то вроде этого.
import sys
from PyQt4 import QtGui, QtCore
rows = "ABCD"
choices = ['apple', 'orange', 'banana']
class Delegate(QtGui.QItemDelegate):
def __init__(self, owner, items):
super(Delegate, self).__init__(owner)
self.items = items
def createEditor(self, parent, option, index):
self.editor = QtGui.QComboBox(parent)
self.editor.addItems(self.items)
return self.editor
def paint(self, painter, option, index):
value = index.data(QtCore.Qt.DisplayRole).toString()
style = QtGui.QApplication.style()
opt = QtGui.QStyleOptionComboBox()
opt.text = str(value)
opt.rect = option.rect
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt, painter)
QtGui.QItemDelegate.paint(self, painter, option, index)
def setEditorData(self, editor, index):
value = index.data(QtCore.Qt.DisplayRole).toString()
num = self.items.index(value)
editor.setCurrentIndex(num)
if index.column() == 1: #just to be sure that we have a QCombobox
editor.showPopup()
def setModelData(self, editor, model, index):
value = editor.currentText()
model.setData(index, QtCore.Qt.DisplayRole, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class Model(QtCore.QAbstractTableModel):
def __init__(self):
super(Model, self).__init__()
self.table = [[row, choices[0]] for row in rows]
def rowCount(self, index=QtCore.QModelIndex()):
return len(self.table)
def columnCount(self, index=QtCore.QModelIndex()):
return 2
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return self.table[index.row()][index.column()]
def setData(self, index, role, value):
if role == QtCore.Qt.DisplayRole:
self.table[index.row()][index.column()] = value
return True
else:
return False
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.model = Model()
self.table = QtGui.QTableView()
self.table.setModel(self.model)
self.table.setEditTriggers(QtGui.QAbstractItemView.CurrentChanged) # this is the one that fits best to your request
self.table.setItemDelegateForColumn(1, Delegate(self, ["apple", "orange", "banana"]))
self.setCentralWidget(self.table)
self.setWindowTitle('Delegate Test')
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
app.exec_()
Как видите, я только что добавил пару строк в ваш код. Представление управляет "редакцией", поэтому вы должны изменить триггеры редакции. Затем, когда вы устанавливаете данные делегата, заставьте делегата показывать всплывающее окно из виджета.
Некоторое время назад я прочитал сообщение в блоге, в котором автор разделил QAbstractItemView на "правильную" работу с делегатами (издание, навигация, обновление данных и т. Д.), Но я не могу найти сообщение:(
Надеюсь, поможет.
Это должно работать:
view = QTreeView()
model = QStandardItemModel(view)
view.setModel(model)
combobox = QComboBox()
child1 = QStandardItem('test1')
child2 = QStandardItem('test2')
child3 = QStandardItem('test3')
model.appendRow([child1, child2, child3])
a = model.index(0, 2)
view.setIndexWidget(a, combobox)
Вот моя версия для PyQt5. Он основан на работе ToddP( этот ответ) и решает еще несколько проблем:
- Поддержка текста и значения
- Использует
Qt.EditRole
обновить модель - Показывать всплывающее окно при нажатии на виджет
В общем, я считаю, что такой подход неудовлетворителен. Когда поле со списком получает фокус, оно начинает поглощать все виды событий. Нет четких указаний на то, что он находится в фокусе. Когда всплывающее окно отображается, вы больше не можете перейти к следующей ячейке. Это просто кажется неуклюжим по сравнению с реальным виджетом QComboBox внутри QTableWidget.
Поскольку у меня небольшая таблица (< 100 строк), я переключился на QTableWidget. Теперь я могу сам создавать виджеты и помещать их в таблицу, используяsetCellWidget()
. Это значительно упрощает добавление особого поведения (например, переход к следующей / предыдущей ячейке при нажатии влево / вправо в начале / конце текста вQLineEdit
).
class ComboBoxDelegate(QtWidgets.QItemDelegate):
def __init__(self, choices, parent=None):
super().__init__(parent)
self.choices = choices
self.valueIndex = {
self.choices[i][1]: i
for i in range(len(self.choices))
}
def createEditor(self, parent, option, index):
self.editor = QtWidgets.QComboBox(parent)
for text, value in self.choices:
self.editor.addItem(text, value)
QTimer.singleShot(0, self.showPopup)
return self.editor
@QtCore.pyqtSlot()
def showPopup(self):
self.editor.showPopup()
def paint(self, painter, option, index):
value = index.data(QtCore.Qt.DisplayRole)
style = QtWidgets.QApplication.style()
opt = QtWidgets.QStyleOptionComboBox()
opt.text = str(value)
opt.rect = option.rect
style.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt, painter)
style.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt, painter)
QtWidgets.QItemDelegate.paint(self, painter, option, index)
def setEditorData(self, editor, index):
value = index.data(QtCore.Qt.EditRole)
num = self.valueIndex[value]
editor.setCurrentIndex(num)
def setModelData(self, editor, model, index):
value = editor.currentData()
model.setData(index, value, QtCore.Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
Вы захотите использовать
table.setEditTriggers(QAbstractItemView.CurrentChanged) # Edit on first click
(см. этот ответ)
Смотрите также: