PyQt - 如何使用QItemDelegate在表格视图中设置QComboBox

9
我正在尝试在表格中显示一个组合框,以便我可以像表格中的其他单元格一样从表格模型中设置选定的索引。我从其他示例中拼凑了这个示例,但仍然无法理解如何交互来设置QComboBox的选定索引。
这是我能想到的最简单的示例来演示问题。如果有人能演示如何从模型数据自动设置索引?还有如何使用'currentIndexChanged'信号,因为每次重新绘制时似乎都会触发它?谢谢。
# The following tells SIP (the system that binds Qt's C++ to Python)
# to return Python native types rather than QString and QVariant
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)


from PyQt4 import QtCore, QtGui

class TableModel(QtCore.QAbstractTableModel):
    """
    A simple 5x4 table model to demonstrate the delegates
    """
    def rowCount(self, parent=QtCore.QModelIndex()): return 5
    def columnCount(self, parent=QtCore.QModelIndex()): return 4

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid(): return None
        if not role==QtCore.Qt.DisplayRole: return None
        return "{0:02d}".format(index.row())


class ComboDelegate(QtGui.QItemDelegate):
    """
    A delegate that places a fully functioning QComboBox in every
    cell of the column to which it's applied
    """
    def __init__(self, parent):

        QtGui.QItemDelegate.__init__(self, parent)

    def paint(self, painter, option, index):

        self.combo = QtGui.QComboBox(self.parent())
        self.connect(self.combo, QtCore.SIGNAL("currentIndexChanged(int)"), self.parent().currentIndexChanged)

        li = []
        li.append("Zero")
        li.append("One")
        li.append("Two")
        li.append("Three")
        li.append("Four")
        li.append("Five")

        self.combo.addItems(li)

        if not self.parent().indexWidget(index):
            self.parent().setIndexWidget(
                index, 
                self.combo
            )

class TableView(QtGui.QTableView):
    """
    A simple table to demonstrate the QComboBox delegate.
    """
    def __init__(self, *args, **kwargs):
        QtGui.QTableView.__init__(self, *args, **kwargs)

        # Set the delegate for column 0 of our table
        # self.setItemDelegateForColumn(0, ButtonDelegate(self))
        self.setItemDelegateForColumn(0, ComboDelegate(self))

    @QtCore.pyqtSlot()
    def currentIndexChanged(self, ind):
        print "Combo Index changed {0} {1} : {2}".format(ind, self.sender().currentIndex(), self.sender().currentText())

if __name__=="__main__":
    from sys import argv, exit

    class Widget(QtGui.QWidget):
        """
        A simple test widget to contain and own the model and table.
        """
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)

            l=QtGui.QVBoxLayout(self)
            self._tm=TableModel(self)
            self._tv=TableView(self)
            self._tv.setModel(self._tm)
            l.addWidget(self._tv)

    a=QtGui.QApplication(argv)
    w=Widget()
    w.show()
    w.raise_()
    exit(a.exec_())
1个回答

24
您正在不正确地使用paint方法。当您想要更改视图的显示行为时才应该使用它。而且每次想要绘制小部件时创建新的小部件非常昂贵。但是,由于您想要更改编辑行为,因此需要更改整个程序的逻辑。
请参见修复后的代码。下面我将解释更改。 1.首先,我们需要使第一列可编辑。你可以通过重新实现QAbstractItemModel::flags来实现:
def flags(self, index):
    if (index.column() == 0):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
    else:
        return QtCore.Qt.ItemIsEnabled

2. 默认情况下,当用户双击项目时,将创建项目编辑器。如果您希望默认显示所有组合框,则可以使用openPersistentEditor

for row in range(0, self._tm.rowCount()):
    self._tv.openPersistentEditor(self._tm.index(row, 0))

请注意,您还应打开新创建单元格的编辑器(如果有)。
3. 现在回到我们的代理。我们需要实现createEditor方法,当视图请求单元格的编辑器时,该方法将被自动调用:
def createEditor(self, parent, option, index):
    combo = QtGui.QComboBox(parent)
    li = []
    li.append("Zero")
    li.append("One")
    li.append("Two")
    li.append("Three")
    li.append("Four")
    li.append("Five")
    combo.addItems(li)
    self.connect(combo, QtCore.SIGNAL("currentIndexChanged(int)"), 
                 self, QtCore.SLOT("currentIndexChanged()"))
    return combo

请注意,connect位于append之下,因为我们需要在初始化时避免currentIndexChanged信号的触发。
实现setEditorData方法,当视图中的模型数据被更改时,该方法将被调用。同时,它也会在编辑器初始化时被调用。
def setEditorData(self, editor, index):
    editor.blockSignals(True)
    editor.setCurrentIndex(int(index.model().data(index)))
    editor.blockSignals(False)

再次提醒,我们希望避免非用户引起的信号,因此我们使用blockSignals

5. 在槽函数中,我们只需发出commitData信号,这将导致视图调用我们的委托的setModelData

@QtCore.pyqtSlot()
def currentIndexChanged(self):
    self.commitData.emit(self.sender())

6. 实现 setModelData 方法:

def setModelData(self, editor, model, index):
    model.setData(index, editor.currentIndex())

7. 你的模型需要支持数据变更。因此,我们应该实现模型的 setData 方法:

def setData(self, index, value, role=QtCore.Qt.DisplayRole):
    print "setData", index.row(), index.column(), value
    # todo: remember the data

我已经添加了一个关于数据模型的后续问题在这里,而不是扩展这个问题太多。 - C Mars
还有一个关于在视图中添加复选框列的问题,请参见这里 - C Mars
我知道已经过了很长时间,但是我在实现你的解决方案时遇到了问题。我已经在这里发布了一个后续问题(https://stackoverflow.com/questions/51945016/pyqt-qcombobox-in-qtableview)。 - CodingCat
谢谢,这对我有用!关于调用openPersistentEditor(),可以在委托的paint()函数中调用它,而不是循环调用。 - Alexandros Gouvatsos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接