代码之家  ›  专栏  ›  技术社区  ›  maggie

Qt CheckBox委托生成两个复选框

  •  0
  • maggie  · 技术社区  · 9 年前

    我正在尝试在PySideGUI中实现某种列表视图,这让用户有机会在最终处理列表之前启用/禁用列表中的某些条目。

    我决定在CheckBoxDelegate类中使用QTableView和QAbstractTableModel,该类为表视图中的每一行呈现一个复选框。选中和取消选中条目将相应地设置基础列表对象的enabled属性。这允许我在处理时轻松跳过条目。

    我想画一个居中的复选框。因此,我基于这个SO问题在CheckBoxDelegate中使用了QCheckbox的子类 https://stackoverflow.com/a/11802138/1504082 . 现在我的问题是我在第0列有两个复选框。但我不明白为什么。。。

    这是我的代码

    # -*- coding: UTF-8 -*-
    import sys
    
    from sip import setdestroyonexit
    from PySide import QtCore
    from PySide import QtGui
    
    
    def do_action(obj):
        print "do stuff for", obj.data_value
    
    
    class MyObject(object):
        def __init__(self, data_value, enabled=True):
            self.data_value = data_value
            self.enabled = enabled
            self.result = None
            self.action = ''
    
    
    class MyCheckBox(QtGui.QCheckBox):
        def __init__(self, parent):
            QtGui.QCheckBox.__init__(self, parent)
            # create a centered checkbox
            self.cb = QtGui.QCheckBox(parent)
            cbLayout = QtGui.QHBoxLayout(self)
            cbLayout.addWidget(self.cb, 0, QtCore.Qt.AlignCenter)
            self.cb.clicked.connect(self.stateChanged)
    
        def isChecked(self):
            return self.cb.isChecked()
    
        def setChecked(self, value):
            self.cb.setChecked(value)
    
        @QtCore.Slot()
        def stateChanged(self):
            print "sender", self.sender()
            self.clicked.emit()
    
    
    class CheckBoxDelegate(QtGui.QItemDelegate):
        """
        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):
            cb = MyCheckBox(parent)
            cb.clicked.connect(self.stateChanged)
            return cb
    
        def paint(self, painter, option, index):
            value = index.data()
            if value:
                value = QtCore.Qt.Checked
            else:
                value = QtCore.Qt.Unchecked
            self.drawCheck(painter, option, option.rect, value)
            self.drawFocus(painter, option, option.rect)
    
        def setEditorData(self, editor, index):
            """ Update the value of the editor """
            editor.blockSignals(True)
            editor.setChecked(index.model().checked_state(index))
            editor.blockSignals(False)
    
        def setModelData(self, editor, model, index):
            """ Send data to the model """
            model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
    
        @QtCore.Slot()
        def stateChanged(self):
            print "sender", self.sender()
            self.commitData.emit(self.sender())
    
    
    class TableView(QtGui.QTableView):
        """
        A simple table to demonstrate the QCheckBox delegate.
        """
        def __init__(self, *args, **kwargs):
            QtGui.QTableView.__init__(self, *args, **kwargs)
            # Set the delegate for column 0 of our table
            self.setItemDelegateForColumn(0, CheckBoxDelegate(self))
    
    
    class MyWindow(QtGui.QWidget):
    
        def __init__(self, *args):
            QtGui.QWidget.__init__(self, *args)
            # setGeometry(x_pos, y_pos, width, height)
            self.setGeometry(300, 200, 640, 480)
            self.setWindowTitle("CheckBoxDelegate with two Checkboxes?")
            self.object_list = [
                MyObject('Task 1'),
                MyObject('Task 2'),
                MyObject('Task 3'),
            ]
            self.header = ['Active', 'Data value', 'Result', 'Action']
            table_model = MyTableModel(self,
                                       self.object_list,
                                       ['enabled', 'data_value', 'result', 'action'],
                                       self.header)
    
            self.table_view = TableView()
            self.table_view.setModel(table_model)
    
            active_col = self.header.index('Active')
            for row in range(0, table_model.rowCount()):
                self.table_view.openPersistentEditor(table_model.index(row, active_col))
    
            action_col = self.header.index('Action')
            for i, bo in enumerate(self.object_list):
                btn = QtGui.QPushButton(self.table_view)
                btn.setText("View")
                self.table_view.setIndexWidget(table_model.index(i, action_col), btn)
                btn.clicked.connect(lambda obj=bo: do_action(obj))
    
            # set font
            font = QtGui.QFont("Calibri", 10)
            self.table_view.setFont(font)
            # set column width to fit contents (set font first!)
            self.table_view.resizeColumnsToContents()
    
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.table_view)
    
            self.setLayout(layout)
    
    
    class MyTableModel(QtCore.QAbstractTableModel):
        def __init__(self, parent, rows, columns, header, *args):
            QtCore.QAbstractTableModel.__init__(self, parent, *args)
            self.rows = rows
            self.columns = columns
            self.header = header
            self.CB_COL = 0
            assert len(columns) == len(header), "Header names dont have the same " \
                                                "length as supplied columns"
    
        def rowCount(self, parent=QtCore.QModelIndex()):
            return len(self.rows)
    
        def columnCount(self, parent=QtCore.QModelIndex()):
            return len(self.columns)
    
        def checked_state(self, index):
            if not index.isValid():
                return None
            elif index.column() == self.CB_COL:
                attr_name = self.columns[index.column()]
                row = self.rows[index.row()]
                return getattr(row, attr_name)
            else:
                return None
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            if not index.isValid():
                return None
            elif role == QtCore.Qt.DisplayRole:
                attr_name = self.columns[index.column()]
                row = self.rows[index.row()]
                if index.column() == self.CB_COL:
                    # no text for checkbox column's
                    return None
                else:
                    return getattr(row, attr_name)
            elif role == QtCore.Qt.CheckStateRole:
                return None
            else:
                return None
    
        def setData(self, index, value, role=QtCore.Qt.EditRole):
            if role == QtCore.Qt.EditRole:
                attr_name = self.columns[index.column()]
                row = self.rows[index.row()]
    
                if ((index.column() == self.CB_COL)
                        and (value != self.rows[index.row()].enabled)):
                    if value:
                        print "Enabled",
                    else:
                        print "Disabled",
                    print self.rows[index.row()].data_value
    
                setattr(row, attr_name, value)
    
                self.emit(QtCore.SIGNAL("dataChanged(const QModelIndex&, const QModelIndex &)"),
                          index, index)
                return True
            else:
                return False
    
        def headerData(self, col, orientation, role):
            if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
                return self.header[col]
            return None
    
        def flags(self, index):
            if (index.column() == self.CB_COL):
                return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
            else:
                return QtCore.Qt.ItemIsEnabled
    
    
    if __name__ == "__main__":
        # avoid crash on exit
        setdestroyonexit(False)
        app = QtGui.QApplication(sys.argv)
        window = MyWindow()
        window.show()
        sys.exit(app.exec_())
    

    有人能给我解释一下为什么会发生这种情况(以及我如何解决它)吗?

    1 回复  |  直到 8 年前
        1
  •  2
  •   strubbly    9 年前

    你有问题是因为你的 MyCheckBox 将两者分类 QCheckBox (通过继承)以及 Q复选框 通过构建新的 Q复选框 实例在其init中( self.cb ).

    你真的只想做其中之一。为了演示,我重写了MyCheckBox类,如下所示:

    class MyCheckBox(QtGui.QWidget):
        def __init__(self, parent):
            QtGui.QWidget.__init__(self, parent)
            # create a centered checkbox
            self.cb = QtGui.QCheckBox(parent)
            cbLayout = QtGui.QHBoxLayout(self)
            cbLayout.addWidget(self.cb, 0, QtCore.Qt.AlignCenter)
            self.cb.clicked.connect(self.amClicked)
    
        clicked = QtCore.Signal()
    
        def amClicked(self):
            self.clicked.emit()
    

    这解决了问题(尽管您也需要进行一些其他更改)。 请注意,您使用的单击信号需要来自 我的复选框 而不是 Q复选框 所以我已经通过 amClicked 狭槽您不需要区分 data() checked_state() 方法,所以我将它们合并为一个:

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        elif role == QtCore.Qt.DisplayRole:
            attr_name = self.columns[index.column()]
            row = self.rows[index.row()]
            return getattr(row, attr_name)
        elif role == QtCore.Qt.CheckStateRole:
            return None
        else:
            return None
    

    然后代理看起来像这样。我只安排了一个编辑器,如果标志说它是可编辑的。如果没有,那么它负责绘制,因此它也必须在绘制方法中做正确的事情。

    class CheckBoxDelegate(QtGui.QItemDelegate):
        """
        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):
            if not (QtCore.Qt.ItemIsEditable & index.flags()):
                return None
            cb = MyCheckBox(parent)
            cb.clicked.connect(self.stateChanged)
            return cb
    
        def setEditorData(self, editor, index):
            """ Update the value of the editor """
            editor.blockSignals(True)
            editor.setChecked(index.data())
            editor.blockSignals(False)
    
        def setModelData(self, editor, model, index):
            """ Send data to the model """
            model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
    
        def paint(self, painter, option, index):
            value = index.data()
            if value:
                value = QtCore.Qt.Checked
            else:
                value = QtCore.Qt.Unchecked
            self.drawCheck(painter, option, option.rect, value)
            self.drawFocus(painter, option, option.rect)
    
        @QtCore.Slot()
        def stateChanged(self):
            print "sender", self.sender()
            self.commitData.emit(self.sender())
    

    另一种方法是使用继承而不是包含/委派。下面是一个使用的示例:

    class MyCheckBox(QtGui.QCheckBox):
        def __init__(self, parent):
            QtGui.QCheckBox.__init__(self, parent)
            # Do some customisation here
    
        # Might want to customise the paint here
        # def paint(self, painter, option, index):
    
    
    class CheckBoxDelegate(QtGui.QItemDelegate):
        """
        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)
    

    这似乎更直接,然而,在这种情况下,它有几个问题。很难在 我的复选框 类-这需要我们重写 paintEvent 要做到这一点,需要仔细绘制。它也不会完全覆盖代理的绘制。所以你可以把它拿出来。但是,只有在为该行创建了编辑器时,它才能工作。因此,在这种情况下,第一种解决方案可能是最简单的。