【问题标题】:PyQt - simplest working example of a combobox inside QTableViewPyQt - QTableView 内组合框的最简单工作示例
【发布时间】:2013-08-06 18:24:07
【问题描述】:

背景:我在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_()

【问题讨论】:

  • 您可能会发现我对this question 的回答很有帮助。
  • 谢谢,现在我看到paint 覆盖是不必要的,我需要openPersistentEditor。但是,如果我需要从模型外部调用它,调用 openPersistentEditor 似乎违背了模型/视图的目的。另外,当您一次只能操作一个时,绘制所有这些组合框似乎效率低下。有没有办法摆脱双击要求,以便在单元格选择时出现?
  • 你不需要从模型中调用它。您可以使用另一个对象(例如您的子类视图或表单)来跟踪模型更改并在必要时调用编辑器。对于第二个问题,将view->selectionModel()selectionChanged 信号连接到您的插槽。在此插槽中,在选定单元格中打开编辑器,并在必要时关闭以前的编辑器。

标签: python qt pyqt pyqt4


【解决方案1】:

使用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()

【讨论】:

  • 该问题要求在 QTableView 中使用组合框,而不是 QTableWidget。
  • @ekhumoro 是的,我看到了,但我认为他只是想要一种在表格中获取小部件的方法。我可能会删除这个答案。
【解决方案2】:

如果有人感兴趣,下面是针对 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_()

【讨论】:

  • 虽然这可行,但在更改列大小时会出现图形故障(而不是不断更改组合框的大小,组合框保持在顶部,而另一个组合框正在背景中绘制 - 这个ComboBox 显示了预期的行为,因此只需删除顶部的组合框。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    你可以试试这样的。

    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 子类化,以便与委托“正确”工作(编辑、导航、更新数据等),但我找不到该帖子:(

    希望对您有所帮助。

    【讨论】:

      【解决方案5】:

      这应该可行:

      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)
      

      【讨论】:

        【解决方案6】:

        这是我的 PyQt5 版本。它基于ToddP 的工作(this answer)并解决了更多问题:

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

        总而言之,我发现这种方法的用户体验很差。当组合框获得焦点时,它开始吞噬各种事件。没有明确的迹象表明它具有焦点。当弹出窗口可见时,您不能再跳到下一个单元格。与 QTableWidget 中真正的 QComboBox 小部件相比,它只是感觉很笨重。

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

        另见:

        【讨论】:

          猜你喜欢
          • 2013-11-14
          • 2014-07-12
          • 1970-01-01
          • 2021-03-16
          • 1970-01-01
          • 2011-11-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多