TreeView 简单使用

本文主要介绍 QML 中 TreeView 的基本使用方法,包括:TreeView的适用场景;

控件简介

QML TreeView 是 Qt Quick 中的一个组件,用于显示树形结构的数据。它提供了一种以层次结构方式展示数据的方式,其中每个节点可以包含子节点。

以下是 QML TreeView 的一些关键概念和特点:

  1. Model-View 架构:QML TreeView 遵循 Model-View 架构,其中数据模型(通常是 QAbstractItemModel 的子类)负责提供数据,而视图组件则负责显示和交互。数据模型提供了节点层次结构以及每个节点的数据。
  2. Delegate 委托:TreeView 使用委托来定义每个节点的外观和行为。委托可以自定义,允许您根据需求定制每个节点的呈现方式。
  3. 展开和折叠:TreeView 支持展开和折叠节点的功能。当节点具有子节点时,可以通过点击节点旁边的展开/折叠按钮或者通过编程方式来切换节点的展开状态。
  4. 自定义节点样式:您可以自定义节点的样式,包括文本颜色、背景色、字体、图标等。通过使用不同的委托和属性设置,您可以实现丰富多样的外观效果。
  5. 选择和焦点:TreeView 支持节点的选择和焦点管理。您可以根据需要选择一个或多个节点,并可以通过编程方式操作当前焦点节点。
  6. 事件处理:TreeView 允许处理节点上的各种事件,例如点击、双击、拖放等。您可以根据需要响应这些事件并执行相应的操作。

QML TreeView 在许多应用中都有广泛的应用场景,特别是需要展示层次结构数据的场景。例如,文件浏览器、目录结构、组织架构图等都可以使用 TreeView 来呈现数据并提供交互性。

TreeView 简单使用_第1张图片

QML TreeView 提供了灵活的自定义选项,可以根据需求定制节点的样式和交互行为。通过自定义委托元素,可以为每个节点定义不同的显示方式,以满足特定的设计要求。

前置准备

阅读本节内容前请确保您了解 Qt 中与模型/视图架构相关的内容、与 Python 中调用 QML 相关的内容,包括:

  1. 模型(model)、视图(View)和委托(Delegate)之间的关系。
  2. QML 调用 Python 模型。

如不是特别熟悉,请先阅读《QML 与 Python 交互》,若您想更深入了解,请参阅官方文档《Python-QML integration》、《》。

环境准备:

  • OS:Windows 10 x64
  • Python环境:Python 3.8.x 及以上
  • Python包:PySide6(version 6.3.x 及以上)

使用方法

通过查阅官方文档,我们发现,官方提供了 QML 的 TreeView 的示例代码,但是还需要我们自己实现其对应的 model 部分,下边我们使用 Qt 官方提供的示例代码,给它匹配一个 model ,完成一个完整的节点树,主要我们可以概括为两部分:

  1. 在 Python 文件中定义 TreeView 对应的 model。
  2. 将 model 暴露给 QML ,绑定到 TreeView。

定义 model

在 QML 中,TreeView 使用的 model 必须是继承自 QAbstractItemModel 类的子类,在对 QAbstractItemModel 进行子类实例化时,至少必须实现 index() , parent() , rowCount() , columnCount()data() 。简而言之,就是:在 Python 文件中定义一个类,这个类继承自 QAbstractItemModel ,并且在这个子类中要重写这几个方法。

可选需重写的虚函数:

  • flag——是为了实现可编辑才重写的。
  • setData——是为了编辑之后把数据保存到数据结构才重写的。

要在模型中启用编辑,还必须实现:

  • setData()
  • flags()(要确保返回 ItemIsEditable)

控制模型标题的显示方式,可以实现:

  • headerData()
  • setHeaderData()

在重新实现 setData()setHeaderData() 函数时,必须分别显式地发出 dataChanged()headerDataChanged() 信号。

绑定 model 给 TreeView

在 main.py 中添加这行代码,表示将 treeModel 暴露给 QML 上下文环境中,在 QML 中可以当组件直接调用:

# ...
engine.rootContext().setContextProperty('treeModel', treeModel)
# ...

在 QML 中可以直接使用,如:

// ...
model: treeModel
// ...

这样就完成了 Python 数据模型和 QML 文件的绑定。

完整示例

项目目录结构:

Project
  ├─ main.qml
  ├─ man.py
  └─ TreeModel.py

完整代码:

