Модели и представления в Qt Quick
Проще говоря, приложениям необходимо формировать данные и отображать их. Qt Quick использует понятия моделей, представлений и делегатов для отображения данных. Они модулизируют визуализацию данных, предоставляя разработчику или дизайнеру контроль над различными аспектами данных. Разработчик может заменить представление списка на представление сетки с небольшими изменениями в данных. Аналогично, инкапсулирование экземпляра данных в делегат позволяет разработчику определять способ представления или обработки данных.
- Модель - содержит данные и их структуру. Существуют различные типы QML для создания моделей.
- Представление - контейнер, отображающий данные. Представление может отображать данные в виде списка или сетки.
- Делегат - определяет, как данные должны отображаться в представлении. Делегат берет каждый элемент данных из модели и инкапсулирует его. Данные доступны через делегат.
Для визуализации данных свяжите свойство model представления с моделью, а свойство delegate - с компонентом или другим совместимым типом.
Отображение данных с помощью представлений
Представления являются контейнерами для наборов элементов. Они обладают богатым функционалом и могут быть настраиваемыми для удовлетворения требований к стилю или поведению.
В базовом наборе графических типов Qt Quick предоставляется набор стандартных представлений:
- ListView - упорядочивает элементы в горизонтальный или вертикальный список
- GridView - упорядочивает элементы в сетку в доступном пространстве
- PathView - упорядочивает элементы по пути
Эти типы имеют свойства и поведение, уникальные для каждого типа. Подробнее см. в соответствующей документации.
Декорирование представлений
Представления позволяют настраивать визуальное оформление через свойства декораций, такие как header, footer, и section свойства. Связывая объект, обычно другой визуальный объект, с этими свойствами, представления могут быть декорированы. Подвал может включать тип Rectangle, демонстрирующий границы, или заголовок, отображающий логотип в верхней части списка.
Предположим, что определенный клуб хочет декорировать список своих членов в своих фирменных цветах. Список членов находится в model, и delegate отобразит содержимое модели.
ListModel {
id: nameModel
ListElement { name: "Alice" }
ListElement { name: "Bob" }
ListElement { name: "Jane" }
ListElement { name: "Harry" }
ListElement { name: "Wendy" }
}
Component {
id: nameDelegate
Text {
text: name;
font.pixelSize: 24
}
} Клуб может декорировать список членов, связав визуальные объекты со свойствами header и footer. Визуальный объект может быть определен непосредственно, в другом файле или в типе Component.
ListView {
anchors.fill: parent
clip: true
model: nameModel
delegate: nameDelegate
header: bannercomponent
footer: Rectangle {
width: parent.width; height: 30;
gradient: clubcolors
}
highlight: Rectangle {
width: parent.width
color: "lightgray"
}
}
Component { //instantiated when header is processed
id: bannercomponent
Rectangle {
id: banner
width: parent.width; height: 50
gradient: clubcolors
border {color: "#9EDDF2"; width: 2}
Text {
anchors.centerIn: parent
text: "Club Members"
font.pixelSize: 32
}
}
}
Gradient {
id: clubcolors
GradientStop { position: 0.0; color: "#8EE2FE"}
GradientStop { position: 0.66; color: "#7ED2EE"}
} Обработка мыши и сенсорного ввода
Представления обрабатывают перетаскивание и прокрутку своего содержимого, но не обрабатывают взаимодействие с сенсорным вводом с отдельными делегатами. Чтобы делегаты реагировали на сенсорный ввод, например, для установки currentIndex, делегат должен содержать MouseArea с соответствующей логикой обработки сенсорного ввода.
Обратите внимание, что если highlightRangeMode установлено в StrictlyEnforceRange, значение currentIndex будет изменяться при перетаскивании/прокрутке представления, поскольку представление всегда будет гарантировать, что currentIndex находится в диапазоне выделения.
Разделы ListView
ListView содержимое может быть сгруппировано в разделы, где связанные элементы списка маркируются в соответствии с их разделами. Кроме того, разделы могут быть декорированы делегатами.
Список может содержать список, указывающий имена людей и команду, к которой они принадлежат.
ListModel {
id: nameModel
ListElement { name: "Alice"; team: "Crypto" }
ListElement { name: "Bob"; team: "Crypto" }
ListElement { name: "Jane"; team: "QA" }
ListElement { name: "Victor"; team: "QA" }
ListElement { name: "Wendy"; team: "Graphics" }
}
Component {
id: nameDelegate
Text {
text: name;
font.pixelSize: 24
anchors.left: parent.left
anchors.leftMargin: 2
}
} Тип ListView имеет section присоединённое свойство, которое может объединять смежные и связанные типы в раздел. section.property определяет, какое свойство типа списка использовать в качестве разделов. section.criteria определяет способ отображения названий разделов, а section.delegate аналогично свойству делегата представлений.
ListView {
anchors.fill: parent
model: nameModel
delegate: nameDelegate
focus: true
highlight: Rectangle {
color: "lightblue"
width: parent.width
}
section {
property: "team"
criteria: ViewSection.FullString
delegate: Rectangle {
color: "#b0dfb0"
width: parent.width
height: childrenRect.height + 4
Text { anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 16
font.bold: true
text: section
}
}
}
} Делегаты представлений
Для визуального представления элемента в списке представлениям необходим делегат. Представление визуализирует каждый элемент списка в соответствии с шаблоном, определенным делегатом. Элементы модели доступны через свойство index, а также через свойства элемента.
Component {
id: petdelegate
Text {
id: label
font.pixelSize: 24
text: if (index == 0)
label.text = type + " (default)"
else
text: type
}
} Доступ к представлениям и моделям из делегатов
Список представлений, к которому привязан делегат, доступен из делегата через свойство ListView.view. Аналогично, GridView GridView.view доступен делегатам. Соответствующая модель и её свойства, следовательно, доступны через ListView.view.model. Кроме того, доступны все определённые сигналы или методы в модели.
Этот механизм полезен, когда вы хотите использовать один и тот же делегат для нескольких представлений, например, но хотите, чтобы декорации или другие функции отличались для каждого представления, и вы хотите, чтобы эти разные настройки были свойствами каждого из представлений. Аналогично, может быть интересно получить доступ к свойствам модели или отобразить их.
В следующем примере делегат показывает свойство language модели, а цвет одного из полей зависит от свойства fruit_color представления.
Rectangle {
width: 200; height: 200
ListModel {
id: fruitModel
property string language: "en"
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
Component {
id: fruitDelegate
Row {
id: fruit
Text { text: " Fruit: " + name; color: fruit.ListView.view.fruit_color }
Text { text: " Cost: $" + cost }
Text { text: " Language: " + fruit.ListView.view.model.language }
}
}
ListView {
property color fruit_color: "green"
model: fruitModel
delegate: fruitDelegate
anchors.fill: parent
}
} Модели
Данные предоставляются делегату с помощью именованных ролей данных, к которым делегат может привязаться. Вот ListModel с двумя ролями, type и age, и ListView с делегатом, привязанным к этим ролям для отображения их значений:
import QtQuick 2.0
Item {
width: 200; height: 250
ListModel {
id: myModel
ListElement { type: "Dog"; age: 8 }
ListElement { type: "Cat"; age: 5 }
}
Component {
id: myDelegate
Text { text: type + ", " + age }
}
ListView {
anchors.fill: parent
model: myModel
delegate: myDelegate
}
} Если между свойствами модели и свойствами делегата возникает конфликт имён, к ролям можно получить доступ с использованием квалифицированного имени model. Например, если у типа Text были свойства type или age, текст в приведённом выше примере отображал бы эти значения свойств вместо значений type и age из элемента модели. В этом случае к свойствам можно было бы обратиться как model.type и model.age, чтобы гарантировать, что делегат отображает значения свойств из элемента модели.
Также доступна специальная роль index, содержащая индекс элемента в модели. Обратите внимание, что этот индекс устанавливается в -1, если элемент удалён из модели. Если вы привязываетесь к роли index, убедитесь, что логика учитывает возможность значения index быть -1, т.е. что элемент больше недействителен. (Обычно элемент будет вскоре уничтожен, но есть возможность отложить уничтожение делегата в некоторых представлениях с помощью delayRemove присоединённого свойства.)
Модели, не имеющие именованных ролей (например, ListModel, показанный ниже), будут предоставлять данные через роль modelData. Роль modelData также предоставляется для моделей, имеющих только одну роль. В этом случае роль modelData содержит те же данные, что и именованная роль.
QML предоставляет несколько типов моделей данных в наборе встроенных типов QML. Кроме того, модели могут быть созданы с помощью Qt C++ и затем предоставлены QQmlEngine для использования компонентами QML. Сведения о создании этих моделей см. в статьях Использование C++ моделей с Qt Quick представлениями и создание типов QML.
Расположение элементов из модели можно достичь с помощью Repeater.
Модель списка
ListModel представляет собой простую иерархию типов, указанную в QML. Доступные роли задаются свойствами ListElement.
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
} Вышеупомянутая модель имеет две роли, name и cost. К ним можно привязаться, например, делегатом ListView:
ListView {
anchors.fill: parent
model: fruitModel
delegate: Row {
Text { text: "Fruit: " + name }
Text { text: "Cost: $" + cost }
}
} ListModel предоставляет методы для непосредственного управления ListModel через JavaScript. В этом случае первый вставленный элемент определяет доступные роли для любых представлений, использующих модель. Например, если создаётся пустой ListModel и заполняется через JavaScript, роли, предоставляемые первым элементом, являются единственными ролями, которые будут показаны в представлении:
ListModel { id: fruitModel }
...
MouseArea {
anchors.fill: parent
onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"})
} При нажатии на MouseArea, у fruitModel будут две роли, cost и name. Даже если впоследствии добавляются другие роли, только первые две будут обрабатываться представлениями, использующими модель. Чтобы сбросить доступные роли в модели, вызовите ListModel::clear().
Модель XML
XmlListModel позволяет создавать модель из источника данных XML. Роли задаются с помощью типа XmlRole. Необходимо импортировать тип.
import QtQuick.XmlListModel 2.0
Следующая модель имеет три роли, title, link и description:
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "link"; query: "link/string()" }
XmlRole { name: "description"; query: "description/string()" }
} Свойство query указывает, что XmlListModel генерирует элемент модели для каждого <item> в документе XML.
Демонстрация RSS новостей показывает, как можно использовать XmlListModel для отображения ленты RSS.
Модель объекта
ObjectModel содержит визуальные элементы, которые будут использоваться в представлении. Когда ObjectModel используется в представлении, представлению не требуется делегат, так как ObjectModel уже содержит визуальный делегат (элементы).
В примере ниже три цветных прямоугольника размещаются в ListView.
import QtQuick 2.0
import QtQml.Models 2.1
Rectangle {
ObjectModel {
id: itemModel
Rectangle { height: 30; width: 80; color: "red" }
Rectangle { height: 30; width: 80; color: "green" }
Rectangle { height: 30; width: 80; color: "blue" }
}
ListView {
anchors.fill: parent
model: itemModel
}
} Примечание: VisualItemModel также может быть использован, но он предоставляется только по соображениям совместимости. VisualItemModel позволяет предоставить QML-элемент в качестве модели. Эта модель содержит данные и делегат; дочерние элементы VisualItemModel обеспечивают содержимое делегата. Модель не предоставляет никаких ролей.
Целые числа как модели
Целое число может использоваться как модель, содержащая определённое количество типов. В этом случае у модели нет ролей данных.
В следующем примере создаётся ListView с пятью элементами:
Item {
width: 200; height: 250
Component {
id: itemDelegate
Text { text: "I am item number: " + index }
}
ListView {
anchors.fill: parent
model: 5
delegate: itemDelegate
}
} Примеры объектов в качестве моделей
Объект может использоваться для указания модели с единственным типом объекта. Свойства объекта предоставляются в качестве ролей.
В примере ниже создаётся список с одним элементом, отображающим цвет текста myText. Обратите внимание на использование полностью квалифицированного свойства model.color для предотвращения конфликта со свойством color типа Text в делегате.
Rectangle {
width: 200; height: 250
Text {
id: myText
text: "Hello"
color: "#dd44ee"
}
Component {
id: myDelegate
Text { text: model.color }
}
ListView {
anchors.fill: parent
anchors.topMargin: 30
model: myText
delegate: myDelegate
}
} Модели данных C++
Модели могут быть определены в C++ и затем предоставлены для QML. Этот механизм полезен для экспонирования существующих моделей данных C++ или иных сложных наборов данных для QML.
Для получения дополнительной информации посетите статью Использование моделей C++ с Qt Quick Views.
Повторители
Повторители создают элементы из шаблона для использования с позиционерами, используя данные из модели. Объединение повторителей и позиционеров — простой способ выкладывать множество элементов. Элемент Repeater размещается внутри позиционера и генерирует элементы, которые позиционер размещает.
Каждый Repeater создаёт определённое количество элементов, объединяя каждый элемент данных из модели, указанный с помощью свойства model, со шаблонным элементом, определённым как дочерний элемент внутри Repeater. Общее количество элементов определяется количеством данных в модели.
Следующий пример демонстрирует повторитель, используемый с элементом Grid для упорядочивания набора элементов Rectangle. Элемент Repeater создаёт серию из 24 прямоугольников для элемента Grid, чтобы расположить их в 5 на 5 сетке.
import QtQuick 2.0
Rectangle {
width: 400; height: 400; color: "black"
Grid {
x: 5; y: 5
rows: 5; columns: 5; spacing: 10
Repeater { model: 24
Rectangle { width: 70; height: 70
color: "lightgreen"
Text { text: index
font.pointSize: 30
anchors.centerIn: parent } }
}
}
} Количество элементов, созданных повторителем, хранится в свойстве count. Невозможно установить это свойство для определения количества создаваемых элементов. Вместо этого, как в примере выше, мы используем целое число в качестве модели.
Для получения более подробной информации, см. документ QML Модели данных.
Если модель представляет собой список строк, делегат также получает доступ к свойству только для чтения modelData , которое содержит строку. Например:
Column {
Repeater {
model: ["apples", "oranges", "pears"]
Text { text: "Data: " + modelData }
}
} |
Также возможно использовать делегат в качестве шаблона для элементов, созданных повторителем. Это указывается с помощью свойства delegate.
Использование переходов
Переходы могут использоваться для анимации элементов, добавляемых, перемещаемых или удаляемых из позиционера.
Переходы для добавления элементов применяются к элементам, создаваемым в рамках позиционера, а также к тем, которые переназначаются в качестве дочерних элементов позиционера.
Переходы для удаления элементов применяются к элементам внутри позиционера, которые удаляются, а также к тем, которые удаляются из позиционера и получают новых родителей в документе.
Примечание: Изменение непрозрачности элементов на ноль не приведёт к их исчезновению из позиционера. Их можно удалить и добавить повторно, изменив свойство visible.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/qtquick-modelviewsdata-modelview.html