[PyQt] problem with setItemDelegateForColumn and ItemIsUserCheckable

Brian DeWeese brian.deweese at gmail.com
Wed Jun 6 16:00:10 BST 2007


David,

Thanks for your reply.  I was passing a QObject (a QDialog to be exact) as
the parent to the delegate but I wasn't storing a reference to the delegate
anywhere so perhaps it was being deleted in some cases.  I added a variable
to store the reference to the delegate but unfortunately that didn't change
anything.  I pared down the code to it's bare minimum and included it here.
Thanks for looking into the problem.

Brian DeWeese

<code start of BugTest.py>

#!/usr/bin/env python

import sys
from PyQt4 import QtGui, QtCore

_qvChecked = QtCore.QVariant(QtCore.Qt.Checked)
_qvUnchecked = QtCore.QVariant(QtCore.Qt.Unchecked)

##------------------------------------------------------------------------------
class BTObject(object):

    def __init__(self, enabled=False, foo='', bar=0):
        object.__init__(self)
        self._enabled = enabled
        self._foo = foo
        self._bar = bar

    def isEnabled(self):
        return self._enabled

    def setEnabled(self, b=True):
        self._enabled = b

    def createInlineEditor(self, parent):
        return BTObject.InlineEditor(self, parent)

    def __repr__(self):
        return 'BTObject(enabled='+str(self._enabled)+',
foo=\"'+str(self._foo)+'\", bar='+str(self._bar)+')'

    class InlineEditor(QtGui.QWidget):

        _MUTE = 'MUTE'

        def __init__(self, btobject, parent):
            QtGui.QWidget.__init__(self, parent)
            self._btobject = btobject

            self.setAutoFillBackground(True)
            lo = QtGui.QHBoxLayout()
            lo.setMargin(0)
            lo.setSpacing(4)

            self._cbFoo = QtGui.QComboBox()
            for x in ["ABC", "DEF", "GHI", "JKL"]:
                self._cbFoo.addItem(x)

            self._leBar = QtGui.QLineEdit(str(btobject._bar), self)
            self._leBar.setValidator(QtGui.QIntValidator(0, 999999, self))

            lo.addWidget(self._cbFoo, 3)
            lo.addSpacing(5)
            lo.addWidget(QtGui.QLabel('Bar:'))
            lo.addWidget(self._leBar, 3)
            lo.addStretch(5)
            self.setLayout(lo)

            # set the object data into the gui

self._cbFoo.setCurrentIndex(self._cbFoo.findText(self._btobject._foo))
            self._leBar.setText(str(self._btobject._bar))

        def accept(self):
            text = str(self._cbFoo.currentText())
            self._btobject._foo = text
            self._btobject._bar = int(self._leBar.text())
            print 'accept: btobject='+repr(self._btobject)

        def reject(self):
            pass
