如何使用 QCombobox 选择更新 QTableView 单元格?

How to update a QTableView cell with a QCombobox selection?

我想向某些 QTableView 行中的特定单元格添加委托 QComboBox 委托。我找到了几篇关于如何添加委托的帖子,但是 none 提供了使用 QComboBox 选择更新单元格的示例。

这是我目前拥有的:

main.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>350</width>
    <height>239</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QWidget" name="formLayoutWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>341</width>
     <height>231</height>
    </rect>
   </property>
   <layout class="QFormLayout" name="formLayout">
    <item row="0" column="1">
     <widget class="QPushButton" name="btnPopulate">
      <property name="text">
       <string>Populate Table</string>
      </property>
     </widget>
    </item>
    <item row="1" column="1">
     <widget class="QTableView" name="tableView"/>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

test.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os
from PyQt5 import uic, QtWidgets
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QDialog, QComboBox, QApplication

class GUI(QDialog):

    def __init__(self):
        super(GUI, self).__init__()
        dirname = os.path.dirname(os.path.abspath(__file__))
        uic.loadUi(os.path.join(dirname,'main.ui'), self)
        # button
        self.btnPopulate.clicked.connect(self.populate)
        # table model
        self.header = ['col1', 'col2', 'col3']
        self.QSModel = QStandardItemModel()
        self.QSModel.setColumnCount(3)
        self.QSModel.setHorizontalHeaderLabels(self.header)
        self.tableView.setModel(self.QSModel)
        # combobox delegate
        self.cbDelegate = QComboBox()
        self.cbDelegate.addItems(['choice1', 'choice2'])

    def populate(self):
        row = self.QSModel.rowCount()
        for x in range(0, 7):
            self.QSModel.insertRow(row)
            self.QSModel.setData(self.QSModel.index(row, 0), 'data')
            self.QSModel.item(row, 0).setEditable(True)
            self.QSModel.setData(self.QSModel.index(row, 1), 'data')
            self.QSModel.item(row, 1).setEditable(True)
            # add combobox delegate to even rows
            if x % 2 == 0:
                print('Delegate added.')
                index = self.tableView.model().index(row, 1)
                self.tableView.setIndexWidget(index, self.cbDelegate)
            self.QSModel.setData(self.QSModel.index(row, 2), 'data')
            self.QSModel.item(row, 1).setEditable(True)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = GUI()
    window.show()
    sys.exit(app.exec_())

但是,当我单击 Populate Table 按钮时,只有第一行中的第二个单元格显示 QComboBox。

  1. 我需要如何更改代码以在第 0、2、4 和 6 行或任何任意行中显示 QComboBox。 (是否显示QComboBox取决于reach行第一个cell的值。)

  2. 用QComboBox选区替换单元格内容需要用什么方法?

How do I need to change the code to display the QComboBox in rows 0, 2, 4 and 6 or any arbitrary row for that matter. (Whether or not the QComboBox needs to be displayed depends on the value of the first cell in reach row.)

只能看到一个QComboBox的问题是因为你只创建了一个QComboBox,要解决这个问题你必须为每个你想建立它们的单元格创建一个QComboBox。

What kind of method do I need to use to replace the cell contents with the QComboBox selection?

您必须将适当的信号连接到模型的 setData() 方法,我们可以使用 QModelIndex 但这很危险,因为当您删除、移动或插入项目时它可能会被更改,使用它是合适的QPersistentModelIndex,在这种情况下你必须使用信号 currentIndexChanged

在这个例子中我将使用 QModelIndex:

