可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Background: I cannot find a full working example of a combobox inside a QTableView
. So I wrote this code based on several other more contrived examples out there. The problem is, however, that this example requires you to double-click on the combobox before it becomes enabled, then you have to click again to drop it down. It's not very user-friendly. If I do the non-model/view-thing using QTableWidget
, the combobox drops down on the first click.
Question: Can someone look at this and tell me what needs to be done to make it respond just like QTableWidget
? Also if there is anything that I'm doing that is unnecessary, please indicate that also. For example, is it absolutely necessary to reference the application style?
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_()
回答1:
Using 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()
回答2:
If you are trying to adjust when the view displays the editor, you need to change the edit trigger as defined in QAbstractItemView. The default is edit on doubleClick, but I think what you are after is QAbstractItemView.CurrentChanged. Set it by calling myView.setEditTrigger()
回答3:
If anyone is interested, below is the same example modified for PyQt5 and Python 3. Key updates include:
- Python 3:
super().__init__()
- PyQt5: most classes are in
QtWidgets
; QtGui
is not needed for this example
Model.setData
: input argument order changed to: index, value, role
, and True
returned instead of None
- Combo box
choices
and table contents now specified inside Main
; this makes Delegate
and Model
more general
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_()
回答4:
This should work:
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)
回答5:
You can try something like this.
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_()
As you can see, I just added a couple of lines to your code. The View manages the "edition" so you must change the edition triggers. Then when you set the delegate data, force the delegate to show the popup from the widget.
Some time ago, I read a blog post on which the author subclassed QAbstractItemView in order to work 'properly' with delegates (edition, navigation, updating data, etc), but I cant find the post :(
Hope it helps.