Однажды один мой знакомый спросил: «Как можно отобразить древовидную структуру в qml?» Получившейся вариант я уже пытался опубликовать на хабре, но в тот раз проспал инвайт… Под катом вторая попытка опубликовать таки статью.
Цель: разработать тестовое приложение отображающее древовидную структуру с помою Qt Quick.
Результат можно посмотреть на GitHub: github.com/1KoT1/QMLPresentTree
Структура данных приложения
В нашем приложении элементы дерева описаны классом TreeItem.
Свойство content хранит некоторое содержимое элемента. Для тестового приложения вполне подойдёт строка.
Каждый экземпляр элемента имеет список содержащихся в нём подэлементов – childItems. Таким образом строится дерево.
Свойство isOpen хранит состояние списка внутренних элементов: развёрнут или скрыт.
Свойство hasChild показывает, имеются ли дочерние элементы. Можно было бы обойтись использованием !item.childItems().isEmpty(), но для простоты дальнейшего использования ввёл поле hasChild.
Код реализующий данный класс см. в treeitem.h и treeitem.cpp.
В model.h и model.cpp описана модель данных приложения. Модель содержит единственное свойство – дерево элементов.
В main.cpp создаю модель, представление и экспортирую модель в qml. Контроллер отсутствует, т. к. нет функционала, кроме отображения.
Отображение дерева в qml
Теперь переходим к самому интересному. Опишем отображение нашей древовидной структуры с помощью qml.
Главный файл qml содержит следующий код:
import QtQuick 2.0
Rectangle {
width: 360
height: 360
ListView{
anchors.fill: parent
model: programmModel.tree
delegate: ItemView{}
}
}
Основная идея в следующем: Отображаем плоский список элементов первого уровня. Делегат, описывающий элемент содержит плоский список для отображения подэлементов. И т. д. по структуре дерева.
Окно целиком заполнено элементом ListView. ListView отображает плоский список и позволяет прокручивать его. В качестве модели указываю наше дерево. По сути это плоский список элементов, каждый из которых в свою очередь содержит список подэлементов. Отображение каждого элемента описано в ItemView.qml:
import QtQuick 2.0
Row{
id: itemView
Text{
width: 10
height: 10
text: modelData.hasChild? modelData.isOpen ? "-" : "+" : ""
MouseArea{
anchors.fill: parent
onClicked: modelData.isOpen = !modelData.isOpen;
}
}
Column{
Text{ text: modelData.content }
Loader{
source: modelData.isOpen ? "TreeItemsList.qml" : "Empty.qml"
}
}
}
Элемент состоит из вывода содержимого:
Text{ text: modelData.content }
Элемента управления, показывающего наличие подэлементов и позволяющего раскрыть список:
Text{
width: 10
height: 10
text: modelData.hasChild? modelData.isOpen ? "-" : "+" : ""
MouseArea{
anchors.fill: parent
onClicked: modelData.isOpen = !modelData.isOpen;
}
}
Списка поделементов:
Loader{
source: modelData.isOpen ? "TreeItemsList.qml" : "Empty.qml"
}
Список подэлементов надо отображать только, когда он открыт. Для этого использую элемент Loader. В закрытом состоянии в него подгружается Empty.qml – прямоуголник размером 0 на 0. Отображение отрытого списка подэлементов описано в TreeItemsList.qml.
Рассмотрим его:
import QtQuick 2.0
Column{
Repeater{
model: modelData.childItems
delegate: ItemView{}
}
}
Построение вертикального списка проиходит с помощью комбинации элементов Column и Repeater. В отличии от ListView Column не позволяет прокручивать содержимое и занимает всё небходимое пространство для отображение внутренних элементов. Прокрутка дерева целиком обеспечивается с помощью ListView в главном файле.
Для отображения подэлементов используется тотже делегот, что и в списке верхнего уровня. Таким образом каждый элемент может отобразить свои поэлементы. Вложенность ограничена только системными ресурсами.
Благодаря использованному подходу выполняется ещё одно важное требование: визуальное дерево строится только для открытых элементов, т. е. не тратятся лишние системные ресурсы.