##>--------------------------------------------------------------------------<##
class BTModel(QtCore.QAbstractTableModel):

    def __init__(self, parent=None ):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._items = [BTObject(foo="ABC", bar=1),
                       BTObject(foo="DEF", bar=2),
                       BTObject(foo="GHI", bar=3)]
        self._headerData = (QtCore.QVariant("Name"), QtCore.QVariant
("repr"))

    def columnCount(self, parentIndex):
        return len(self._headerData)

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled

        if index.column() == 0:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsUserCheckable
        elif index.column() == 1:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEditable
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def getItemAt(self, row):
        if row >= 0 and row < len(self._items):
            return self._items[row]
        return None

    def indexOfItem(self, item):
        return self._items.index(item)

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role in (
QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
            return self._headerData[section]

        return QtCore.QVariant()

    def rowCount(self, parentIndex):
        return len(self._items)

    def setData(self, index, value, role):
        if index.isValid():
            if index.column() == 0 and role == QtCore.Qt.CheckStateRole:
                state = value.toInt()[0] # int value stored as a tuple,
where's that documented?
                btobject = self._items[index.row()]
                btobject.setEnabled(state == QtCore.Qt.Checked)

                # Force a repaint of the entire row.
                index2 = self.createIndex(index.row(), 1)
                self.emit(QtCore.SIGNAL('dataChanged(const QModelIndex &,
const QModelIndex &)'), index2, index2)

        return True

    def data( self, index, role ):
        if not index.isValid():
            return QtCore.QVariant()

        if role == QtCore.Qt.DisplayRole:
            col = index.column()
            if col == 0:
                return QtCore.QVariant(self._items[index.row()]._foo)
            elif col == 1:
                return QtCore.QVariant(repr(self._items[index.row()]))

        elif role == QtCore.Qt.CheckStateRole:
            if index.column() == 0:
                retVal = _qvUnchecked
                btobject = self._items[index.row()]
                if btobject.isEnabled():
                    retVal = _qvChecked
                return retVal

        return QtCore.QVariant()

##>--------------------------------------------------------------------------<##
class BTItemDelegate(QtGui.QItemDelegate):

    def __init__(self, parent):
        QtGui.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        if index.column() == 1:
            model = index.model()
            btobject = model.getItemAt(index.row())
            editor = btobject.createInlineEditor(parent)
            return editor
        return QtGui.QItemDelegate.createEditor(self, parent, option, index)

    def setEditorData(self, editor, index):
        ''' I don't need to do anything here because I passed in the object
            being edited when the editor was constructed.
        '''
        pass

    def setModelData(self, editor, model, index):
        editor.accept()

##>--------------------------------------------------------------------------<##
class BTEditor(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self.setWindowTitle('BTObject Editor')

        # Create a button box for the dialog containing the Ok and Cancel
buttons
        buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok
                                         | QtGui.QDialogButtonBox.Cancel);
        QtCore.QObject.connect(buttonBox, QtCore.SIGNAL('accepted()'),
self.accept)
        QtCore.QObject.connect(buttonBox, QtCore.SIGNAL('rejected()'),
self.reject)

        # The tree view widget
        self._view = BTEditor.TableView(self)
        self._view.setMinimumSize(QtCore.QSize(300, 100))

        self._delegate = BTItemDelegate(self)


#----------------------------------------------------------------------#
        # If you comment out the setItemDelegat calls than the checkable
        # column will work correctly.  If setItemDelegate is uncommented
        # than the custom editor will work but it breaks the checkable
        # column. If setItemDelegateForColumn is uncommented than the
        # checkable column works correctly but my editor is never used
        # either.

#----------------------------------------------------------------------#
        self._view.setItemDelegate(self._delegate)
        #self._view.setItemDelegateForColumn(1, self._delegate) # this
doesn't work

        self._model = BTModel()
        self._view.setModel(self._model)

        # The final layout, putting it all together
        gl = QtGui.QGridLayout()
        gl.addWidget(self._view   , 1, 0, 1, 2)
        gl.addWidget(buttonBox    , 2, 0, 1, 2)
        self.setLayout(gl)


##------------------------------------------------------------------------##
    class TableView(QtGui.QTableView):
        def __init__(self, parent):
            QtGui.QTableView.__init__(self, parent)
            self.verticalHeader().hide()
            self.setAlternatingRowColors(True)
            self.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked |
                                 QtGui.QAbstractItemView.EditKeyPressed)
            self.setGridStyle(QtCore.Qt.NoPen)
            self.setLineWidth(0)
            self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
            self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
            self.horizontalHeader().setStretchLastSection(True)
            self.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents )
            self.verticalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents )

        def sizeHint(self):
            return QtCore.QSize(600, 100)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    win = BTEditor()
    win.show()
    app.connect(app, QtCore.SIGNAL('lastWindowClosed()'), app, QtCore.SLOT
('quit()'))
    sys.exit(app.exec_())


</code>

Brian DeWeese

On 6/5/07, David Boddie <david at boddie.org.uk> wrote:
>
> On Fri, 1 Jun 2007 09:56:25 -0500, Brian DeWeese wrote:
>
> > I have a 2 column QTableView where column 0 is checkable and column 1 is
> > editable. I've written a custom editor by implement QItemDelegate which
> is
> > working fine. Also, the checkbox in column 0 is also working fine. But
> > not both at the same time.
> >
> > If I use view.setItemDelegate(myDelegate) than my delegate is called to
> > create my custom editor and everything about column 1 works correctly.
> But
> > column 0 doesn't work correctly. It is displaying a checkbox with the
> > correct current value but clicking on it does not call my model's
> setData()
> > method or do anything at all as far as I can tell.
>
> OK. This doesn't sound right.
>
> > If I use view.setItemDelegateForColumn(1, myDelegate) than the checkbox
> in
> > colum 0 works but double-clicking on column 1 will ignore my delegate
> and
> > create a default editor.
>
> Did you create the delegate in a way that stops it from being garbage
> collected and subsequently deleted on the C++ side? In other words,
> did you store the instance somewhere, or create it with a parent QObject?
>
> I'm guessing that you did, otherwise you wouldn't see your editor in the
> previous situation. :-/
>
> > Is this a known bug in either PyQt or Qt itself? Or am I doing something
> > wrong?
>
> I would like to see more code before declaring something wrong with
> setItemDelegateForColumn() in either Qt or PyQt.
>
> > I'm using PyQt 4.1.1 with Qt 4.2 on SUSE 10.1. (BTW, Is there a proper
> way
> > to verify that I'm using the versions that I think I'm using?)
>
> from PyQt4 import pyqtconfig
> hex(pyqtconfig._pkg_config["pyqt_version"])
> hex(pyqtconfig._pkg_config["qt_version"])
>
> > Here is my model.flags() method.
> >
> > def flags(self, index):
> > if not index.isValid():
> > return QtCore.Qt.ItemIsEnabled
> >
> > if index.column() == 0:
> > return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
> > QtCore.Qt.ItemIsUserCheckable
>
> You might want to make this editable, too.
>
> > elif index.column() == 1:
> > return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
> > QtCore.Qt.ItemIsEditable
> >
> > return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
>
> Hope this helps,
>
> David
>
> _______________________________________________
> PyQt mailing list    PyQt at riverbankcomputing.com
> http://www.riverbankcomputing.com/mailman/listinfo/pyqt
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20070606/7891d732/attachment-0001.html


More information about the PyQt mailing list