Hello,

I had a hard time figuring out why my QAbstractItemModel subclass wasn't
working properly. I added debug statements everywhere, which showed that
my subclass was behaving fine. The only symptoms were:

  1. The associated QTreeView() had an empty display.

  2. My model was only queried for the columnCount() of the root item
     (twice) and for part of the headerData(), and that's all. No
     rowCount(), no index(), no data(), nothing.

I reduced the problem to a minimal example, and eventually found that it
was caused by the model (subclass of QAbstractItemModel) falling off the
scope, and therefore presumably gargabe-collected by Python. If I keep
an explicit reference to the model, everything works fine.

My question is: is this behavior normal?

I mean, I have something like:

  view.setModel(model)
  return view

followed by:

  view = [...]
  app.exec_()

so that the view is surely not garbage-collected when the app is
running. It seems to me that 'view.setModel(model)' should have added a
reference from 'view' to 'model', so that 'model' doesn't get
garbage-collected either.

But clearly, this isn't the case, because everything works fine as long
as I keep an explicit reference to 'model' when running app.exec_().

To make this clear, I'm attaching my minimal example. There you'll find
the following function:

def test():
    model = FooBarModel(files)
    view = QtGui.QTreeView()
    view.setModel(model)

    return view, model

If my main() function calls:

  view, model = test()

then everything works fine, because we keep a reference to the model as
long as app.exec_() runs.

But if I change this line into:

  view = test()[0]

then everything fails in a very hard-to-debug way, the reason being
presumably that the model is garbage-collected by Python during the
exection of app.exec_().

The tools I'm using are:

  * Python 2.4.4 (#2, Apr  5 2007, 20:11:18) 
    [GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
    (from Debian etch)

  * Qt 4.2.1
     (from Debian etch; version 4.2.1-2+etch1 of the qt4 packages)

  * PyQt 4.2
    (from Debian unstable; python-qt4 packages version 4.2-1 backported
    by myself)

Thanks in advance for your comments.
#! /usr/bin/env python

import sys
from PyQt4 import QtCore, QtGui


def debug(s):
    sys.stderr.write(s + '\n')

files = [("foo", 12),
         ("bar", 13),
         ("baz", 72)]


class FooBarModel(QtCore.QAbstractItemModel):
    def __init__(self, files, parent=None):
        QtCore.QAbstractItemModel.__init__(self, parent)

        self.files = files
        self.num_columns = 2

    def rowCount(self, parent):
        debug("*** rowCount(self, parent=%s)" % (parent,))
        debug("parent.internalPointer() = %s" % (parent.internalPointer(),))
        debug("parent.isValid(): %s" % parent.isValid())

        if parent.isValid():
            res = 0
        else:
            res = len(self.files)

        debug("-> return %s" % res)
        return res

    def columnCount(self, parent):
        debug("*** columnCount(self, parent=%s)" % (parent,))
        debug("parent.internalPointer() = %s" % (parent.internalPointer(),))

        if parent.isValid():
            res = 0
        else:
            res = self.num_columns

        debug("-> return %s" % res)
        return res

    def index(self, row, column, parent):
        debug("*** index(self, row=%s, column=%s, parent=%s)" % \
              (row, column, parent))
        debug("parent.internalPointer() = %s" % (parent.internalPointer(),))

        # Only the root element has childs
        if parent.isValid():
            return QtCore.QModelIndex()

        if row < 0 or column < 0 \
           or row >= len(self.files) or column >= self.num_columns:
            return QtCore.QModelIndex()

        return self.createIndex(row, column, self.files[row])

    def data(self, index, role):
        debug("*** data(self, index=%s, role=%s)" % (index, role))
        debug("index.internalPointer() = %s" % (index.internalPointer(),))

        if not index.isValid():
            return QtCore.QVariant()

        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        item = index.internalPointer()

        if index.column() == 0:
            data = item[0]
        elif index.column() == 1:
            data = item[1]
        else:
            assert False, index.column()
        
        return QtCore.QVariant(data)

    def flags(self, index):
        debug("*** flags(self, index=%s)" % (index,))

        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled
        else:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def headerData(self, section, orientation, role):
        debug("*** headerData(self, section=%s, orientation=%s, role=%s)" \
              % (section, orientation, role))

        data = map(QtCore.QVariant, [self.tr("File"), self.tr("Size")])

        if orientation == QtCore.Qt.Horizontal \
               and role == QtCore.Qt.DisplayRole:
            res = data[section]
        else:
            res = QtCore.QVariant()

        debug("-> return %s (str: '%s')" % (repr(res), res.toString()))
        return res

    def parent(self, index):
        debug("*** parent(self, index=%s)" % (index,))

        return QtCore.QModelIndex()


def test():
    model = FooBarModel(files)
    view = QtGui.QTreeView()
    view.setModel(model)

    return view, model


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    # With this line, everything works
#    view, model = test()

    # But with this one, no luck
    view = test()[0]

    view.show()

    sys.exit(app.exec_())
-- 
Florent
_______________________________________________
PyQt mailing list    [email protected]
http://www.riverbankcomputing.com/mailman/listinfo/pyqt

Reply via email to