Reputation: 105
I'm trying to create a model that can be used for both a QTableView and QTreeView. As an example, my data is something like:
ID | Location | Name |
---|---|---|
101 | 201 | Apple |
201 | None | Kitchen |
102 | 201 | Banana |
301 | None | Cellar |
302 | 301 | Potatoes |
202 | 302 | Nail |
So every entry has a location which is itself an entry in the model. For the QTableView, I'd like to simply display all entries under each other as shown above, while for the QTreeView I'd like something like
My problem however is that I can't figure out how to implement QAbstractProxyModel.maptoSource() or mapfromSource() as I lose information about the parent in the QTableView. Reading https://www.qtcentre.org/threads/26163-Map-table-to-tree-through-model-view-possible it seems that perhaps this is not possible at all. However the QAbstractProxyModel explicitly says that's it's meant for showing data in both views. Can anyone point me in the right direction or knows whether it's possible to implement a model like this? Especially in Python, I can't find any examples unfortunately.
I really like the idea of just using an unindented TreeView as a sort of TableView. Unfortunately I'm still having trouble creating the model. Currently, only the top entries are being shown.
class MyModel(qtg.QStandardItemModel):
def __init__(
self,
engine
):
self.engine = engine
self.hierarchy_key = 'location_id'
self.column_names = ['id', 'location_id', 'name', 'quantity']
super().__init__(0, len(self.fields))
self.setHorizontalHeaderLabels(self.column_names)
self.root = self.invisibleRootItem()
self.build_model()
def build_model(self):
def add_children_to_tree(entries, parent_item):
for entry in entries:
items = []
for col in self.column_names:
text = getattr(entry, col)
item = qtg.QStandardItem(text)
items.append(qtg.QStandardItem(text))
parent_item.appendRow(items)
item = items[1] #the location_id item
parent_item.setChild(item.index().row(), item.index().column(), item)
with session_scope(self.engine) as session:
child_entries = (
session.query(self.entry_type)
.filter(
getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(
entry.id
)
)
.all()
)
if child_entries:
add_children_to_tree(child_entries, item)
self.removeRows(0, self.rowCount())
with session_scope(self.engine) as session:
root_entries = session.query(self.entry_type).filter(getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)).all()
if not isinstance(root_entries, list):
root_entries = [root_entries]
add_children_to_tree(root_entries, self.root)
The idea is that the session query results in a list of entries. Each entry is a record in the database with the attributes "id", "location_id", etc. Each attribute thus is an Item and the list of items creates a row in the model. I can't figure out how one makes the row of items a child of another row in the way it's shown here:
I assume the setChild() function needs to be called differently?
Upvotes: 0
Views: 1728
Reputation: 15632
I have been using QTreeView
for several years (usually with QStandardItemModel
) but in the non-ideal situation of knowing what works in practice but not really with much knowledge in depth.
Luke in his answer is correct about the lack of clear documentation. However, there is a superb example which deserves a lot of scrutiny here. This is direct from the Qt Group, and is a rewoking in Python (actually PySide, but easy enough to adapt to PyQt) of a previous example in C++.
What it basically does is construct something similar to a QStandardItemModel
, and show how this works in conjunction with a QTreeView
.
To get the full benefit of things, it's probably best to read the notes of the projects in this order:
... and if you then actually build your own Python model implementation, essentially copying that example, you should end up with a fair grasp of how things fit together. As I say, it is the people at Qt Group who produced these examples, so we can hopefully be fairly confident that this represents "best practice".
NB of particular interest is the file treeitem.py, containing the class TreeItem
. As explained in the C++ project notes, this does not use any Qt package imports at all! It is a pure-Python implementation of an essential component element in the structure. Luke's implementation of his class MyItem
in his answer is very similar.
Upvotes: 0
Reputation: 105
As there is a distinct lack of examples for python, I'll post my modified version of the simpletreemodel here, which is what ended up working for me. By then using a QTreeView instead of a QTableView as suggested, I got the table to behave as I wanted it too. Overall, this creates MyItem which is an item containing the entire row of information and I then use recursion to add children to parents if their value for the hierarchy_key (location_id) is equal to the id of the parent.
class MyItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column=None):
try:
if column == None:
return [self.itemData[i] for i in range(self.columnCount())]
return self.itemData[column]
except IndexError:
return None
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class MyModel(QtCore.QAbstractItemModel):
def __init__(self, entry_type, engine, hierarchy_key, description_key, parent=None):
super(ORMModel, self).__init__(parent)
self.entry_type = entry_type
if isinstance(self.entry_type, str):
self.entry_type = getattr(ds, self.entry_type)
self.engine = engine
self.hierarchy_key = hierarchy_key
self.column_names = ['id', 'location_id', 'name', 'quantity']
self.rootItem = MyItem(self.column_names)
self.setHeaderData(0, Qt.Horizontal, self.rootItem)
self.initiateModel()
def root(self):
return self.rootItem
def columnCount(self, parent):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole:
return item.data(index.column())
return None
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
def initiateModel(self):
def add_children_to_tree(entries, parent_item):
for entry in entries:
row = []
for field in self.fields.keys():
val = getattr(entry, field)
if isinstance(val, list):
text = "; ".join(map(str, val))
else:
text = str(val)
row.append(text)
item = ORMItem(row, parent_item)
parent_item.appendChild(item)
with session_scope(self.engine) as session:
child_entries = (
session.query(self.entry_type)
.filter(
getattr(
getattr(self.entry_type, self.hierarchy_key), "is_"
)(entry.id)
)
.all()
)
if child_entries:
add_children_to_tree(child_entries, item)
with session_scope(self.engine) as session:
root_entries = (
session.query(self.entry_type)
.filter(
getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)
)
.all()
)
if not isinstance(root_entries, list):
root_entries = [root_entries]
add_children_to_tree(root_entries, self.rootItem)
Upvotes: 2