使用QItemDelegate关闭或打开QCheckBox时删除QComboBox

问题描述 投票:0回答:1

每当以上复选框的切换更改时,我都希望使用ComboBox更新QTableView的单元格内容。我正在使用QTableView和自定义委托来绘制ComboBoxes。复选框由QTableView本身控制。当前,当我切换复选框时,将出现下面的组合框,但是在切换复选框时我无法删除组合框。

我的代码示例如下。

import sys
import pandas as pd
from pandas.api.types import is_numeric_dtype
import numpy as np

from PyQt5.QtCore import (QAbstractTableModel, Qt, pyqtProperty, pyqtSlot,
                          QVariant, QModelIndex, pyqtSignal)
from PyQt5.QtWidgets import (QComboBox, QApplication, QAbstractItemView,
                             QItemDelegate, QCheckBox, QMainWindow, QTableView)


class DataFrameModel(QAbstractTableModel):
    DtypeRole = Qt.UserRole + 1000
    ValueRole = Qt.UserRole + 1001

    def __init__(self, df=pd.DataFrame(), parent=None):
        super(DataFrameModel, self).__init__(parent)
        self._dataframe = df
        self.df2 = pd.DataFrame(self._dataframe.iloc[2:3, :].to_dict())

    def setDataFrame(self, dataframe):
        self.beginResetModel()
        self._dataframe = dataframe.copy()
        self.endResetModel()

    def dataFrame(self):
        return self._dataframe

    dataFrame = pyqtProperty(pd.DataFrame, fget=dataFrame, fset=setDataFrame)

    @pyqtSlot(int, Qt.Orientation, result=str)
    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()

        if orientation == Qt.Horizontal:
            try:
                return self._dataframe.columns.tolist()[section]
            except (IndexError,):
                return QVariant()
        elif orientation == Qt.Vertical:
            try:
                if section in [0, 1]:
                    pass
                else:
                    return self._dataframe.index.tolist()[section - 2]
            except (IndexError,):
                return QVariant()

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

    def columnCount(self, parent=QModelIndex()):

        if parent.isValid():
            return 0
        return self._dataframe.columns.size

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        col = index.column()
        row = index.row()
        dt = self.df2[self.df2.columns[col]].dtype
        is_numeric = is_numeric_dtype(self.df2[self.df2.columns[col]])
        if row == 0 and is_numeric:
            value = self._dataframe.iloc[row, col].text()
        else:
            value = self._dataframe.iloc[row, col]
        if role == Qt.DisplayRole:
            return value
        elif role == Qt.CheckStateRole:
            if row == 0 and is_numeric:
                if self._dataframe.iloc[row, col].isChecked():
                    return Qt.Checked
                else:
                    return Qt.Unchecked

        elif role == DataFrameModel.ValueRole:
            return value
        elif role == DataFrameModel.ValueRole:
            return value
        if role == DataFrameModel.DtypeRole:
            return dt
        return QVariant()

    def roleNames(self):
        roles = {
            Qt.DisplayRole: b'display',
            DataFrameModel.DtypeRole: b'dtype',
            DataFrameModel.ValueRole: b'value'
        }
        return roles

    def setData(self, index, value, role=Qt.EditRole):
        if not index.isValid():
            return False
        col = index.column()
        row = index.row()
        is_numeric = is_numeric_dtype(self.df2[self.df2.columns[col]])
        if role == Qt.CheckStateRole and index.row() == 0:
            if is_numeric:
                if value == Qt.Checked:
                    self._dataframe.iloc[row, col].setChecked(True)
                    self._dataframe.iloc[row, col].setText("Grade Item")
                else:
                    self._dataframe.iloc[row, col].setChecked(False)
                    self._dataframe.iloc[row, col].setText("Not a Grade")
        elif row == 1 and role == Qt.EditRole:
            if isinstance(value, QVariant):
                value = value.value()
            if hasattr(value, 'toPyObject'):
                value = value.toPyObject()
            self._dataframe.iloc[row, col] = value
        elif row >= 2 and role == Qt.EditRole:
            try:
                value = eval(value)
                if not isinstance(
                        value,
                        self._dataframe.applymap(type).iloc[row, col]):
                    value = self._dataframe.iloc[row, col]
            except:
                value = self._dataframe.iloc[row, col]
            self._dataframe.iloc[row, col] = value
        self.dataChanged.emit(index, index, (Qt.DisplayRole,))
        return True

    def flags(self, index):
        if not index.isValid():
            return None

        if index.row() == 0:
            return (Qt.ItemIsEnabled | Qt.ItemIsSelectable |
                    Qt.ItemIsUserCheckable)
        else:
            return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable

    def sort(self, column, order):
        self.layoutAboutToBeChanged.emit()
        col_name = self._dataframe.columns.tolist()[column]
        sheet1 = self._dataframe.iloc[:2, :]
        sheet2 = self._dataframe.iloc[2:, :].sort_values(
            col_name, ascending=order == Qt.AscendingOrder, inplace=False)

        sheet2.reset_index(drop=True, inplace=True)
        sheet3 = pd.concat([sheet1, sheet2], ignore_index=True)
        self.setDataFrame(sheet3)
        self.layoutChanged.emit()


