Модели и представления в 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. Визуальный объект может быть определен inline, в другом файле или в типе 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. Кроме того, любые определенные сигналы или методы в модели также доступны.
Этот механизм полезен, когда вы хотите использовать один и тот же делегированный элемент для нескольких представлений, но хотите, чтобы украшения или другие функции отличались для каждого представления, и вы хотите, чтобы эти различные настройки были свойствами каждого из представлений. Аналогично, может быть интересно получить доступ к некоторым свойствам модели или отобразить их.
В следующем примере делегированный элемент отображает свойство язык модели, а цвет одного из полей зависит от свойства 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 с двумя ролями, тип и возраст, и 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
}
} Если между свойствами модели и свойствами делегированного элемента возникает конфликт имен, к ролям можно получить доступ с использованием квалифицированного имени модели вместо него. Например, если у типа Text были свойства тип или возраст, текст в приведенном выше примере отображал бы эти значения свойств вместо значений тип и возраст из элемента модели. В этом случае свойства можно было бы сослаться как model.type и model.age вместо этого, чтобы гарантировать, что делегированный элемент отображает значения свойств из элемента модели.
Для делегированного элемента также доступна специальная роль индекс, содержащая индекс элемента в модели. Обратите внимание, что этот индекс установлен в -1, если элемент удален из модели. Если вы привязываетесь к роли индекса, убедитесь, что логика учитывает возможность того, что индекс равен -1, т.е. что элемент больше не действителен. (Обычно элемент вскоре будет уничтожен, но возможно отложить уничтожение делегированного элемента в некоторых представлениях с помощью присоединенного свойства delayRemove. )
Модели, у которых нет именованных ролей (например, ListModel, показанный ниже), будут предоставлять данные через роль modelData. Роль modelData также предоставляется для моделей, имеющих только одну роль. В этом случае роль modelData содержит те же данные, что и именованная роль.
QML предоставляет несколько типов моделей данных среди встроенного набора типов QML. Кроме того, модели могут быть созданы с помощью Qt C++ и затем сделаны доступными для QQmlEngine для использования компонентами QML. Для получения информации о создании этих моделей посетите статьи Использование моделей C++ с представлениями Qt Quick и создание типов QML.
Расположение элементов из модели может быть достигнуто с помощью Repeater.
ListModel
ListModel представляет собой простую иерархию типов, указанную в QML. Доступные роли определяются свойствами ListElement.
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
} Приведенная выше модель имеет две роли, имя и стоимость. К ним может быть привязано представление 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 будет иметь две роли, стоимость и имя. Даже если последующие роли будут добавлены, только первые две будут обрабатываться представлениями, использующими модель. Чтобы сбросить доступные роли в модели, вызовите ListModel::clear().
XmlListModel
XmlListModel позволяет создать модель из XML источника данных. Роли определяются с помощью типа XmlRole. Тип необходимо импортировать.
import QtQuick.XmlListModel 2.0
Следующая модель имеет три роли, заголовок, ссылка и описание:
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()" }
} Демо RSS News демонстрирует, как XmlListModel может быть использован для отображения ленты RSS.
VisualItemModel
VisualItemModel позволяет использовать элементы QML в качестве модели.
Эта модель содержит как данные, так и делегированный элемент; дочерние элементы VisualItemModel предоставляют содержимое делегированного элемента. Модель не предоставляет никаких ролей.
VisualItemModel {
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
}
} Экземпляры объектов как модели
Экземпляр объекта может использоваться для указания модели с одним типом объекта. Свойства объекта предоставляются в качестве ролей.
Нижеприведенный пример создает список с одним элементом, отображающим цвет текста 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 представлениями.
Повторители
Повторители создают элементы из шаблона для использования с позиционерами, используя данные из модели. Объединение повторителей и позиционеров — это простой способ выложить множество элементов. Элемент Повторителя размещается внутри позиционера и генерирует элементы, которые окружающий позиционер организует.
Каждый Повторитель создаёт количество элементов путём объединения каждого элемента данных из модели, указанной с помощью свойства model, с элементом-шаблоном, определённым как дочерний элемент внутри Повторителя. Общее количество элементов определяется объёмом данных в модели.
Следующий пример демонстрирует использование повторителя с элементом Grid для упорядочения набора прямоугольников. Элемент Повторителя создаёт серию из 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 Модели данных.
Также можно использовать делегат в качестве шаблона для элементов, созданных повторителем. Это задаётся с помощью свойства delegate.
Использование переходов
Переходы могут быть использованы для анимации элементов, добавляемых, перемещаемых внутри или удаляемых из позиционера.
Переходы для добавления элементов применяются к элементам, созданным как часть позиционера, а также к тем, которые переопределяются для того, чтобы стать дочерними элементами позиционера.
Переходы для удаления элементов применяются к элементам внутри позиционера, которые удаляются, а также к тем, которые удаляются из позиционера и получают новых родителей в документе.
Примечание: Изменение непрозрачности элементов на ноль не приведёт к их исчезновению из позиционера. Их можно удалить и добавить снова, изменив свойство visible.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.6/qtquick-modelviewsdata-modelview.html