AME
AME

Reputation: 2609

How to lazy load changing data into a QColumnView (with PyQt)?

I have a hierarchical data source for a QColumnView I want to fill. The data source loads the data from a server using a REST interface.

Lets say the hierarchy looks like this:

Car_Manufacturer -> Car_Type -> Specific_Model -> Motor_Type

I have to use a QColumnView to display this (since it is a customer requirement). The behavior is supposed to be like this:

When the program starts, it loads the Car_Manufacturer from the server. When one of the Car_Manufacturer items is clicked, the Car_Type items for the selected Car_Manufacturer is loaded from the server and displayed in a new column. When the Car_Manufacturer is clicked again, the data has to be fetched again from the server and the column has to be updated. When Car_Type is clicked, the Specific_Model items for this Car_Manufacturer and Car_type have to be queried from the server and loaded into a new column... and so on.

The datasource has this api:

datasource.get_manufacturers(hierarchy)  # hierarchy = []
datasource.get_car_type(hierarchy)  # hierarchy = [manufacturer, ]
datasource.get_specific_model(hierarchy)  # hierarchy = [manufacturer, car_type]
datasource.get_motor_type(hierarchy)  # hierarchy = [manufacturer, car_type, specific_model ]

Where each element in the hierarchy is a string key representation of the item. When an item is clicked it has to inform a controller about this with the hierarchy of the curernt item.

How can I get the QColumnView to update the children of one item when the item is clicked using the datasource? How can this stay flexible when a new hierarchy layer is added or removed?

Upvotes: 1

Views: 1951

Answers (2)

Henning
Henning

Reputation: 81

Here is an example which implements a custom DirModel. The method _create_children is called lazily and should return a list of instances which implement AbstractTreeItem.

import sys
import os
import abc
from PyQt4.QtCore import QAbstractItemModel, QModelIndex, Qt, QVariant
from PyQt4.QtGui import QColumnView, QApplication


class TreeModel(QAbstractItemModel):

    def __init__(self, root, parent=None):
        super(TreeModel, self).__init__(parent)
        self._root_item = root
        self._header = self._root_item.header()

    def columnCount(self, parent=None):
        if parent and parent.isValid():
            return parent.internalPointer().column_count()
        else:
            return len(self._header)

    def data(self, index, role):
        if not index.isValid():
            return QVariant()
        item = index.internalPointer()
        if role == Qt.DisplayRole:
            return item.data(index.column())
        if role == Qt.UserRole:
            if item:
                return item.person
        return QVariant()

    def headerData(self, column, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            try:
                return QVariant(self._header[column])
            except IndexError:
                pass
        return QVariant()

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()
        if not parent.isValid():
            parent_item = self._root_item
        else:
            parent_item = parent.internalPointer()
        child_item = parent_item.child_at(row)
        if child_item:
            return self.createIndex(row, column, child_item)
        else:
            return QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()
        child_item = index.internalPointer()
        if not child_item:
            return QModelIndex()
        parent_item = child_item.parent()
        if parent_item == self._root_item:
            return QModelIndex()
        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent=QModelIndex()):
        if parent.column() > 0:
            return 0
        if not parent.isValid():
            parent_item = self._root_item
        else:
            parent_item = parent.internalPointer()
        return parent_item.child_count()



class AbstractTreeItem(object):

    __metaclass__ = abc.ABCMeta

    def __init__(self, parent=None):
        self._children = None
        self._parent = parent

    @abc.abstractmethod
    def header(self):
        #return ["name"]
        raise NotImplementedError(self.header)

    @abc.abstractmethod
    def column_count(self):
        #return 1
        raise NotImplementedError(self.column_count)

    def parent(self):
        return self._parent

    @abc.abstractmethod
    def _create_children(self):
        # subclass this method
        return []

    def row(self):
        if self._parent:
            return self._parent._children.index(self)
        return 0

    @property
    def children(self):
        if self._children is None:
            self._children = self._create_children()
        return self._children

    def child_at(self, row):
        return self.children[row]

    @abc.abstractmethod
    def data(self, column):
        #return ""
        raise NotImplementedError(self.data)

    def child_count(self):
        count = len(self.children)
        return count



class DirPathModel(AbstractTreeItem):

    def __init__(self, root="/", parent=None):
        super(DirPathModel, self).__init__(parent)
        self._root = root

    def _create_children(self):
        print "walking into", self._root
        children = []
        try:
            entries = os.listdir(self._root)
        except OSError:
            # no permission etc
            entries = []
        for name in entries:
            fn = os.path.join(self._root, name)
            if os.path.isdir(fn):
                children.append(self.__class__(fn, self))
        return children

    def data(self, column):
        #assert column == 0
        return os.path.basename(self._root)

    def header(self):
        return ["name"]

    def column_count(self):
        return 1


def main():
    app = QApplication(sys.argv)
    view = QColumnView()
    view.setWindowTitle("Dynamic Column view test")
    view.resize(1024, 768)
    root = DirPathModel("/")
    model = TreeModel(root)
    view.setModel(model)
    view.show()
    return app.exec_()


if __name__ == "__main__":
    sys.exit(main() or 0)

Upvotes: 3

Felipe Lema
Felipe Lema

Reputation: 2718

Asuming you can't bring all data at once and filter it out, you'll have to modify the item model (adding and removing rows) on the go based on whatever the user has selected from the QColumnView.

There's more than one way to remove the items:

  • You can use the index of the selected column and remove all items "on the left" of this column.
  • You can remove items based whose parent (or grandparent) matches the selection being made

Any option you take, you'll have to mirror the relationship between items in some way. That or implement from QAbstractItemModel, which I think would be an overkill.

Upvotes: 1

Related Questions