// main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

 Window {
     width: 600
     height: 400
     visible: true

     TreeView {
         anchors.fill: parent
         // The model needs to be a QAbstractItemModel
         model: treeModel

         delegate: Item {
             id: treeDelegate

             implicitWidth: padding + label.x + label.implicitWidth + padding
             implicitHeight: label.implicitHeight * 1.5

             readonly property real indent: 20
             readonly property real padding: 5

             // Assigned to by TreeView:
             required property TreeView treeView
             required property bool isTreeNode
             required property bool expanded
             required property int hasChildren
             required property int depth

             TapHandler {
                 onTapped: treeView.toggleExpanded(row)
             }

             Text {
                 id: indicator
                 visible: treeDelegate.isTreeNode && treeDelegate.hasChildren
                 x: padding + (treeDelegate.depth * treeDelegate.indent)
                 anchors.verticalCenter: label.verticalCenter
                 text: "▶"
                 rotation: treeDelegate.expanded ? 90 : 0
             }

             Text {
                 id: label
                 x: padding + (treeDelegate.isTreeNode ? (treeDelegate.depth + 1) * treeDelegate.indent : 0)
                 width: treeDelegate.width - treeDelegate.padding - x
                 clip: true
                 text: model.display
             }
         }
     }
 }

这是一个使用 QML 语言编写的树形视图(TreeView)的界面定义。让我们逐行解释:

  1. TreeView: 树形视图组件,用于展示树状结构的数据。
  2. anchors.fill: parent: 将 TreeView ​组件的四个边界锚定到父级窗口,使其充满整个窗口空间。
  3. model: treeModel: 指定 TreeView ​使用的数据模型。在这里,treeModel是一个 QAbstractItemModel ​类型的模型,它提供了树形结构的数据。
  4. delegate: 定义了用于绘制每个树节点的委托元素。
  5. implicitWidthimplicitHeight: 定义委托元素的隐式宽度和高度。这些属性决定了每个委托元素的大小。
  6. indentpadding: 定义了缩进和填充的大小,用于控制节点在水平方向上的位置。
  7. TapHandler: 定义了一个用于处理点击事件的处理器。当用户点击委托元素时,将触发onTapped事件。
  8. indicator: 一个用于显示展开/折叠状态的文本元素。根据节点是否是父节点(isTreeNode)和是否有子节点(hasChildren)来决定是否显示。
  9. label: 显示节点的文本内容。根据节点的深度(depth)和是否是父节点来确定水平偏移量。

创建一个带有树形结构的 TreeView,并为每个节点定义了自定义的委托元素。委托元素根据节点的深度和展开状态来显示节点的文本和展开/折叠指示符。你需要提供一个名为treeModel的 QAbstractItemModel 类型的模型来作为 TreeView 的数据源。

# TreeModel.py
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt


# 自定义的树形节点类
class TreeNode:
    # 初始化节点数据和父亲节点
    def __init__(self, data, parent=None):
        # 存储节点数据
        self._data = data
        # 存储父节点
        self._parent = parent
        # 存储子节点
        self._children = []

    # 添加一个子节点
    def appendChild(self, child):
        self._children.append(child)

    # 删除一个子节点
    def removeChild(self, item):
        self._children.remove(item)

    # 获取某一行的子节点
    def child(self, row):
        return self._children[row]

    # 获取子节点的个数
    def childCount(self):
        return len(self._children)

    # 获取列数
    def columnCount(self):
        return 1

    # 获取节点某一列的数据
    def data(self, column):
        if column == 0:
            return self._data

    # 获取父节点
    def parent(self):
        return self._parent

    # 获取该节点在父节点中的行号
    def row(self):
        if self._parent:
            return self._parent._children.index(self)

    # 判断节点是否包含子节点
    def hasChildren(self):
        return len(self._children) > 0


