How to implement a Qt MVC proxy model to re-arrange item/indices in a tree view ?
Hi,
I believe to have a fundamental Qt MVC question about proxy models. And I am struggling to get a working implementation working.
I have some non-qt data that I want display in two separate views. With the specialty that the underlying data is the same, and it's just the relations that are displayed differently. I also have no control over the Ui design goal and it's structure.
For example:
I have some children entities which are predominantly associated with their respective parents.
At the same time the children can also be associated with their place in a scene.
Family view (family-child-model):
├── Doe
│ └── John_Doe
├── West
│ ├── Hattie_West
│ └── North_West
└── Smith
Scene view (child-in-scene-model (proxyModel)):
├── backyard
│ ├── pool
│ │ └── John_Doe
│ ├── tree
│ │ └── Hattie_West
│ └── treehouse
│ └── Hattie_West
└── house
└── kitchen
└── North_West
Here the family view takes my original QStandartItemModel, which holds the family-child name relations. The children data objects are aware, where they are in the scene. They can query their scene location from some sort of node tree (non-qt data!).
Proxy-model-route:
I am using python, the qtpy shim and the PySide6 bindings for this.
Now instead of creating two different QStandartItemModel models, which would need to be managed separately, I want to map the family-child-model
child indices, to the child-in-scene-model
indices via a proxy model. So the child-in-scene-model
can simply be passed to a tree view without having to re-structure QStandardItems around.
e.G.
...
family_child_view = QtWidgets.QTreeView(self)
child_in_scene_view = QtWidgets.QTreeView(self)
family_child_model = QtGui.QStandardItemModel()
child_in_scene_model = MyRearrangeModel()
child_in_scene_model.setSourceModel(family_child_model)
family_child_view.setModel(family_child_model)
child_in_scene_view.setModel(child_in_scene_model)
...
So, in it's simplest form I want to create new parent QModelIndex(s) for the leaves of the family view.
Question:
In principle, is this a valid thing to try and solve via a proxy model implementation ?
My motivation for that is, when editing and selecting child items, I should get the selection model between the proxy and the base model for free and working. That way I would not have to manage the child leave's so much for every new "change scene-view request. For example the scene view is likely to change in structure. So I hope that writing another proxy model would be all that it takes to address that.
Based on some initial answers and suggestions I would like to provide some basic python boilerplate, to show what I am struggling with. It's mainly creating parent indices for a proxy child index inside a proxy model. Either when subclassing QIdentityProxyModel or even QAbstractProxyModel.
Simple code example:
To end up with something like this simple end result goal
I am trying to workout how to overwrite the QIdentityProxyModel
data
index
parent
mapFromSource
and mapToSource
methods.
import sys
from qtpy.QtCore import Qt, QAbstractProxyModel, QIdentityProxyModel, QModelIndex
from qtpy.QtGui import QStandardItemModel, QStandardItem
from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTreeView
class RearrangeProxyModel(QIdentityProxyModel):
def __init__(self, source_model):
super().__init__()
self.setSourceModel(source_model)
def mapFromSource(self, index):
"""Leaves every index unchanged except for the child_10 index.
For the child_10 index it introduces two parent level nodes.
root_10 and second_relation_10
"""
if not index.isValid():
return QModelIndex()
if index.data() == 'child_10':
# TODO create two new parent indices
# to create a new relation the resembles:
# |- root_10 <- new_parent
# | |- second_relation_10 <- new parent
# | |- parent_10
# | |- child_10
# Use self.parent and self.createIndex
pass
return QIdentityProxyModel.mapFromSource(self, index)
def data(self, index, role=Qt.DisplayRole):
source = self.mapToSource(index)
if source.isValid():
return source.data(role)
return None
class TestUI(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
original_model = QStandardItemModel()
proxy_model = RearrangeProxyModel(original_model)
# Populate the original model with some data
self.populate_model(original_model)
# Create ListViews
original_view = QTreeView(self)
proxy_view = QTreeView(self)
# Set models for ListViews
original_view.setModel(original_model)
proxy_view.setModel(proxy_model)
# Add ListViews to the layout
layout.addWidget(original_view)
layout.addWidget(proxy_view)
original_view.repaint()
proxy_view.repaint()
self.original_model = original_model
self.proxy_model = proxy_model
original_view.expandAll()
proxy_view.expandAll()
@staticmethod
def populate_model(model):
for i in range(9,12):
item = QStandardItem(f"parent_{i}")
child_item = QStandardItem(f"child_{i}")
item.appendRow([child_item])
# NOTE returns None
model.appendRow(item)
def main():
app = QApplication(sys.argv)
ui = TestUI()
ui.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Where one attempt to get it working with the mapFromSource
method.
def mapFromSource(self, index):
"""Leaves every index unchanged except for the child_10 index."""
if not index.isValid():
return QModelIndex()
if index.data() == 'parent_10':
second_relation_10 = self.createIndex(1,0)
new_idx = self.createIndex(0,0, second_relation_10.internalPointer())
return new_idx
return QIdentityProxyModel.mapFromSource(self, index)
When using new_idx = self.createIndex(0,0, second_relation_10.internalId())
it's seems to give me a strange result in the view.