jeudi 3 septembre 2015

Qt CheckBox delegate generates two checkboxes

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.

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.

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

This is my code

# -*- 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):
    """docstring for BatchObject"""
    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)?




Aucun commentaire:

Enregistrer un commentaire