I'm trying to achieve same thing as mentioned in below post with QML.
Breadcrumbs navigation using QToolBar and QListView
I'm not able to figure out, How to append ToolButton via PySide2 to QML ToolBar and update Gridview relatively (based on given hierarchical data).
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls.Styles 1.4
ApplicationWindow {
id: mainWindowId
visible: true
width: 960
height: 540
title: qsTr("Breadcrumbs Test")
Rectangle {
width: parent.width
height: parent.height
ColumnLayout {
width: parent.width
height: parent.height
spacing: 6
TextField {
id: filterTextFieldId
Layout.fillWidth: true
Layout.preferredHeight: 40
font {
family: "SF Pro Display"
pixelSize: 22
}
placeholderText: "Type Filter Expression"
color: "dodgerblue"
}
ToolBar {
background: Rectangle {
color: "transparent"
}
RowLayout {
anchors.fill: parent
spacing: 10
ToolButton {
Layout.preferredHeight: 20
contentItem: Text {
text: qsTr('Home')
color: "#FFFFFF"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 12
color: "#40e0d0"
}
onClicked: crumbsNavigation.on_buttonTriggered()
}
ToolButton {
Layout.preferredHeight: 20
contentItem: Text {
text: qsTr('About')
color: "#FFFFFF"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 12
color: "#40e0d0"
}
onClicked: crumbsNavigation.on_buttonTriggered()
}
ToolButton {
Layout.preferredHeight: 20
contentItem: Text {
text: qsTr('Contact')
color: "#FFFFFF"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 12
color: "#40e0d0"
}
onClicked: crumbsNavigation.on_buttonTriggered()
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "dodgerblue"
GridView {
id: crumbsViewId
width: parent.width
height: parent.height
anchors.fill: parent
anchors.margins: 12
cellWidth: 130
cellHeight: 130
model: crumbsNavigation.model
delegate: Text {text:qsTr('Hello'); color:"white"}
focus: true
}
}
}
}
}
qmlBreadcrumbs.py
from PySide2 import QtCore, QtQuick, QtGui, QtWidgets, QtQml
import os
import sys
import re
crumbs_data = {"books":{
"web":{
"front-end":{
"html":["the missing manual", "core html5 canvas"],
"css":["css pocket reference", "css in depth"],
"js":["you don't know js", "eloquent javascript"]
},
"back-end":{
"php":["modern php", "php web services"],
"python":["dive into python", "python for everybody",
"Think Python", "Effective Python", "Fluent Python"]
}
},
"database":{
"sql":{
"mysql":["mysql in a nutshell", "mysql cookbook"],
"postgresql":["postgresql up and running", "practical postgresql"]
},
"nosql":{
"mongodb":["mongodb in action", "scaling mongodb"],
"cassandra":["practical cassandra", "mastering cassandra"]
}}}}
def dict_to_model(item, d):
if isinstance(d, dict):
for k, v in d.items():
it = QtGui.QStandardItem(k)
item.appendRow(it)
dict_to_model(it, v)
elif isinstance(d, list):
for v in d:
dict_to_model(item, v)
else:
item.appendRow(QtGui.QStandardItem(str(d)))
class crumbsNavigation(QtCore.QObject):
clicked = QtCore.Signal(QtCore.QModelIndex)
def __init__(self, json_data, parent=None):
super(crumbsNavigation, self).__init__(parent)
self.model = QtGui.QStandardItemModel(self)
dict_to_model(self.model.invisibleRootItem(), json_data)
it = self.model.item(0, 0)
ix = self.model.indexFromItem(it)
@QtCore.Slot(QtCore.QModelIndex)
def on_clicked(self, index):
if not self.model.hasChildren(index):
self.clicked.emit(index)
return
action = self.toolbar.addAction(index.data())
action.setData(QtCore.QPersistentModelIndex(index))
self.listview.setRootIndex(index)
@QtCore.Slot(QtWidgets.QAction)
def on_actionTriggered(self, action):
ix = action.data()
model = ix.model()
self.listview.setRootIndex(QtCore.QModelIndex(ix))
self.toolbar.clear()
ixs = []
while ix.isValid():
ixs.append(ix)
ix = ix.parent()
for ix in reversed(ixs):
action = self.toolbar.addAction(ix.data())
action.setData(ix)
@QtCore.Slot()
def on_buttonTriggered(self):
print('Toolbutton Triggered')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
crumbObject = crumbsNavigation(crumbs_data)
engine.rootContext().setContextProperty("crumbsNavigation", crumbObject)
engine.load(QtCore.QUrl.fromLocalFile('E:/Tech/main.qml'))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
The logic in QML is the same in Qt Widgets but not with the same elements since for example neither QAction nor QToolBar exist in QML.
I will summarize the logic in the following:
The rootIndex of the view (ListView/QListView) must be updated when an item in the view or the ToolBar/QToolBar is pressed.
ToolBar/QToolBar items must be rootIndex and its parents.
On the other hand, the ListView does not allow to establish a rootIndex unlike QListView, so to implement the same functionality you must use DelegateModel.
On the python side I implement a class that handles navigation, having for this the properties:
main.py
from PySide2 import QtCore, QtGui, QtWidgets, QtQml
crumbs_data = # ...
def dict_to_model(item, d):
if isinstance(d, dict):
for k, v in d.items():
it = QtGui.QStandardItem(k)
item.appendRow(it)
dict_to_model(it, v)
elif isinstance(d, list):
for v in d:
dict_to_model(item, v)
else:
item.appendRow(QtGui.QStandardItem(str(d)))
class NavigationManager(QtCore.QObject):
headersChanged = QtCore.Signal()
rootIndexChanged = QtCore.Signal("QModelIndex")
def __init__(self, json_data, parent=None):
super().__init__(parent)
self.m_model = QtGui.QStandardItemModel(self)
dict_to_model(self.m_model.invisibleRootItem(), json_data)
self.m_headers = []
self.m_rootindex = QtCore.QModelIndex()
self.rootIndexChanged.connect(self._update_headers)
self.rootIndex = self.m_model.index(0, 0)
def _update_headers(self, ix):
self.m_headers = []
while ix.isValid():
self.m_headers.insert(0, [ix, ix.data()])
ix = ix.parent()
self.headersChanged.emit()
@QtCore.Property(QtCore.QObject, constant=True)
def model(self):
return self.m_model
@QtCore.Property("QVariantList", notify=headersChanged)
def headers(self):
return self.m_headers
def get_root_index(self):
return self.m_rootindex
def set_root_index(self, ix):
if self.m_rootindex != ix:
self.m_rootindex = ix
self.rootIndexChanged.emit(ix)
rootIndex = QtCore.Property(
"QModelIndex", fget=get_root_index, fset=set_root_index, notify=rootIndexChanged
)
if __name__ == "__main__":
import os
import sys
navigation_manager = NavigationManager(crumbs_data)
model = QtGui.QStandardItemModel()
app = QtWidgets.QApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("navigation_manager", navigation_manager)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQml.Models 2.13
ApplicationWindow {
id: mainWindowId
visible: true
width: 960
height: 540
title: qsTr("Breadcrumbs Test")
Rectangle {
width: parent.width
height: parent.height
ColumnLayout {
width: parent.width
height: parent.height
spacing: 6
TextField {
id: filterTextFieldId
Layout.fillWidth: true
Layout.preferredHeight: 40
font {
family: "SF Pro Display"
pixelSize: 22
}
placeholderText: "Type Filter Expression"
color: "dodgerblue"
}
ToolBar {
background: Rectangle {
color: "transparent"
}
RowLayout {
anchors.fill: parent
spacing: 10
Repeater{
model: navigation_manager.headers
ToolButton {
Layout.preferredHeight: 20
contentItem: Text {
text: model.modelData[1]
color: "#FFFFFF"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 12
color: "#40e0d0"
}
onClicked: navigation_manager.rootIndex = model.modelData[0]
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "dodgerblue"
ListView{
id: view
anchors.fill: parent
anchors.margins: 12
model: DelegateModel {
model: navigation_manager.model
rootIndex: navigation_manager.rootIndex
delegate: Rectangle {
height: 25
color:"transparent"
Text {
text: model.display
color:"white"
MouseArea{
anchors.fill: parent
onClicked: {
if (model.hasModelChildren)
navigation_manager.rootIndex = view.model.modelIndex(index)
}
}
}
}
}
}
}
}
}
}