QStyledItemDelegate在QTableView中显示QComboBox

时间:2018-10-30 07:39:30

标签: python python-3.x pyqt pyqt5

我是Python和PyQt5的新手。我正在使用k作为仅由ComboBox组成的QStyledItemDelegate列之一。我设法显示了ComboBox,但是它的行为遇到了麻烦。

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

问题2:当我向表中添加新行时,新行的ComboBox选择会一直恢复到第一个选择。为什么会这样?

有人可以帮助我一些建议吗?谢谢。

代码:

QTableView

1 个答案:

答案 0 :(得分:3)

默认情况下,关闭编辑器时会调用setModelData(),在您使用openPersistentEditor()的情况下,除非您调用closePersistentEditor(),否则编辑器将永远不会关闭,因此setModelData()不会被调用。因此,上述解决方案是发出commitData()信号,因此我们通知委托保存数据。但是仍然无法保存数据,因为setData()的实现存在问题,在您的代码中使用range(0, 1),并且知道range(0, n)[0, 1, ..., n-1],因此在您的情况下range(0, 1)等于[0],并且QComboBox的数据在1列中,因此您必须修改该逻辑,以便它也接受1。 / p>

另一方面,我看到的错误是,如果添加行,则不会持久打开编辑器,其逻辑是该代码:if isinstance(self.parent(), QtWidgets.QAbstractItemView): self.parent().openPersistentEditor (index)可以执行此操作,但是希望委托的父级执行是视图,而不是主要的窗口。

使用上述方法,可获得以下解决方案:

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