Qt CheckBox委托生成两个复选框

时间:2015-09-03 07:31:14

标签: python qt checkbox pyside

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

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

我想画一个居中的复选框。因此,我基于此SO问题https://stackoverflow.com/a/11802138/1504082在CheckBoxDelegate中使用QCheckbox的子类。 现在我的问题是我在第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 个答案:

答案 0 :(得分:2)

您遇到了问题,因为您的MyCheckBox课程 一个QCheckBox(通过继承), 一个QCheckBox通过在其init(QCheckBox)中构建一个新的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()

这解决了问题(虽然你也需要做一些其他的改变)。 请注意,您使用的点击信号需要来自MyCheckBox而非QCheckBox,因此我已通过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

然后代表看起来像这样。我已经安排它只提供一个编辑器,如果标志说它是可编辑的。如果没有,那么它负责绘图,所以它也必须在paint方法中做正确的事情。

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)

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