QStyledItemDelegate 在 QTableView 中显示 QComboBox [英] QStyledItemDelegate to display QComboBox in QTableView

查看:60
本文介绍了QStyledItemDelegate 在 QTableView 中显示 QComboBox的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 Python 和 PyQt5 的新手.我正在使用 QStyledItemDelegate 来制作仅由 ComboBox 组成的 QTableView 列之一.我设法显示了 ComboBox,但是它的行为有问题.

I'm new to Python and PyQt5. I'm using QStyledItemDelegate to make one of the QTableView column consisting of only ComboBox. I managed to display the ComboBox but and I'm having trouble with its behavior.

问题 1:即使选择已更改,ComboBox 似乎也未提交对模型的更改.我使用导出按钮打印出列表以供检查.

Problem 1: The ComboBox doesn't seems to commit changes to the model even though selection was changed. I used the export button to print out the list for checking purposes.

问题 2:当我向表中添加新行时,新行 ComboBox 选择不断恢复到第一个选择.这是为什么?

Problem 2: When I add a new row to the table, the new row ComboBox selection keeps reverting back to the first selection. Why is that?

有人可以帮我提供一些建议吗?谢谢.

Could anyone help me with some advice? Thank you.

代码:

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import re

class Delegate(QStyledItemDelegate):
    def __init__(self, owner, choices):
        super().__init__(owner)
        self.items = choices

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

    def paint(self, painter, option, index):
        if isinstance(self.parent(), QAbstractItemView):
            self.parent().openPersistentEditor(index, 1)
        QStyledItemDelegate.paint(self, painter, option, index)

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
        value = index.data(Qt.DisplayRole)
        num = self.items.index(value)
        editor.setCurrentIndex(num)
        editor.blockSignals(False)

    def setModelData(self, editor, model, index):
        value = editor.currentText()
        model.setData(index, value, Qt.EditRole)

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

class Model(QAbstractTableModel):
    ActiveRole = Qt.UserRole + 1
    def __init__(self, datain, headerdata, parent=None):
        """
        Args:
            datain: a list of lists\n
            headerdata: a list of strings
        """
        super().__init__()
        self.arraydata = datain
        self.headerdata = headerdata

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return QVariant(self.headerdata[section])
        return QVariant()

    def rowCount(self, parent):
        return len(self.arraydata)

    def columnCount(self, parent):
        if len(self.arraydata) > 0:
            return len(self.arraydata[0])
        return 0

    def flags(self, index):
        return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def data(self, index, role):
        if not index.isValid():
            return QVariant()
        elif role != Qt.DisplayRole:
            return QVariant()
        return QVariant(self.arraydata[index.row()][index.column()])

    def setData(self, index, value, role):
        r = re.compile(r"^[0-9]\d*(\.\d+)?$")
        if role == Qt.EditRole and value != "":
            if not index.column() in range(0, 1):
                if index.column() == 2:
                    if r.match(value) and (0 < float(value) <= 1):
                        self.arraydata[index.row()][index.column()] = value
                        self.dataChanged.emit(index, index, (Qt.DisplayRole, ))
                        return True
                else:
                    if r.match(value):
                        self.arraydata[index.row()][index.column()] = value
                        self.dataChanged.emit(index, index, (Qt.DisplayRole, ))
                        return True
            elif index.column() in range(0, 1):
                self.arraydata[index.row()][index.column()] = value
                self.dataChanged.emit(index, index, (Qt.DisplayRole, ))
                return True
        return False

    def print_arraydata(self):
        print(self.arraydata)

