I have a script that contains 4 classes: class Node, class TreeViewModel, class Prj_Collection and class Prj.
First I create Prj objects using Prj class that are added to the dictionary using Prj_Collection class. Then dictionary is converted to a list and attribute "name" of each object is represented in QTreeView using class Node and class TreeViewModel. I have a "selection_changed" function that returns the "number" attribute of selected object when selection is changed.
The way how right now everything is implemented is that I populate QTreeView with strings that doesn't have any reference to a Prj object. And when selection is changed I do a search through all objects to find an object for which "name" atribute and selected item in QTreeView match in order to find object of interest and have access to other attributes of that object, e.g. "number" attribute.
I guess that there should be a way that QTreeView can contain a reference directly to the Prj object and not to the string. But how can I implement it, that TreeViewModel will contain information about Prj objects and not just strings?
Here is the code for aforementioned 4 classes and "selection_changed" function:
import sys, os
from PySide6 import QtCore
from PySide6.QtWidgets import QTreeView, QApplication
from PySide6.QtCore import QAbstractItemModel, QModelIndex
import random
class Node(object):
def __init__(self, name="", parent=None):
self._parent = parent
self._name = name
self._children = []
def children(self):
return self._children
def hasChildren(self):
return bool(self.children())
def parent(self):
return self._parent
def name(self):
return self._name
def set_name(self, name):
self._name = name
def type_info(self):
return 'NODE'
def columnCount(self):
return 1
def child_count(self):
return len(self._children)
def add_child(self, child):
self._children.append(child)
child._parent = self
def insert_child(self, position, child):
if 0 <= position < self.child_count():
self._children.insert(position, child)
child._parent = self
return True
return False
def remove_child(self, position):
if 0 <= position < len(self._children):
child = self._children.pop(position)
child._parent = None
return True
return False
def child(self, row):
if 0 <= row < self.child_count():
return self._children[row]
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
return -1
def find_child_by_name(self, name):
for child in self._children:
if child.name() == name:
return child
return None
def log(self, tab_level=-1):
output = ''
tab_level += 1
for i in range(tab_level):
output += '\t'
output += '|____' + self._name + '\n'
for child in self._children:
output += child.log(tab_level)
tab_level -= 1
return output
def __repr__(self):
return self.log()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class TreeViewModel(QAbstractItemModel):
def __init__(self, opened_fits=None, parent=None):
super().__init__(parent)
self._root_node = Node()
self.opened_fits = opened_fits or []
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return 'Name'
else:
return 'Type'
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QModelIndex()
node = parent.internalPointer() if parent.isValid() else self._root_node
if node.children:
return self.createIndex(row, column, node.child(row))
else:
return QModelIndex()
def parent(self, child):
if not child.isValid():
return QModelIndex()
node = child.internalPointer()
if node.row() >= 0:
return self.createIndex(node.row(), 0, node.parent())
return QModelIndex()
def rowCount(self, parent=QModelIndex()):
node = parent.internalPointer() if parent.isValid() else self._root_node
return node.child_count()
def columnCount(self, parent=QModelIndex()):
return 1
def hasChildren(self, parent=QModelIndex()):
node = parent.internalPointer() if parent.isValid() else self._root_node
return node.hasChildren()
def data(self, index: QModelIndex(), role=QtCore.Qt.DisplayRole):
if index.isValid() and role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole,):
node = index.internalPointer()
return node.name()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role in (QtCore.Qt.EditRole,):
node = index.internalPointer()
node.set_name(value)
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index: QModelIndex()):
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
def indexFromItem(self, it):
root_index = QModelIndex()
if isinstance(it, Node):
parents = []
while it is not self._root_node:
parents.append(it)
it = it.parent()
root = self._root_node
for parent in reversed(parents):
root = root.find_child_by_name(parent.name())
root_index = self.index(root.row(), 0, root_index)
return root_index
def item_from_path(self, path, sep):
depth = path.split(sep)
root = self._root_node
for d in depth:
root = root.find_child_by_name(d)
if root is None:
return None
return root
def appendRow(self, item, parent=None):
self.appendRows([item], parent)
def appendRows(self, items, parent=None):
if isinstance(items, list):
ix = self.indexFromItem(parent)
self.insertRows(self.rowCount(ix), items, parent)
def insertRows(self, position, items, parent=None):
parent_index = self.indexFromItem(parent)
self.beginInsertRows(parent_index, position, position + len(items) - 1)
if parent is None:
parent = self._root_node
for item in items:
parent.add_child(item)
self.endInsertRows()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class Prj_Collection(dict):
def __init__(self, *args, **kwargs):
super(Prj_Collection, self).__init__(*args, **kwargs)
def prj(self, *args, **kwargs):
f = Prj(*args, **kwargs)
if os.path.basename(f.project_path) in self:
self[os.path.basename(f.project_path)].append(f)
else:
self[os.path.basename(f.project_path)] = [f]
return f
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class Prj(object):
def __init__(self, name, project_path):
self.name = name
self.project_path = project_path
self.number = random.randint(1, 100)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def selection_changed(selected, deselected):
# return selected index which is QModelIndex
model_index = selected.indexes()[0]
# Return data correcponding to QModelIndex
print(model.data(model_index))
# Dictionary of objects to list
_list = list(map(list, dictionary.items()))
# Return an object that correcponds to the selected item in a QTreeView
for item in _list:
for x in item[1]:
if x.name == model.data(model_index):
print("i found object!")
print(x.number)
break
else:
x = None
Here is the code for populating data and representing in a QTreeView:
# populate data
folder = ["project1", "project2", "project3"]
dictionary = Prj_Collection()
for item in folder:
dictionary.prj(item, "folder")
#represent data in a QTreeView
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
app = QApplication(sys.argv)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
view = QTreeView()
model = TreeViewModel()
view.setModel(model)
model.appendRow(Node("folder"))
_list = list(map(list, dictionary.items())) # make a list from dictionary
for root, dirs in _list:
depth = root.split(os.sep)
if not root.startswith("folder"):
root = "folder"
it = model.item_from_path(root, os.sep)
(model.appendRows([Node(_dir.name) for _dir in dirs], it))
view.show()
view.selectionModel().selectionChanged.connect(selection_changed)
sys.exit(app.exec())
I found an example that used QAbstractItemModel with QTreeView for accessing objects. I adjust code from the example to my need with PySide5:
from PySide6 import QtGui, QtCore
from PySide6.QtWidgets import QTreeView, QApplication, QDialog, QVBoxLayout, QLabel, QFrame, QHBoxLayout, QPushButton
import random
HORIZONTAL_HEADERS = ("Project", "Folder")
class PrjCollection(list):
def __init__(self, *args, **kwargs):
super(PrjCollection, self).__init__(*args, **kwargs)
def create_prj(self, *args, **kwargs):
p = Prj(*args, **kwargs)
self.append(p)
return p
class Prj(object):
def __init__(self, name, folder):
self.folder = folder
self.name = name
self.number = random.randint(0, 100)
def __repr__(self):
return "PROJECT - %s %s" % (self.name, self.folder)
class TreeItem(object):
def __init__(self, project, header, parent_item):
self.project = project
self.parent_item = parent_item
self.header = header
self.children = []
def add_child(self, child):
self.children.append(child)
def child(self, row):
return self.children[row]
def child_count(self):
return len(self.children)
def column_count(self):
return 1
def data(self, column):
if self.project == None:
if column == 0:
# return QtCore.QVariant(self.header)
return self.header
if column == 1:
# return QtCore.QVariant("")
return ""
else:
if column == 0:
# return QtCore.QVariant(self.project.name)
return self.project.name
if column == 1:
# return QtCore.QVariant(self.project.folder)
return self.project.folder
# return QtCore.QVariant()
return None
def parent(self):
return self.parent_item
def row(self):
if self.parent_item:
return self.parent_item.children.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, projects, parent=None):
super(TreeModel, self).__init__(parent)
self.projects = projects or []
self.root_item = TreeItem(None, "All", None)
self.parents = {0: self.root_item}
self.setup_model_data()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().column_count()
else:
return 1
def data(self, index, role):
if not index.isValid():
# return QtCore.QVariant()
return None
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.project
# return QtCore.QVariant()
return None
def headerData(self, column, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
try:
# return QtCore.QVariant(HORIZONTAL_HEADERS[column])
return HORIZONTAL_HEADERS[column]
except IndexError:
pass
# return QtCore.QVariant()
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.root_item
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()
if not childItem:
return QtCore.QModelIndex()
parentItem = childItem.parent()
if parentItem == self.root_item:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.root_item
else:
p_Item = parent.internalPointer()
return p_Item.child_count()
def setup_model_data(self):
for project in self.projects:
folder = project.folder
if folder not in self.parents:
newparent = TreeItem(None, folder, self.root_item)
self.root_item.add_child(newparent)
self.parents[folder] = newparent
parentItem = self.parents[folder]
newItem = TreeItem(project, "", parentItem)
parentItem.add_child(newItem)
def searchModel(self, project):
def searchNode(node):
for child in node.children:
if project == child.project:
index = self.createIndex(child.row(), 0, child)
return index
if child.child_count() > 0:
result = searchNode(child)
if result:
return result
retarg = searchNode(self.parents[0])
return retarg
def find_GivenName(self, name):
app = None
for project in self.projects:
if project.name == name:
app = project
break
if app != None:
index = self.searchModel(app)
return (True, index)
return (False, None)
if __name__ == "__main__":
def row_clicked(index):
print(tv.model().data(index, QtCore.Qt.UserRole).number)
app = QApplication([])
prj_col = PrjCollection()
for name, folder in (("Project1", "Folder1"),
("Project2", "Folder1"), ("Project11", "Folder2")):
prj_col.create_prj(name, folder)
model = TreeModel(prj_col)
dialog = QDialog()
dialog.setMinimumSize(300, 150)
layout = QVBoxLayout(dialog)
tv = QTreeView(dialog)
tv.setModel(model)
tv.setAlternatingRowColors(True)
layout.addWidget(tv)
tv.clicked[QtCore.QModelIndex].connect(row_clicked)
dialog.exec()
app.closeAllWindows()