PyQt - QTableView中的复选框列

时间:2013-07-19 14:25:38

标签: python python-2.7 pyqt pandas

我正在从Pandas数据帧动态创建QTableView。我有示例代码here.

我可以使用复选框创建表格,但我无法获得反映模型数据的复选框,甚至无法取消选中以取消选中。

我正在关注此前question的示例代码,并以@raorao答案为指导。这将显示表格中的框,但功能无效。

任何人都可以建议任何更改,或者此代码有什么问题。为什么它没有反映模型,为什么它不会改变?

请查看我的完整示例代码here.

编辑一个:来自Frodon的评论后更新: 更正了字符串强制转换为bool,并进行了比较xxx ==' True'

class CheckBoxDelegate(QtGui.QStyledItemDelegate):
    """
    A delegate that places a fully functioning QCheckBox in every
    cell of the column to which it's applied
    """
    def __init__(self, parent):
        QtGui.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        '''
        Important, otherwise an editor is created if the user clicks in this cell.
        ** Need to hook up a signal to the model
        '''
        return None

    def paint(self, painter, option, index):
        '''
        Paint a checkbox without the label.
        '''
        checked = index.model().data(index, QtCore.Qt.DisplayRole) == 'True'
        check_box_style_option = QtGui.QStyleOptionButton()

        if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
            check_box_style_option.state |= QtGui.QStyle.State_Enabled
        else:
            check_box_style_option.state |= QtGui.QStyle.State_ReadOnly

        if checked:
            check_box_style_option.state |= QtGui.QStyle.State_On
        else:
            check_box_style_option.state |= QtGui.QStyle.State_Off

        check_box_style_option.rect = self.getCheckBoxRect(option)

        # this will not run - hasFlag does not exist
        #if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
            #check_box_style_option.state |= QtGui.QStyle.State_ReadOnly

        check_box_style_option.state |= QtGui.QStyle.State_Enabled

        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)

    def editorEvent(self, event, model, option, index):
        '''
        Change the data in the model and the state of the checkbox
        if the user presses the left mousebutton or presses
        Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
        '''
        print 'Check Box editor Event detected : ' 
        if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
            return False

        print 'Check Box edior Event detected : passed first check' 
        # Do not change the checkbox-state
        if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
            if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
                return False
            if event.type() == QtCore.QEvent.MouseButtonDblClick:
                return True
        elif event.type() == QtCore.QEvent.KeyPress:
            if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
                return False
            else:
                return False

        # Change the checkbox-state
        self.setModelData(None, model, index)
        return True

3 个答案:

答案 0 :(得分:9)