class Main(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        # create table view:
        self.get_choices_data()
        self.get_table_data()
        self.tableview = self.createTable()
        self.tableview.clicked.connect(self.tv_clicked_pos)

        # Set the maximum value of row to the selected row
        self.selectrow = self.tableview.model().rowCount(QModelIndex())

        # create buttons:
        self.addbtn = QPushButton('Add')
        self.addbtn.clicked.connect(self.insert_row)
        self.deletebtn = QPushButton('Delete')
        self.deletebtn.clicked.connect(self.remove_row)
        self.exportbtn = QPushButton('Export')
        self.exportbtn.clicked.connect(self.export_tv)
        self.computebtn = QPushButton('Compute')
        self.enablechkbox = QCheckBox('Completed')

        # create label:
        self.lbltitle = QLabel('Table')
        self.lbltitle.setFont(QFont('Arial', 20))

        # create gridlayout
        self.grid_layout = QGridLayout()
        self.grid_layout.addWidget(self.exportbtn, 2, 2, 1, 1)
        self.grid_layout.addWidget(self.computebtn, 2, 3, 1, 1)
        self.grid_layout.addWidget(self.addbtn, 2, 4, 1, 1)
        self.grid_layout.addWidget(self.deletebtn, 2, 5, 1, 1)
        self.grid_layout.addWidget(self.enablechkbox, 2, 6, 1, 1, Qt.AlignCenter)
        self.grid_layout.addWidget(self.tableview, 1, 0, 1, 7)
        self.grid_layout.addWidget(self.lbltitle, 0, 3, 1, 1, Qt.AlignCenter)

        # initializing layout
        self.title = 'Data Visualization Tool'
        self.setWindowTitle(self.title)
        self.setGeometry(0, 0, 1024, 576)
        self.showMaximized()
        self.centralwidget = QWidget()
        self.centralwidget.setLayout(self.grid_layout)
        self.setCentralWidget(self.centralwidget)

    def get_table_data(self): 
        # set initial table values:
        self.tabledata = [['Name', self.choices[0], 0.0, 0.0, 0.0]]

    def get_choices_data(self):
        # set combo box choices:
        self.choices = ['type_1', 'type_2', 'type_3', 'type_4', 'type_5']

    def createTable(self):
        tv = QTableView()

        # set header for columns:
        header = ['Name', 'Type', 'var1', 'var2', 'var3']       

        tablemodel = Model(self.tabledata, header, self)
        tv.setModel(tablemodel)
        hh = tv.horizontalHeader()
        tv.resizeRowsToContents()

        # ItemDelegate for combo boxes
        tv.setItemDelegateForColumn(1, Delegate(self, self.choices))

        # make combo boxes editable with a single-click:
        for row in range(len(self.tabledata)):
            tv.openPersistentEditor(tablemodel.index(row, 1))

        return tv

    def export_tv(self):
        self.tableview.model().print_arraydata()

    def insert_row(self, position, rows=1, index=QModelIndex()):
        position = self.selectrow
        self.tableview.model().beginInsertRows(QModelIndex(), position, position + rows - 1)
        for row in range(rows):
            self.tableview.model().arraydata.append(['Name', self.choices[0], 0.0, 0.0, 0.0])
        self.tableview.model().endInsertRows()
        self.tableview.model().rowsInserted.connect(lambda: QTimer.singleShot(0, self.tableview.scrollToBottom))
        return True

    def remove_row(self, position, rows=1, index=QModelIndex()):
        position = self.selectrow
        self.tableview.model().beginRemoveRows(QModelIndex(), position, position + rows - 1)
        self.tableview.model().arraydata = self.tableview.model().arraydata[:position] + self.tableview.model().arraydata[position + rows:]
        self.tableview.model().endRemoveRows()
        return True

    def tv_clicked_pos(self, indexClicked):
        self.selectrow = indexClicked.row()

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    main = Main()
    main.show()
    app.exec_()

推荐答案

默认情况下,setModelData() 在编辑器关闭时调用,在您的情况下使用 openPersistentEditor() 编辑器永远不会被关闭,除非你调用 closePersistentEditor(),因此 setModelData() 不会被调用.所以上面的解决办法就是发出commitData()信号,这样我们就通知delegate去保存数据了.但是它仍然没有保存数据,因为 setData() 的实现有问题,在你的代码中你使用 range(0, 1) 并且知道 range(0, n)[0, 1, ..., n-1] 所以在你的情况下 range(0, 1) 等于[0] 并且 QComboBox 的数据在 1 列中,因此您必须修改该逻辑,使其也接受 1.

By default, setModelData() is called when the editor is closed, in your case when using openPersistentEditor() the editor will never be closed unless you call closePersistentEditor(), therefore setModelData() will not be called. So the solution for the above is to issue the commitData() signal, so we notify the delegate to save the data. But still it does not save the data because the implementation of setData() has problems, in your code you use range(0, 1) and it is known that range(0, n) is [0, 1, ..., n-1] so in your case range(0, 1) equals [0] and the data of the QComboBox is in column 1, so you have to modify that logic so that it also accepts the 1.

另一方面,我看到的错误是,如果添加一行,编辑器不会持久打开,逻辑是代码:if isinstance(self.parent(), QtWidgets.QAbstractItemView): self.parent().openPersistentEditor (index) 做那个工作,但委托的父应该是视图,而不是主寡妇.

On the other hand the error that I see is that if a row is added the editor is not opened persistently, and the logic is that the code: if isinstance(self.parent(), QtWidgets.QAbstractItemView): self.parent().openPersistentEditor (index) Do that job, but the delegate's parent is expected to be the view, not the mainwidow.

使用上面的方法,得到以下解决方案:

Using the above, the following solution is obtained:

from PyQt5 import QtCore, QtGui, QtWidgets
import re

class Delegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, owner, choices):
        super().__init__(owner)
        self.items = choices

    def paint(self, painter, option, index):
        if isinstance(self.parent(), QtWidgets.QAbstractItemView):
            self.parent().openPersistentEditor(index)
        super(Delegate, self).paint(painter, option, index)

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QComboBox(parent)
        editor.currentIndexChanged.connect(self.commit_editor)
        editor.addItems(self.items)
        return editor

    def commit_editor(self):
        editor = self.sender()
        self.commitData.emit(editor)

    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, value, QtCore.Qt.EditRole)

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

