在 QTableView 中正确使用 QComboBox - 设置数据和清除 QComboBoxes 的问题

Using QComboBox in QTableView properly - issues with data being set and clearing QComboBoxes

在我的应用程序中,我使用 QTableView、QStandardItemModel 和中间的 QSortFilterProxyModel 进行过滤。

内容通过第 1 列和第 2 列的方法更新,我希望有第 3 列供用户选择 select。我更愿意使用 QComboBox。

我已经完成了所有工作,除了当我 select QComboBox 中的项目在第 3 列的任何单元格中时,它不会填充。跟我的setModelData()方法有关系吗?

我还有一个清除按钮,我想将所有 QComboBoxes 重置为第一个项目,这是一个空条目。我不确定如何解决这个问题,我发现使用 deleteLater() 或将 QTableView 的 setItemDelegateForColumn() 设置为 None 并重新申请。

显然这些都不是最有效的。我错过了什么?

工作示例:

import win32com.client
from PyQt5 import QtCore, QtGui, QtWidgets

outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries

class ComboDelegate(QtWidgets.QItemDelegate):

    def __init__(self,parent=None):
        super().__init__(parent)
        self.items = ['','To', 'CC']

    def createEditor(self, widget, option, index):
        editor = QtWidgets.QComboBox(widget)
        editor.addItems(self.items)
        return editor

    def setEditorData(self, editor, index):
        if index.column() == 2:
            editor.blockSignals(True)
            text = index.model().data(index, QtCore.Qt.EditRole)
            try:
                i = self.items.index(text)
            except ValueError:
                i = 0
            editor.setCurrentIndex(i)
            editor.blockSignals(False)
        else:
            QtWidgets.QItemDelegate.setModelData(editor,model,index)

    def setModelData(self, editor, model, index):
        if index.column() == 2:
            model.setData(index, editor.currentText())
        else:
            QtWidgets.QItemDelegate.setModelData(editor,model,index)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

    def paint(self, painter, option, index):
        QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)

class App(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        """This method creates our GUI"""
        self.centralwidget = QtWidgets.QWidget()
        self.setCentralWidget(self.centralwidget)
        self.lay = QtWidgets.QVBoxLayout(self.centralwidget)
        self.filterEdit = QtWidgets.QLineEdit()
        self.filterEdit.setPlaceholderText("Type to filter name.")
        self.label = QtWidgets.QLabel("Select an option for each person:")
        self.button = QtWidgets.QPushButton("Test Button")
        self.button.clicked.connect(self.runButton)
        self.resetbutton = QtWidgets.QPushButton("Clear")
        self.resetbutton.clicked.connect(self.clear)
        self.lay.addWidget(self.filterEdit)
        self.lay.addWidget(self.label)

        self.tableview=QtWidgets.QTableView(self.centralwidget)
        self.model=QtGui.QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['Name','Address','Option'])
        self.tableview.verticalHeader().hide()
        self.tableview.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
        self.tableview.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
        self.proxyModel = QtCore.QSortFilterProxyModel(self)
        self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxyModel.setSourceModel(self.model)
        self.proxyModel.sort(0,QtCore.Qt.AscendingOrder)
        self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.tableview.setModel(self.proxyModel)

        self.model.insertRow(self.model.rowCount(QtCore.QModelIndex()))
        #self.fillModel(self.model) #uncomment if you have outlook

        self.tableview.resizeColumnsToContents()
        self.tableview.verticalHeader().setDefaultSectionSize(10)
        self.filterEdit.textChanged.connect(self.onTextChanged)
        self.lay.addWidget(self.tableview)
        self.delegate = ComboDelegate()
        self.tableview.setItemDelegateForColumn(2, self.delegate)
        self.lay.addWidget(self.button)
        self.lay.addWidget(self.resetbutton)

        self.setMinimumSize(450, 200)
        self.setMaximumSize(1500, 200)
        self.setWindowTitle('Application')

    def clear(self):
        ###clear tableview comboboxes in column 3
        print("clear")

    def runButton(self,index):
        print("Do stuff")

    def fillModel(self,model):
        """Fills model from outlook address book """
        nameList = []
        addressList = []
        for row,entry in enumerate(entries):
            if entry.Type == "EX":
                user = entry.GetExchangeUser()
                if user is not None:
                    if len(user.FirstName) > 0 and len(user.LastName) > 0:
                        nameItem = QtGui.QStandardItem(str(user.Name))
                        emailItem = QtGui.QStandardItem(str(user.PrimarySmtpAddress))
                        nameItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
                        emailItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
                        model.appendRow([nameItem,emailItem])

    @QtCore.pyqtSlot(str)
    def onTextChanged(self, text):
        self.proxyModel.setFilterRegExp(text)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = App()
    w.show()
    sys.exit(app.exec_())

问题是您不必要地覆盖了 paint 方法,因为您不想自定义任何东西。在覆盖之前,我建议您了解它的作用,为此您可以使用文档或源代码。但总而言之,在 QItemDelegate 的情况下,paint 方法会在 "option" 中建立角色的信息,然后进行绘制,而该信息中就是文本。但在您的情况下,没有必要,因此无需覆盖。另一方面,如果您的委托具有建立 QComboBox 的唯一功能,那么您不必验证列。考虑到以上所有因素,我已将您的代表简化为:

class ComboDelegate(QtWidgets.QItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.items = ["", "To", "CC"]

    def createEditor(self, widget, option, index):
        editor = QtWidgets.QComboBox(widget)
        editor.addItems(self.items)
        return editor

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
        text = index.model().data(index, QtCore.Qt.EditRole)
        try:
            i = self.items.index(text)
        except ValueError:
            i = 0
        editor.setCurrentIndex(i)
        editor.blockSignals(False)

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

另一方面,QItemEditorFactory 使用 qproperty 用户作为更新的参数,在 QComboBox 的情况下是 "currentText",因此可以使用该信息进一步简化:

class ComboDelegate(QtWidgets.QItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.items = ["", "To", "CC"]

    def createEditor(self, widget, option, index):
        editor = QtWidgets.QComboBox(widget)
        editor.addItems(self.items)
        return editor

清除方法很简单:遍历第三列的所有行并设置空文本:

def clear(self):
    # clear tableview comboboxes in column 3
    for i in range(self.model.rowCount()):
        index = self.model.index(i, 2)
        self.model.setData(index, "")