Reputation: 2609
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
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
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:
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