Модели и представления в Qt Quick
Проще говоря, приложениям необходимо формировать данные и отображать их. Qt Quick имеет понятия моделей, представлений и делегатов для отображения данных. Они модулизируют визуализацию данных, предоставляя разработчику или дизайнеру контроль над различными аспектами данных. Разработчик может заменить представление списка представлением сетки с небольшими изменениями в данных. Аналогично, инкапсулирование экземпляра данных в делегате позволяет разработчику диктовать способ представления или обработки данных.
- Модель - содержит данные и их структуру. Существуют несколько типов QML для создания моделей.
- Представление - контейнер, отображающий данные. Представление может отображать данные в виде списка или сетки.
- Делегат - диктует, как данные должны отображаться в представлении. Делегат берет каждый элемент данных из модели и инкапсулирует его. Данные доступны через делегат. Делегат также может записывать данные обратно в редактируемые модели (например, в обработчик onAccepted в TextField).
Для визуализации данных привяжите свойство 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.view GridView доступен делегатам. Соответствующая модель и ее свойства, следовательно, доступны через 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, если элемент удален из модели. Если вы привязываетесь к роли индекса, убедитесь, что логика учитывает возможность того, что индекс равен -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 News показывает, как можно использовать 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
}
} Целые числа в качестве моделей
Целое число может использоваться в качестве модели, содержащей определенное количество типов. В этом случае модель не имеет ролей данных.
В следующем примере создается ListView с пятью элементами:
Item {
width: 200; height: 250
Component {
id: itemDelegate
Text { text: "I am item number: " + index }
}
ListView {
anchors.fill: parent
model: 5
delegate: itemDelegate
}
} Примечание: Предел количества элементов в модели целых чисел составляет 100 000 000.
Экземпляры объектов в качестве моделей
Объект-экземпляр может использоваться для задания модели с одним типом объекта. Свойства объекта предоставляются в качестве ролей.
В примере ниже создается список с одним элементом, отображающим цвет текста myText. Обратите внимание на использование полностью квалифицированного свойства model.color для предотвращения конфликта со свойством color типа Текст в делегате.
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 } }
}
}
} Количество элементов, созданных Repeater, хранится в его свойстве count. Невозможно установить это свойство для определения количества создаваемых элементов. Вместо этого, как в приведенном выше примере, мы используем целое число как модель.
Для более подробной информации см. документ QML Модели данных.
Если модель является списком строк, делегат также получает доступ к только для чтения modelData свойству, которое содержит строку. Например:
Column {
Repeater {
model: ["apples", "oranges", "pears"]
Text { text: "Data: " + modelData }
}
} |
Также возможно использовать делегат в качестве шаблона для элементов, созданных Repeater. Это задается с помощью свойства delegate.
Изменение данных модели
Для изменения данных модели можно присвоить обновленные значения свойствам model. QML ListModel по умолчанию является редактируемым, тогда как модели C++ должны реализовывать setData(), чтобы стать редактируемыми. Модели целых чисел и массивов JavaScript являются только для чтения.
Предположим, модель C++ на основе QAbstractItemModel, которая реализует метод setData, зарегистрирована как тип QML под названием EditableModel. Затем данные можно записать в модель так:
ListView {
anchors.fill: parent
model: EditableModel {}
delegate: TextEdit {
width: ListView.view.width
height: 30
text: model.edit
Keys.onReturnPressed: model.edit = text
}
} Примечание: Роль edit равна Qt::EditRole. См. roleNames() для встроенных имен ролей. Однако, реальные модели обычно регистрируют пользовательские роли.
Для получения дополнительной информации посетите статью Использование моделей C++ с Qt Quick Views.
Использование переходов
Переходы могут использоваться для анимации элементов, которые добавляются, перемещаются или удаляются из позиционера.
Переходы для добавления элементов применяются к элементам, которые создаются как часть позиционера, а также к тем, которые переродились, чтобы стать дочерними элементами позиционера.
Переходы для удаления элементов применяются к элементам внутри позиционера, которые удаляются, а также к тем, которые удаляются из позиционера и получают новых родителей в документе.
Примечание: Изменение непрозрачности элементов на ноль не приведет к их исчезновению из позиционера. Их можно удалить и добавить повторно, изменив свойство visible.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/qtquick-modelviewsdata-modelview.html