我找到了适合你的解决方案。诀窍是:

  1. 编写模型的setData方法
  2. 始终在data方法
  3. 中返回QVariant

    在这里。 (我必须创建一个名为Dataframe的类,以使示例在没有pandas的情况下工作。请替换您的所有if has_pandas语句:

    from PyQt4 import QtCore, QtGui
    
    has_pandas = False
    try:
      import pandas as pd
      has_pandas = True
    except:
      pass
    
    class TableModel(QtCore.QAbstractTableModel):
        def __init__(self, parent=None, *args):
            super(TableModel, self).__init__()
            self.datatable = None
            self.headerdata = None
    
        def update(self, dataIn):
            print 'Updating Model'
            self.datatable = dataIn
            print 'Datatable : {0}'.format(self.datatable)
            if has_pandas:
              headers = dataIn.columns.values
            else:
              headers = dataIn.columns
            header_items = [
                        str(field)
                        for field in headers
            ]
            self.headerdata = header_items
            print 'Headers'
            print self.headerdata
    
        def rowCount(self, parent=QtCore.QModelIndex()):
            return len(self.datatable.index)
    
        def columnCount(self, parent=QtCore.QModelIndex()):
            if has_pandas:
              return len(self.datatable.columns.values)
            else:
              return len(self.datatable.columns)
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            if role == QtCore.Qt.DisplayRole:
                i = index.row()
                j = index.column()
                return QtCore.QVariant('{0}'.format(self.datatable.iget_value(i, j)))
            else:
                return QtCore.QVariant()
    
        def setData(self, index, value, role=QtCore.Qt.DisplayRole):
            if index.column() == 4:
                self.datatable.iset_value(index.row(), 4, value)
                return value
            return value
    
        def headerData(self, col, orientation, role):
            if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
                return '{0}'.format(self.headerdata[col])
    
        def flags(self, index):
            if index.column() == 4:
                return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
            else:
                return QtCore.Qt.ItemIsEnabled
    
    
    class TableView(QtGui.QTableView):
        """
        A simple table to demonstrate the QComboBox delegate.
        """
        def __init__(self, *args, **kwargs):
            QtGui.QTableView.__init__(self, *args, **kwargs)
            self.setItemDelegateForColumn(4, CheckBoxDelegate(self))
    
    
    class CheckBoxDelegate(QtGui.QStyledItemDelegate):
        """
        A delegate that places a fully functioning QCheckBox in every
        cell of the column to which it's applied
        """
        def __init__(self, parent):
            QtGui.QItemDelegate.__init__(self, parent)
    
        def createEditor(self, parent, option, index):
            '''
            Important, otherwise an editor is created if the user clicks in this cell.
            ** Need to hook up a signal to the model
            '''
            return None
    
        def paint(self, painter, option, index):
            '''
            Paint a checkbox without the label.
            '''
    
            checked = index.data().toBool()
            check_box_style_option = QtGui.QStyleOptionButton()
    
            if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
                check_box_style_option.state |= QtGui.QStyle.State_Enabled
            else:
                check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
    
            if checked:
                check_box_style_option.state |= QtGui.QStyle.State_On
            else:
                check_box_style_option.state |= QtGui.QStyle.State_Off
    
            check_box_style_option.rect = self.getCheckBoxRect(option)
    
            # this will not run - hasFlag does not exist
            #if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
                #check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
    
            check_box_style_option.state |= QtGui.QStyle.State_Enabled
    
            QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)
    
        def editorEvent(self, event, model, option, index):
            '''
            Change the data in the model and the state of the checkbox
            if the user presses the left mousebutton or presses
            Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
            '''
            print 'Check Box editor Event detected : '
            print event.type()
            if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
                return False
    
            print 'Check Box editor Event detected : passed first check'
            # Do not change the checkbox-state
            if event.type() == QtCore.QEvent.MouseButtonPress:
              return False
            if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
                if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
                    return False
                if event.type() == QtCore.QEvent.MouseButtonDblClick:
                    return True
            elif event.type() == QtCore.QEvent.KeyPress:
                if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
                    return False
            else:
                return False
    
            # Change the checkbox-state
            self.setModelData(None, model, index)
            return True
    
        def setModelData (self, editor, model, index):
            '''
            The user wanted to change the old state in the opposite.
            '''
            print 'SetModelData'
            newValue = not index.data().toBool()
            print 'New Value : {0}'.format(newValue)
            model.setData(index, newValue, QtCore.Qt.EditRole)
    
        def getCheckBoxRect(self, option):
            check_box_style_option = QtGui.QStyleOptionButton()
            check_box_rect = QtGui.QApplication.style().subElementRect(QtGui.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
            check_box_point = QtCore.QPoint (option.rect.x() +
                                option.rect.width() / 2 -
                                check_box_rect.width() / 2,
                                option.rect.y() +
                                option.rect.height() / 2 -
                                check_box_rect.height() / 2)
            return QtCore.QRect(check_box_point, check_box_rect.size())
    
    
    ###############################################################################################################################
    class Dataframe(dict):
      def __init__(self, columns, values):
        if len(values) != len(columns):
          raise Exception("Bad values")
        self.columns = columns
        self.values = values
        self.index = values[0]
        super(Dataframe, self).__init__(dict(zip(columns, values)))
        pass
    
      def iget_value(self, i, j):
        return(self.values[j][i])
    
      def iset_value(self, i, j, value):
        self.values[j][i] = value
    
    
    if __name__=="__main__":
        from sys import argv, exit
    
        class Widget(QtGui.QWidget):
            """
            A simple test widget to contain and own the model and table.
            """
            def __init__(self, parent=None):
                QtGui.QWidget.__init__(self, parent)
    
                l=QtGui.QVBoxLayout(self)
                cdf = self.get_data_frame()
                self._tm=TableModel(self)
                self._tm.update(cdf)
                self._tv=TableView(self)
                self._tv.setModel(self._tm)
                for row in range(0, self._tm.rowCount()):
                    self._tv.openPersistentEditor(self._tm.index(row, 4))
                self.setGeometry(300, 300, 550, 200)
                l.addWidget(self._tv)
    
            def get_data_frame(self):
                if has_pandas:
                  df = pd.DataFrame({'Name':['a','b','c','d'],
                  'First':[2.3,5.4,3.1,7.7], 'Last':[23.4,11.2,65.3,88.8], 'Class':[1,1,2,1], 'Valid':[True, False, True, False]})
                else:
                  columns = ['Name', 'First', 'Last', 'Class', 'Valid']
                  values = [['a','b','c','d'], [2.3,5.4,3.1,7.7], [23.4,11.2,65.3,88.8], [1,1,2,1], [True, False, True, False]]
                  df = Dataframe(columns, values)
                return df
    
        a=QtGui.QApplication(argv)
        w=Widget()
        w.show()
        w.raise_()
        exit(a.exec_())
    

答案 1 :(得分:4)

这是PyQt5上面相同代码的端口。 在这里发布,因为在QT5中似乎没有另一个工作CheckBox委托的例子,我正试图让它发挥作用。希望它对某人有用。

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView

class CheckBoxDelegate(QtWidgets.QItemDelegate):
    """
    A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
    """
    def __init__(self, parent):
        QtWidgets.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        """
        Important, otherwise an editor is created if the user clicks in this cell.
        """
        return None

    def paint(self, painter, option, index):
        """
        Paint a checkbox without the label.
        """
        self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)

    def editorEvent(self, event, model, option, index):
        '''
        Change the data in the model and the state of the checkbox
        if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
        '''
        if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
            return False

        if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
            # Change the checkbox-state
            self.setModelData(None, model, index)
            return True

        return False


    def setModelData (self, editor, model, index):
        '''
        The user wanted to change the old state in the opposite.
        '''
        model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)



if __name__ == '__main__':

    import sys

    app = QApplication(sys.argv)

    model = QStandardItemModel(4, 3)
    tableView = QTableView()
    tableView.setModel(model)

    delegate = CheckBoxDelegate(None)
    tableView.setItemDelegateForColumn(1, delegate)
    for row in range(4):
        for column in range(3):
            index = model.index(row, column, QModelIndex())
            model.setData(index, 1)

    tableView.setWindowTitle("Check Box Delegate")
    tableView.show()
    sys.exit(app.exec_())

答案 2 :(得分:2)

我添加了

if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.MouseMove:
      return False

防止复选框在移动鼠标时闪烁