# 自定义的树形数据模型
class TreeModel(QAbstractItemModel):
    # 初始化根节点
    def __init__(self, root, parent=None):
        super().__init__(parent)
        # 如果没有传入根节点,则创建一个空的根节点作为根
        self._root = root or TreeNode(None)
        # self._root = root

    # 获取某个节点在模型中的Index
    def index(self, row, column, parent=QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parentItem = self._root
        else:
            parentItem = parent.internalPointer()

        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QModelIndex()

    # 获取某个节点的父节点的Index
    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        childItem = index.internalPointer()
        parentItem = childItem.parent()

        if parentItem == self._root:
            return QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    # 获取某个节点的子节点个数
    def rowCount(self, parent=QModelIndex()):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parentItem = self._root
        else:
            parentItem = parent.internalPointer()

        return parentItem.childCount()

    # 获取列数,这里只有一列
    def columnCount(self, parent=QModelIndex()):
        return 1

    # 获取某个节点的数据
    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        item = index.internalPointer()
        if role == Qt.DisplayRole:
            return item.data(index.column())

    def flags(self, index):
        if not index.isValid():
            return Qt.NoItemFlags

        return super().flags(index)

这段代码是一个用于创建树形数据模型的类 TreeModel,它是基于 QAbstractItemModel 的子类,用于在 PySide6 中实现树形视图的数据模型。

代码中定义了一个自定义的树形节点类 TreeNode,每个节点包含一个数据项和对父节点和子节点的引用。TreeNode 类提供了一些方法来操作节点的子节点,如添加子节点、删除子节点等。还提供了一些方法用于获取节点的数据、父节点、子节点数量等。

TreeModel 类的构造函数接受一个根节点作为参数,并将其存储在成员变量 _root 中。如果没有传入根节点,则创建一个空的根节点。该类还实现了一些必要的方法,使得树形数据模型能够与视图进行交互。

以下是对 TreeModel 类中重要方法的解读:

  • index(self, row, column, parent=QModelIndex()): 返回指定行和列的节点在模型中的索引。如果索引无效,则返回 QModelIndex()。如果父索引无效,则返回根节点;否则,返回父节点的子节点。
  • parent(self, index): 返回给定索引的父节点的索引。如果索引无效,则返回 QModelIndex()。如果父节点是根节点,则返回无效索引;否则,返回父节点的索引。
  • rowCount(self, parent=QModelIndex()): 返回给定父索引下的子节点数量。如果父索引的列数大于 0,则返回 0。如果父索引无效,则返回根节点的子节点数量;否则,返回父节点的子节点数量。
  • columnCount(self, parent=QModelIndex()): 返回模型中的列数,这里固定为 1。
  • data(self, index, role=Qt.DisplayRole): 返回给定索引处节点的数据。如果索引无效,则返回 None。如果角色是 Qt.DisplayRole,则返回节点的数据。
  • flags(self, index): 返回给定索引的标志。如果索引无效,则返回 Qt.NoItemFlags,表示没有任何标志。
# main.py
from PySide6.QtCore import QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from TreeModel import TreeModel,TreeNode
import sys


if __name__ == '__main__':

    # 创建树形结构
    rootItem = TreeNode("root")
    childItem1 = TreeNode("child1", rootItem)
    childItem2 = TreeNode("child2", rootItem)
    childItem3 = TreeNode("child3", rootItem)
    subChildItem1 = TreeNode("sub_child1", childItem1)
    subChildItem2 = TreeNode("sub_child2", childItem1)

    rootItem.appendChild(childItem1)
    rootItem.appendChild(childItem2)
    rootItem.appendChild(childItem3)
    childItem1.appendChild(subChildItem1)
    childItem1.appendChild(subChildItem2)

    # 创建树形数据模型
    treeModel = TreeModel(rootItem)

    # 启动
    app = QGuiApplication([])
    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty('treeModel', treeModel)
    engine.load(QUrl.fromLocalFile('main.qml'))
    engine.quit.connect(app.quit)
    sys.exit(app.exec())

这是一个使用 PySide6 构建的应用程序的入口点 main.py。它创建了一个树形结构,并将其与自定义的树形数据模型 TreeModel 结合使用,最后通过 Qt Quick(QML)界面进行展示。

  • 首先,代码导入了必要的 PySide6 模块和类,以及自定义的 TreeModelTreeNode 类。
  • if __name__ == '__main__': 块中,首先创建了树形结构。根节点是一个名为 “root” 的节点,然后创建了几个子节点,并将它们作为根节点的子节点。子节点也可以有它们自己的子节点。
  • 接下来,通过将根节点传递给 TreeModel 类的构造函数,创建了一个树形数据模型 treeModel
  • 之后,创建了 QGuiApplication 实例 app,用于启动应用程序。
  • 创建了 QQmlApplicationEngine 实例 engine,用于加载和运行 QML 界面。
  • 通过 engine.rootContext().setContextProperty('treeModel', treeModel)treeModel 设置为 QML 上下文属性,以便在 QML 中可以访问该树形数据模型。
  • 使用 engine.load(QUrl.fromLocalFile('main.qml')) 加载 QML 文件,该文件定义了应用程序的界面。
  • 最后,通过 engine.quit.connect(app.quit)enginequit 信号连接到 appquit 槽,以便在关闭应用程序时能够正确退出。
  • 最后使用 sys.exit(app.exec()) 启动应用程序的事件循环。

运行命令:

python main.py

未实现

  • 双击编辑节点

参考

[1].TreeView QML Type.[EB/OL].[2023-6-15].https://doc.qt.io/qt-6/qml-qtquick-treeview.html

[2].QAbstractItemModel.[EB/OL].[2023-6-15].https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.PySide6.QtCore.QAbstractItemModel

你可能感兴趣的:(QML教程,python,开发语言)