def populate(self):
    row = self.QSModel.rowCount()
    for x in range(7):
        self.QSModel.insertRow(row)
        self.QSModel.setData(self.QSModel.index(row, 0), 'data')
        self.QSModel.item(row, 0).setEditable(True)
        self.QSModel.setData(self.QSModel.index(row, 1), 'data')
        self.QSModel.item(row, 1).setEditable(True)
        # add combobox delegate to even rows
        if x % 2 == 0:
            index = self.tableView.model().index(row, 1)
            cbDelegate = QComboBox()
            pix = QPersistentModelIndex(index)
            cbDelegate.currentIndexChanged[str].connect(lambda txt, pix=pix:self.tableView.model().setData(QModelIndex(pix), txt))
            cbDelegate.addItems(['choice1', 'choice2'])
            self.tableView.setIndexWidget(index, cbDelegate)
        self.QSModel.setData(self.QSModel.index(row, 2), 'data')
        self.QSModel.item(row, 1).setEditable(True)

在这个例子中我将使用 QStandartItem:

def populate(self):
    row = self.QSModel.rowCount()
    for x in range(7):
        self.QSModel.insertRow(row)
        self.QSModel.setData(self.QSModel.index(row, 0), 'data')
        self.QSModel.item(row, 0).setEditable(True)
        self.QSModel.setData(self.QSModel.index(row, 1), 'data')
        self.QSModel.item(row, 1).setEditable(True)
        if x % 2 == 0:
            item = self.tableView.model().item(row, 1)
            cbDelegate = QComboBox()
            cbDelegate.currentIndexChanged[str].connect(item.setText)
            cbDelegate.addItems(['choice1', 'choice2'])
            self.tableView.setIndexWidget(item.index(), cbDelegate)
        self.QSModel.setData(self.QSModel.index(row, 2), 'data')
        self.QSModel.item(row, 1).setEditable(True)

做类似事情的另一种方法是使用委托,为此我们创建了一个继承自 QStyledItemDelegate:

的 class
class ComboBoxDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        if index.row()%2 == 0:
            opt = QStyleOptionComboBox()
            opt.rect = option.rect 
            opt.currentText = index.data()
            QApplication.style().drawComplexControl(QStyle.CC_ComboBox, opt, painter)
            QApplication.style().drawControl(QStyle.CE_ComboBoxLabel, opt, painter)
        else:
            QStyledItemDelegate.paint(self, painter, option, index)

    def createEditor(self, parent, option, index):
        if index.row()%2 == 0:
            combobox = QComboBox(parent)
            options = ['choice1', 'choice2']
            if index.data() not in options:
                combobox.addItem(index.data())
            combobox.addItems(options)
            return combobox
        return QStyledItemDelegate.createEditor(self, parent, option, index)

    def setEditorData(self, editor, index):
        if isinstance(editor, QComboBox):
            text = index.data()
            ix = editor.findText(text)
            if ix > 0:
                editor.setCurrentIndex(ix)
            else:
                index.model().setData(index, editor.currentText())
        else:
            QStyledItemDelegate.setEditorData(self, editor, index)

    def setModelData(self, editor, model, index):
        if isinstance(editor, QComboBox):
            model.setData(index, editor.currentText())
        else:
            QStyledItemDelegate.setModelData(self, editor, model, index)

class GUI(QDialog):
    def __init__(self):
        super(GUI, self).__init__()
        dirname = os.path.dirname(os.path.abspath(__file__))
        uic.loadUi(os.path.join(dirname,'main.ui'), self)
        self.btnPopulate.clicked.connect(self.populate)
        self.header = ['col1', 'col2', 'col3']
        self.QSModel = QStandardItemModel()
        self.QSModel.setColumnCount(3)
        self.QSModel.setHorizontalHeaderLabels(self.header)
        self.tableView.setModel(self.QSModel)
        self.tableView.setItemDelegateForColumn(1, ComboBoxDelegate(self.tableView))
        self.populate()

    def populate(self):
        row = self.QSModel.rowCount()
        for x in range(7):
            for i, value in enumerate(['data1', 'data2', 'data3']):
                self.QSModel.setItem(row+x, i, QStandardItem(value))
                self.QSModel.item(row+x, i).setEditable(True)