PySide2/QML 将操作附加到工具栏并在层次结构中导航
PySide2/QML append actions to ToolBar and navigate hierarchical
我正在尝试使用 QML 实现与下面 post 中提到的相同的事情。
我无法弄清楚,如何通过 PySide2 将 ToolButton 附加到 QML ToolBar 并相对更新 Gridview(基于给定的分层数据)。
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_())
QML 中的逻辑与 Qt Widgets 中的逻辑相同,但元素不同,因为例如 QML 中既不存在 QAction 也不存在 QToolBar。
我将逻辑总结如下:
当按下视图中的项目或 ToolBar/QToolBar 时,必须更新视图的 rootIndex (ListView/QListView)。
ToolBar/QToolBar 项必须是 rootIndex 及其父项。
另一方面,与 QListView 不同,ListView 不允许建立 rootIndex,因此要实现相同的功能,您必须使用 DelegateModel。
在 python 方面,我实现了一个 class 来处理导航,为此具有以下属性:
- 模型:它以分层的方式具有信息。
- headers: 有文本的信息和ToolBar的QModelIndex。
- rootIndex.
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)
}
}
}
}
}
}
}
}
}
}
我正在尝试使用 QML 实现与下面 post 中提到的相同的事情。
我无法弄清楚,如何通过 PySide2 将 ToolButton 附加到 QML ToolBar 并相对更新 Gridview(基于给定的分层数据)。
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_())
QML 中的逻辑与 Qt Widgets 中的逻辑相同,但元素不同,因为例如 QML 中既不存在 QAction 也不存在 QToolBar。
我将逻辑总结如下:
当按下视图中的项目或 ToolBar/QToolBar 时,必须更新视图的 rootIndex (ListView/QListView)。
ToolBar/QToolBar 项必须是 rootIndex 及其父项。
另一方面,与 QListView 不同,ListView 不允许建立 rootIndex,因此要实现相同的功能,您必须使用 DelegateModel。
在 python 方面,我实现了一个 class 来处理导航,为此具有以下属性:
- 模型:它以分层的方式具有信息。
- headers: 有文本的信息和ToolBar的QModelIndex。
- rootIndex.
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)
}
}
}
}
}
}
}
}
}
}