class ComboBoxDelegate(QItemDelegate):
    def __init__(self, parent, choices=None):
        super().__init__(parent)
        self.items = choices

    def createEditor(self, parent, option, index):
        self.parent().model().dataChanged.emit(index, index, (Qt.DisplayRole,))
        if is_numeric_dtype(
                self.parent().model().df2[
                    self.parent().model().df2.columns[index.column()]]):
            checked = self.parent().model().dataFrame.iloc[
                0, index.column()].isChecked()
            if checked:
                editor = QComboBox(parent)
                editor.addItems(self.items)
                editor.currentIndexChanged.connect(self.currentIndexChanged)
                return editor

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

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

    def setEditorData(self, editor, index):
        text = index.data(Qt.DisplayRole) or ""
        editor.setCurrentText(text)

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

    @pyqtSlot()
    def currentIndexChanged(self):
        editor = self.sender()
        self.commitData.emit(editor)


class MainWindow(QMainWindow):
    def __init__(self, pandas_sheet):
        super().__init__()
        self.pandas_sheet = pandas_sheet

        self.table = QTableView()
        self.setCentralWidget(self.table)

        check_bx_lst = []
        is_number = [is_numeric_dtype(self.pandas_sheet[col]) for col
                     in self.pandas_sheet.columns]
        for is_numb in is_number:
            if is_numb:
                checkbox = QCheckBox('Not a Grade')
                checkbox.setChecked(False)
                check_bx_lst.append(checkbox)
            else:
                check_bx_lst.append(None)

        for i in range(2):
            self.pandas_sheet.loc[-1] = [' '] * \
                                        self.pandas_sheet.columns.size
            self.pandas_sheet.index = self.pandas_sheet.index + 1
            self.pandas_sheet = self.pandas_sheet.sort_index()

        self.pandas_sheet.loc[0] = check_bx_lst

        model = DataFrameModel(self.pandas_sheet)
        self.table.setModel(model)
        self.table.setSortingEnabled(True)

        delegate = ComboBoxDelegate(self.table,
                                    [None, 'Test', 'Quiz'])
        self.table.setItemDelegateForRow(1, delegate)
        self.table.resizeColumnsToContents()
        self.table.resizeRowsToContents()


if __name__ == '__main__':
    df = pd.DataFrame({'a': ['student ' + str(i) for i in range(5)],
                       'b': np.arange(5),
                       'c': np.random.rand(5)})
    app = QApplication(sys.argv)
    window = MainWindow(df)
    window.table.model().sort(df.columns.get_loc("a"), Qt.AscendingOrder)
    window.setFixedSize(280, 200)
    window.show()

    sys.exit(app.exec_())

我想取消选中以上复选框时的组合框。

非常感谢您的帮助。

enter image description here

python pyqt pyqt5
1个回答
2
投票

您在paint函数中使用openPersistentEditor,这是完全错误的:对于代表使用的[[every索引,绘画[经常发生,并且您实际上每次每次都调用createEditor该行中的单元格已绘制,滚动视图时所有单元格或鼠标悬停的任何单元格都会发生这种情况。

按照您的逻辑,可能是由于数据更改所要求的绘制而最终创建了创建者,因为那时createEditor中的if条件为True。从那时起,您创建一个persistent编辑器,如果不删除它,它将保留在那里。显然,这不是一个好方法,主要是因为您实际上检查了是否可以通过绘画函数创建编辑器,这没有多大意义。

您应该连接到模型的dataChanged信号以验证检查的状态,然后相应地打开或关闭编辑器。

class MainWindow(QMainWindow): def __init__(self, pandas_sheet): # ... model.dataChanged.connect(self.dataChanged) def dataChanged(self, topLeft, bottomRight, roles): if topLeft.row() == 0: if topLeft.data(QtCore.Qt.CheckStateRole): self.table.openPersistentEditor(topLeft.sibling(1, topLeft.column())) else: self.table.closePersistentEditor(topLeft.sibling(1, topLeft.column()))

我过分简化了if条件,但是我认为概念很清楚。

© www.soinside.com 2019 - 2024. All rights reserved.