PyQt - QTableView中组合框的最简单的工作示例

时间:2013-08-05 21:53:36

标签: python qt pyqt pyqt4

背景:我找不到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_()

6 个答案:

答案 0 :(得分: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()

答案 1 :(得分:1)

如果您在视图显示编辑器时尝试调整,则需要更改QAbstractItemView中定义的编辑触发器。默认是在doubleClick上编辑,但我认为你所追求的是QAbstractItemView.CurrentChanged。通过调用myView.setEditTrigger()

进行设置

答案 2 :(得分:0)

如果有人有兴趣,下面是针对PyQt5和Python 3修改的相同示例。主要更新包括:

  • Python 3:super().__init__()
  • PyQt5:大多数课程都在QtWidgets;此示例不需要QtGui
  • Model.setData:输入参数订单已更改为:index, value, role,而True已返回None
  • 组合框choices和现在在Main内指定的表格内容;这会使DelegateModel更加通用
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_()

答案 3 :(得分:0)

这应该有效:

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)

答案 4 :(得分:0)

你可以尝试这样的事情。

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_()

如您所见,我刚刚为您的代码添加了几行代码。 View管理"版本"所以你必须改变版本触发器。然后,当您设置委托数据时,强制委托显示窗口小部件的弹出窗口。

前段时间,我读了一篇博客文章,其中作者将QAbstractItemView子类化,以便正确地使用'与代表(版本,导航,更新数据等),但我找不到帖子:(

希望它有所帮助。

答案 5 :(得分:0)

这是我的PyQt5版本。它基于ToddP的工作(this answer),并解决了其他一些问题:

  • 支持文本和值
  • 使用Qt.EditRole更新模型
  • 单击窗口小部件后立即显示弹出窗口

总而言之,我发现这种方法的用户体验不好。当combox框获得焦点时,它开始吞噬所有类型的事件。没有明确的迹象表明它有重点。当弹出窗口可见时,您将无法再跳到下一个单元格。与QTableWidget内部的真实QComboBox小部件相比,它感觉笨拙。

由于我只有一个短表(<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

(请参阅此answer

另请参阅: