Qt CheckBox 委托生成两个复选框 [英] Qt CheckBox delegate generates two checkboxes

查看:149
本文介绍了Qt CheckBox 委托生成两个复选框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to implement some kind of list view within a PySide GUI which gives the user the opportunity to enable/disable some entries of the list before finally processing the list.

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

I decided to use a QTableView and QAbstractTableModel with a CheckBoxDelegate class which renders a checkbox for each row in the table view. Checking and unchecking an entry will set the enabled attribute of the underlying list's object accordingly. This allows me to easily skip entries when processing.

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

I want to draw a centered checkbox. Thus i am using a subclass of QCheckbox within the CheckBoxDelegate based on this SO question https://stackoverflow.com/a/11802138/1504082. Now my problem is that i am getting two checkboxes in column 0. But i dont understand why...

这是我的代码

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

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

Can anybody give me an explanation why this happens (and how i could fix it)?

推荐答案

你有这个问题,因为你的 MyCheckBox 类都一个 QCheckBox(通过继承)并且还一个 QCheckBox 通过在它的 init 中构造一个新的 QCheckBox 实例(self.cb).

You have the problem because your MyCheckBox class both is a QCheckBox (by inheritance) and also has a QCheckBox by constructing a new QCheckBox instance in its init (self.cb).

你真的只想做一个或另一个.为了演示,我像这样重写了 MyCheckBox 类:

You really only want to do one or the other. Just to demonstrate, I rewrote the MyCheckBox class like this:

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() 方法,因此我将它们合并为一个:

and this fixes the problem (though you need to make some other changes too). Note that the clicked signal you use needs to come from the MyCheckBox not the QCheckBox so I have added it to the containing class via the amClicked slot. You don't need to distinguish the data() and checked_state() methods in your model so I have merged them into one:

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方法中做正确的事情.

Then the Delegate looks like this. I have arranged for it only to provide an Editor if the flags say it is editable. If not, then it is responsible for the drawing so it also has to do the correct thing in the paint method.

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

另一种方法是使用继承而不是包含/委托.这是一个使用它的示例:

Another approach would be to use inheritance rather than inclusion/delegation. Here's an example using that:

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 并且要做到这一点需要仔细绘制.它也不会完全覆盖代表的油漆.所以你可以把它拿出来.但是只有在为该行创建了编辑器时它才会起作用.所以在这种情况下,第一个解决方案可能是最简单的.

This seems to be more straightforward, however, in this case it has a couple of problems. It is difficult to draw the checkbox centred in the MyCheckBox class - that would need us to override the paintEvent and to do that will need careful drawing. It also will not exactly overwrite the paint of the Delegate. So you could take that out. But then it will only work if the editor has been created for the row. So the first solution is probably easiest in this case.

这篇关于Qt CheckBox 委托生成两个复选框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