class Model(QtCore.QAbstractTableModel):
    ActiveRole = QtCore.Qt.UserRole + 1
    def __init__(self, datain, headerdata, parent=None):
        """
        Args:
            datain: a list of lists\n
            headerdata: a list of strings
        """
        super().__init__()
        self.arraydata = datain
        self.headerdata = headerdata

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
            return QtCore.QVariant(self.headerdata[section])
        return QtCore.QVariant()

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid(): return 0
        return len(self.arraydata)

    def columnCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid(): return 0
        if len(self.arraydata) > 0:
            return len(self.arraydata[0])
        return 0

    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def data(self, index, role):
        if not index.isValid():
            return QtCore.QVariant()
        elif role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()
        return QtCore.QVariant(self.arraydata[index.row()][index.column()])

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        r = re.compile(r"^[0-9]\d*(\.\d+)?$")
        if role == QtCore.Qt.EditRole and value != "" and 0 < index.column() < self.columnCount():
            if index.column() in (0, 1):
                self.arraydata[index.row()][index.column()] = value
                self.dataChanged.emit(index, index, (QtCore.Qt.DisplayRole, ))
                return True
            else:
                if index.column() == 2:
                    if r.match(value) and (0 < float(value) <= 1):
                        self.arraydata[index.row()][index.column()] = value
                        self.dataChanged.emit(index, index, (QtCore.Qt.DisplayRole, ))
                        return True
                else:
                    if r.match(value):
                        self.arraydata[index.row()][index.column()] = value
                        self.dataChanged.emit(index, index, (QtCore.Qt.DisplayRole, ))
                        return True
        return False

    def print_arraydata(self):
        print(self.arraydata)

    def insert_row(self, data, position, rows=1):
        self.beginInsertRows(QtCore.QModelIndex(), position, position + rows - 1)
        for i, e in enumerate(data):
            self.arraydata.insert(i+position, e[:])
        self.endInsertRows()
        return True

    def remove_row(self, position, rows=1):
        self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1)
        self.arraydata = self.arraydata[:position] + self.arraydata[position + rows:]
        self.endRemoveRows()
        return True

    def append_row(self, data):
        self.insert_row([data], self.rowCount())


class Main(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        # create table view:
        self.get_choices_data()
        self.get_table_data()
        self.tableview = self.createTable()
        self.tableview.model().rowsInserted.connect(lambda: QtCore.QTimer.singleShot(0, self.tableview.scrollToBottom))

        # Set the maximum value of row to the selected row
        self.selectrow = self.tableview.model().rowCount()

        # create buttons:
        self.addbtn = QtWidgets.QPushButton('Add')
        self.addbtn.clicked.connect(self.insert_row)
        self.deletebtn = QtWidgets.QPushButton('Delete')
        self.deletebtn.clicked.connect(self.remove_row)
        self.exportbtn = QtWidgets.QPushButton('Export')
        self.exportbtn.clicked.connect(self.export_tv)
        self.computebtn = QtWidgets.QPushButton('Compute')
        self.enablechkbox = QtWidgets.QCheckBox('Completed')

        # create label:
        self.lbltitle = QtWidgets.QLabel('Table')
        self.lbltitle.setFont(QtGui.QFont('Arial', 20))

        # create gridlayout
        grid_layout = QtWidgets.QGridLayout()
        grid_layout.addWidget(self.exportbtn, 2, 2, 1, 1)
        grid_layout.addWidget(self.computebtn, 2, 3, 1, 1)
        grid_layout.addWidget(self.addbtn, 2, 4, 1, 1)
        grid_layout.addWidget(self.deletebtn, 2, 5, 1, 1)
        grid_layout.addWidget(self.enablechkbox, 2, 6, 1, 1, QtCore.Qt.AlignCenter)
        grid_layout.addWidget(self.tableview, 1, 0, 1, 7)
        grid_layout.addWidget(self.lbltitle, 0, 3, 1, 1, QtCore.Qt.AlignCenter)

        # initializing layout
        self.title = 'Data Visualization Tool'
        self.setWindowTitle(self.title)
        self.setGeometry(0, 0, 1024, 576)
        self.showMaximized()
        self.centralwidget = QtWidgets.QWidget()
        self.centralwidget.setLayout(grid_layout)
        self.setCentralWidget(self.centralwidget)

    def get_table_data(self): 
        # set initial table values:
        self.tabledata = [['Name', self.choices[0], 0.0, 0.0, 0.0]]

    def get_choices_data(self):
        # set combo box choices:
        self.choices = ['type_1', 'type_2', 'type_3', 'type_4', 'type_5']

    def createTable(self):
        tv = QtWidgets.QTableView()
        # set header for columns:
        header = ['Name', 'Type', 'var1', 'var2', 'var3']       

        tablemodel = Model(self.tabledata, header, self)
        tv.setModel(tablemodel)
        hh = tv.horizontalHeader()
        tv.resizeRowsToContents()
        # ItemDelegate for combo boxes
        tv.setItemDelegateForColumn(1, Delegate(tv, self.choices))
        return tv

    def export_tv(self):
        self.tableview.model().print_arraydata()

    def remove_row(self):
        r = self.tableview.currentIndex().row()
        self.tableview.model().remove_row(r)

    def insert_row(self):
        self.tableview.model().append_row(['Name', self.choices[0], 0.0, 0.0, 0.0])


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

这篇关于QStyledItemDelegate 在 QTableView 中显示 QComboBox的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