Модели и представления в 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 находится в пределах диапазона выделения, указанного вами.
Разделы списка 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, если элемент удален из модели. Если вы привязываетесь к роли индекса, убедитесь, что логика учитывает возможность индекса -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 (RSS News demo) демонстрирует, как 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 типа 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.
Повторители
Повторители создают элементы из шаблона для использования с позиционерами, используя данные из модели. Объединение повторителей и позиционеров является простым способом размещения множества элементов. Элемент Repeater размещается внутри позиционера и генерирует элементы, которые позиционер располагает.
Каждый Repeater создает определенное количество элементов, объединяя каждый элемент данных из модели, указанный с помощью свойства model, с шаблоном элемента, определенным в качестве дочернего элемента внутри Repeater. Общее число элементов определяется количеством данных в модели.
В приведенном ниже примере показан 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 Data Models.
Если модель представляет собой список строк, делегат также получает доступ к свойству только для чтения 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.
Использование переходов
Переходы могут быть использованы для анимации элементов, которые добавляются, перемещаются внутри или удаляются из позиционера.
Переходы для добавления элементов применяются к элементам, которые создаются как часть позиционера, а также к тем, которые переродились, став дочерними элементами позиционера.
Переходы для удаления элементов применяются к элементам внутри позиционера, которые удаляются, а также к тем, которые удаляются из позиционера и получают новых родителей в документе.
Примечание: Изменение непрозрачности элементов на ноль не приведет к их исчезновению из позиционера. Их можно удалить и повторно добавить, изменив свойство видимости.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.14/qtquick-modelviewsdata-modelview.html