Наша модель отвечает за доступ к данным.
Модель может быть реализована как на самом QML, так и на C++.
Выбор здесь больше всего зависит от того, где находится источник данных.
Если в качестве источника данных используется код C++, то модель удобнее создавать там.
Если данные поступают напрямую в QML (например, полученные из сети с помощью XMLHttpRequest), то модель лучше реализовать в QML. В противном случае вам придется передавать данные в C++, а затем возвращать их для отображения, что только усложнит код. В зависимости от того, как реализованы модели, я разделю их на три категории:
- модели на C++;
- QML-модели;
- Модели JavaScript.
Начнем рассмотрение с моделей, реализованных с помощью QML. Представление модели в QML:
- Модель-представление в QML. Часть нулевая, вводная.
- Модель-представление в QML. Часть первая: представления на основе готовых компонентов
- Модель-представление в QML. Часть вторая: Пользовательские представления
- Модель-представление в QML. Часть третья: Модели в QML и JavaScript
- Модель-представление в QML. Часть четвертая: Модели C++
1. Модель списка
Это достаточно простой и в то же время функциональный компонент. Элементы в ListModel могут определяться как статически (это показано в первом примере), так и добавлять/удалять динамически (соответственно, во втором примере).Давайте рассмотрим оба метода более подробно.
1) Статический Когда мы определяем элементы модели статически, нам необходимо определить данные в дочерних элементах, которые имеют тип ListElement и определены внутри модели.
Данные определяются в свойствах объекта ListElement и доступны как роли в делегате.
Когда вы определяете данные статически в ListModel, типы данных, которые можно записать в ListElement, весьма ограничены.
По сути, все данные должны быть константами.
Те.
Вы можете использовать строки или числа, но не можете использовать объект или функцию.
В этом случае вы получите сообщение об ошибке «ListElement: невозможно использовать скрипт для значения свойства».
Но вы можете использовать список, элементами которого будут те же объекты ListElement.
Мы используем роль texts внутри делегата в качестве модели, поэтому можем добиться нескольких уровней вложенности.import QtQuick 2.0 Rectangle { width: 360 height: 240 ListModel { id: dataModel ListElement { color: "orange" texts: [ ListElement { text: "one" }, ListElement { text: "two" } ] } ListElement { color: "skyblue" texts: [ ListElement { text: "three" }, ListElement { text: "four" } ] } } ListView { id: view anchors.margins: 10 anchors.fill: parent spacing: 10 model: dataModel delegate: Rectangle { width: view.width height: 100 color: model.color Row { anchors.margins: 10 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 10 Repeater { model: texts delegate: Text { verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: model.text } } } } } }
В результате мы получим что-то вроде этого:
Еще один важный момент. В статически описанной модели во всех объектах ListElement каждая роль должна хранить только один тип данных.
Те.
Вы не можете записать число в один элемент и строку в другой.
Для примера рассмотрим слегка модифицированную модель из самого первого примера: ListModel {
id: dataModel
ListElement {
color: "orange"
text: 1
}
ListElement {
color: "skyblue"
text: "second"
}
}
Мы получим следующую ошибку: «Невозможно назначить существующей роли «текст» другого типа [String -> Number]» и вместо текста во втором делегате получим 0.
2) Динамический
Этот метод дает нам гораздо больше возможностей, чем статический.Не все из них описаны в документации и могут быть очевидны, поэтому давайте рассмотрим их подробнее.
Интерфейс управления элементами в ListModel аналогичен интерфейсу обычного списка.
?Элементы можно добавлять/удалять/перемещать, их значения можно получать и заменять или редактировать.
ListModel принимает значение элемента как объект JavaScript. Соответственно, свойства этого объекта станут ролями в делегате.
Если взять самый первый пример, то модель можно переписать так, чтобы она заполнялась динамически: ListModel {
id: dataModel
Component.onCompleted: {
append({ color: "orange", text: "first" })
append({ color: "skyblue", text: "second" })
}
}
Объект можно указать не только литералом, но и передачей переменной, содержащей этот объект: var value = {
color: "orange",
text: "first"
}
append(value)
Когда я писал о статическом заполнении, я говорил, что типы данных, которые можно разместить в модели, должны быть константами.
У меня хорошие новости :) Когда мы заполняем модель динамически, эти ограничения не применяются.
В качестве значений свойств мы можем использовать как массивы, так и объекты.
Даже функции, но с небольшими возможностями.
Давайте возьмем тот же пример и немного перепишем его: QtObject {
id: obj
function alive() {
console.log("It's alive!")
}
}
ListModel {
id: dataModel
Component.onCompleted: {
var value
value = {
data: {
color: "orange",
text: "first"
},
functions: obj
}
append(value)
value = {
data: {
color: "skyblue",
text: "second"
},
functions: obj
}
append(value)
}
}
Поскольку свойства цвета и текста мы поместили в объект данных, они будут в делегате как свойства этого объекта, т. е.
model.data.color. Функции немного сложнее.
Если мы просто сделаем свойство на объекте и присвоим ему функцию, то внутри делегата мы увидим, что эта функция превратилась в пустой объект. Но если вы используете тип QtObject, то внутри него все сохраняется и ничего не теряется.
Итак, в определение компонента мы можем добавить следующую строку: Component.onCompleted: model.functions.alive()
и эта функция будет вызвана после создания компонента.
Внесение функций в данные — это скорее хак, и я рекомендую не слишком увлекаться такими вещами, а вот помещение объектов в модель — очень нужная вещь.
Например, если данные поступают из сети напрямую в QML (с помощью XMLHttpRequest) в формате JSON (а при работе с веб-ресурсами такое обычно происходит), то, декодировав JSON, мы получим объект JavaScript, который можно просто добавить в ListModel. .
Я уже писал о том, что роли во всех статически определенных элементах ListModel должны быть одного типа.
По умолчанию это правило также применяется к элементам, динамически добавляемым в ListModel. И первый добавленный элемент определяет, какого типа будут роли.
Но в Qt 5 добавлена возможность делать типы ролей динамическими.
Для этого вам необходимо установить для свойства DynamicRoles ListModel значение true. ListModel {
id: dataModel
dynamicRoles: true
Component.onCompleted: {
append({ color: "orange", text: "first" })
append({ color: "skyblue", text: 2 })
}
}
Удобная вещь, но есть пара важных моментов, о которых стоит помнить.
Цена такого удобства — производительность — разработчики Qt утверждают, что она будет в 4-6 раз меньше.
Кроме того, типы динамических ролей не будут работать для модели со статически определенными элементами.
Еще один очень важный момент. Первый элемент, добавленный в модель, определяет не только типы ролей, но и то, какие роли будут в модели.
Если какие-то роли отсутствуют, то добавить их позже не получится.
Но есть одно исключение.
Если элементы добавляются во время создания модели (т. е.
в обработчике Component.onCompleted), то в конечном итоге модель будет иметь все роли, которые были во всех этих элементах.
Давайте возьмем второй пример и немного его переработаем, чтобы при создании модели добавлялся один элемент без свойства text, а затем при нажатии кнопки мы добавляли элементы с текстом «новый».
import QtQuick 2.0
Rectangle {
width: 360
height: 360
ListModel {
id: dataModel
dynamicRoles: true
Component.onCompleted: {
append({ color: "orange" })
}
}
Column {
anchors.margins: 10
anchors.fill: parent
spacing: 10
ListView {
id: view
width: parent.width
height: parent.height - button.height - parent.spacing
spacing: 10
model: dataModel
clip: true
delegate: Rectangle {
width: view.width
height: 40
color: model.color
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: model.text || "old"
}
}
}
Rectangle {
id: button
width: 100
height: 40
anchors.horizontalCenter: parent.horizontalCenter
border {
color: "black"
width: 1
}
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: "Add"
}
MouseArea {
anchors.fill: parent
onClicked: dataModel.append({ color: "skyblue", text: "new" })
}
}
}
}
В результате все новые элементы не будут иметь текста и будут иметь «старый» текст:
Перепишем определение модели и на этапе создания добавим еще один элемент со свойством text, но без свойства color: ListModel {
id: dataModel
Component.onCompleted: {
append({ color: "orange" })
append({ text: "another old" })
}
}
Давайте настроим определение делегата так, чтобы использовался цвет по умолчанию, если он не указан: color: model.color || "lightgray"
В результате модель формируется с обеими ролями и при добавлении новых элементов всё отображается как задумано:
Также мы можем комбинировать статическое и динамическое заполнение модели.
Но использование статического метода накладывает все свои ограничения и динамически мы можем добавлять только объекты с ролями одних и тех же типов.
Маленькая новость: в Qt 5.1 эта модель перенесена из QtQuick в отдельный модуль QtQml.Models. Чтобы использовать его, вам необходимо подключить этот модуль: import QtQml.Models 2.1
Но не нужно спешить все переписывать — для совместимости с существующим кодом модель будет доступна и в модуле QtQuick.
ListModel можно считать QML-версией моделей из Qt. Он имеет аналогичный функционал, позволяет манипулировать данными и является активной моделью.
Могу сказать, что в QML это самый функциональный и удобный компонент для создания моделей.
2. ВизуалИтемМодель (Объектная Модель).
В архитектуре Model-View фреймворка Qt различаются две основные сущности: модель и представление и одна вспомогательная — делегат. Поскольку представление здесь является контейнером для экземпляров делегата, делегат обычно определяется там.
Этот компонент позволяет переместить делегата из представления в саму модель.
Это достигается за счет помещения в модель не данных, а готовых визуальных элементов.
Соответственно, представление в этом случае не нуждается в делегате и используется лишь как контейнер, обеспечивающий позиционирование элементов и навигацию по ним.
Одна интересная особенность VisualItemModel заключается в том, что в нее можно помещать объекты разных типов.
Типичная модель делегата использует объекты одного типа для отображения всех данных.
Когда вам необходимо отобразить на одном виде элементы разных типов, такая модель является одним из вариантов решения этой проблемы.
В качестве примера поместим в модель объекты типов Rectangle и Text и отобразим их с помощью ListView: import QtQuick 2.0
Rectangle {
width: 360
height: 240
VisualItemModel {
id: itemModel
Rectangle {
width: view.width
height: 100
color: "orange"
}
Text {
width: view.width
height: 100
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
text: "second"
}
}
ListView {
id: view
anchors.margins: 10
anchors.fill: parent
spacing: 10
model: itemModel
}
}
В Qt 5.1 эта модель перенесена из QtQuick в отдельный модуль QtQml.Models и называется ObjectModel. Как и в случае со ListModel, для использования этой модели необходимо подключить соответствующий модуль.
Интерфейс остается прежним, просто замените VisualDataModel на ObjectModel. Модель по-прежнему будет доступна через VisualDataModel, чтобы не нарушать совместимость со старым кодом.
Но если вы разрабатываете под новую версию, лучше сразу использовать новое имя.
3. Модель ХмлСписок
При работе с веб-ресурсами часто используется формат XML. В частности, он используется в таких вещах, как RSS, XSPF, различных подкастах и т. д. Это означает, что перед нами стоит задача получить этот файл и разобрать его.XML также может содержать список элементов (например, список песен в случае XSPF), на основе которых нам нужно будет создать модель.
Перебирать дерево элементов и заполнять модель вручную — не самый удобный способ, поэтому необходимо иметь возможность автоматически выбирать элементы из XML-файла и представлять их в виде модели.
Эту задачу реализует XmlListModel. От нас требуется указать адрес XML-файла, указать критерии выбора элементов и определить, какие роли должны быть видны в делегате.
В качестве критерия выбора элементов пишем запрос в формате XPath. Для ролей делегатов мы также указываем XPath-запрос, на основании которого из элемента будут получены данные для роли.
В простых случаях, таких как анализ RSS, эти запросы также будут простыми и по существу опишут путь в XML-файле.
Я не буду здесь углубляться в дебри XPath, а если вы не очень понимаете, что это за зверь, рекомендую прочитать соответствующий глава в документации Qt. Здесь я буду использовать примеры, в которых не будет сложных выборов, поэтому надеюсь, что все будет достаточно понятно.
В качестве примера мы получим RSS-ленту Хабра и отобразим заголовки статей.
Rectangle {
width: 360
height: 360
color: "lightsteelblue"
XmlListModel {
id: dataModel
source: " http://habrahabr.ru/rss/hubs/ "
query: "/rss/channel/item"
XmlRole {
name: "title"
query: "title/string()"
}
}
ListView {
id: view
anchors.margins: 10
anchors.fill: parent
spacing: 10
model: dataModel
delegate: Rectangle {
width: view.width
height: 40
radius: 10
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.Wrap
renderType: Text.NativeRendering
text: model.title
}
}
}
}
Нужные нам элементы — это блоки, вложенные в , которые, в свою очередь, вложены в .
По этому пути мы создаем наше первое выражение XPath. У нас будет только одна роль, содержащая заголовок статьи.
Чтобы его получить, нужно взять элемент и преобразовать его в строку.
Из этого мы формируем второе выражение XPath. На этом формирование модели завершено; остается только показать это.
В результате мы получим что-то вроде этого:
Данная модель вынесена в отдельный модуль; для его использования необходимо дополнительно подключить этот модуль: import QtQuick.XmlListModel 2.0
4. Модель списка папок
Для многих приложений доступ к файловой системе был бы хорошей идеей.
В QML для этого есть экспериментальный компонент, представляющий каталог файловой системы в виде модели — FileSystemModel. Для его использования необходимо подключить одноименный модуль: import Qt.labs.folderlistmodel 1.0
Хотя это экспериментальный вариант, он является частью Qt Labs, но в будущем его можно переместить в Qt Quick или куда-нибудь еще.
Чтобы использовать модель, нам сначала нужно установить каталог, используя свойство папки.
Путь необходимо указывать в формате URL, т.е.
путь к каталогу файловой системы указывается с помощью «file:».
Вы можете указать путь к ресурсам, используя «qrc:».
Вы можете установить фильтры по именам файлов с помощью свойства nameFilters, которое принимает список масок для выбора нужных файлов.
Вы также можете настроить способ ввода модели каталога и сортировки файлов.
Например, получим список файлов в каталоге и выведем информацию об этих файлах в виде таблицы: import QtQuick 2.0
import QtQuick.Controls 1.0
import Qt.labs.folderlistmodel 1.0
Rectangle {
width: 600
height: 300
FolderListModel {
id: dataModel
showDirs: false
nameFilters: [
"*.
jpg", "*.
png"
]
folder: " file:///mnt/store/Pictures/Wallpapers "
}
TableView {
id: view
anchors.margins: 10
anchors.fill: parent
model: dataModel
clip: true
TableViewColumn {
width: 300
title: "Name"
role: "fileName"
}
TableViewColumn {
width: 100
title: "Size"
role: "fileSize"
}
TableViewColumn {
width: 100
title: "Modified"
role: "fileModified"
}
itemDelegate: Item {
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
renderType: Text.NativeRendering
text: styleData.value
}
}
}
}
Удаляем каталоги из модели и оставляем только файлы *.
jpg и *.
png. В этой модели делегат имеет информацию о файле, доступную в виде данных: путь, имя и т. д. Здесь мы используем имя, размер и время модификации.
Мы узнали, как получить доступ к файловой системе.
Но рассматривать названия картинок может быть не очень увлекательно, поэтому в качестве бонуса сделаем их отображение немного интереснее :) Мы уже рассматривали такую штуку, как CoverFlow. Пришло время использовать его здесь.
Итак, давайте возьмем пример CoverFlow и немного изменим его.
Мы возьмем модель из предыдущего примера.
Увеличим размер элемента: property int itemSize: 400
И давайте изменим делегата: delegate: Image {
property real rotationAngle: PathView.angle
property real rotationOrigin: PathView.origin
width: itemSize
height: width
z: PathView.z
fillMode: Image.PreserveAspectFit
source: model.filePath
transform: Rotation {
axis { x: 0; y: 1; z: 0 }
angle: rotationAngle
origin.x: rotationOrigin
}
}
Ну а теперь посмотрим, какая классная штука у нас получилась:
FolderListModel — очень полезный компонент, который дает нам доступ к файловой системе и, несмотря на его экспериментальный характер, его можно использовать прямо сейчас.
5. Модели JavaScript
Помимо компонентов, специально предназначенных для создания моделей, в качестве модели могут выступать и многие другие объекты.И этот вариант может быть даже проще, чем использование специальных компонентов для модели.
В основном такие модели пассивны и подходят, когда количество элементов фиксировано или меняется редко.
В качестве модели мы будем рассматривать следующие типы:
- списки/массивы;
- объекты JavaScript и компоненты QML;
- целые числа.
import QtQuick 2.0
Rectangle {
property var dataModel: [
{
color: "orange"
},
{
color: "skyblue",
text: "second"
}
]
width: 360
height: 240
ListView {
id: view
anchors.margins: 10
anchors.fill: parent
spacing: 10
model: dataModel
delegate: Rectangle {
width: view.width
height: 100
color: modelData.color
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: modelData.text || "empty text"
}
}
}
}
Если массив содержит объекты, то modelData также будет объектом и будет содержать все свойства исходного объекта.
Если элементы представляют собой простые значения, они будут использоваться как modelData. Например: property var dataModel: [
"orange",
"skyblue"
]
и в делегате мы получаем доступ к данным модели следующим образом: color: modelData
И так же, как и в ListModel, мы можем поместить функцию в данные модели.
Как и в случае с ListModel, если вы поместите его в обычный объект JavaScript, он будет отображаться в делегате как пустой объект. Поэтому здесь мы также используем трюк QtObject. property var dataModel: [
{
color: "orange",
functions: obj
},
{
color: "skyblue",
text: "second",
functions: obj
}
]
QtObject {
id: obj
function alive() {
console.log("It's alive!")
}
}
И в делегате вызываем функцию: Component.onCompleted: modelData.functions.alive()
Я уже говорил, что почти все модели JavaScript пассивны, и эта не исключение.
При изменении элементов и их добавлении/удалении представление не будет знать, что они изменились.
Это происходит потому, что свойства объектов JavaScript не имеют сигналов, которые вызываются при изменении свойства, в отличие от объектов Qt и, соответственно, объектов QML. Представление получит сигнал, если мы изменим само свойство, используемое в качестве модели, заменим модель.
Но есть одна хитрость: мы можем не только присвоить этому свойству новую модель, но и переназначить старую.
Например: dataModel.push({ color: "skyblue", text: "something new" })
dataModel = dataModel
Эта модель хорошо подходит для данных, которые поступают с веб-ресурсов и обновляются редко и/или полностью.
2) предметы Объекты JavaScript и объекты QML могут выступать в качестве модели.
Эта модель будет иметь один элемент, а свойства объекта будут ролями в делегате.
Давайте возьмем самый первый пример и изменим его, чтобы использовать объект JavaScript в качестве модели: property var dataModel: null
Component.onCompleted: {
dataModel = {
color: "orange",
text: "some text"
}
}
Свойства объекта в делегате доступны через modelData: color: modelData.color
Как и в случае с массивами JavaScript, изменение объекта после того, как он был установлен в качестве модели, не влияет на отображение, т.е.
это тоже пассивная модель.
Я также включил использование одного объекта QML в качестве модели JavaScript. Хотя эти объекты можно использовать как полноценную QML-модель, функционал практически такой же, как при использовании обычного объекта JavaScript, но с некоторыми особенностями.
Вот почему я рассматриваю их вместе.
Давайте изменим тот же пример, чтобы использовать его в качестве объектной модели QML: Item {
id: dataModel
property color color: "orange"
property string text: "some text"
}
Здесь выбран элемент, чтобы показать, что любой объект QML можно использовать в качестве модели.
На практике, если вам нужно только хранить данные, лучше всего подойдет QtObject. Это самый простой и, соответственно, самый легкий QML-объект. Товар в данном случае содержит слишком много ненужного.
Для такой модели данные в делегате доступны как через model, так и через modelData. Кроме того, эта модель является единственной активной моделью JavaScript. Поскольку свойства объекта QML имеют сигналы, которые активируются при изменении свойства, изменение свойства объекта приведет к изменению данных в делегате.
3) Целое число Самая простая модель :) В качестве модели можно использовать целое число.
Это число и есть количество элементов модели.
property int dataModel: 5
Или вы можете напрямую указать константу в качестве модели: model: 5
Свойство modelData будет доступно в делегате, который содержит индекс.
Индекс также будет доступен через model.index. Эта модель хорошо подходит, когда нужно создать определенное количество одинаковых элементов.
В заключение
Мы рассмотрели модели, реализованные с помощью QML и JavaScript. Вариантов много, но скажу, что чаще всего используются массивы ListModel и JavaScript. Рассмотренные модели реализуются достаточно просто, если не требовать каких-то особых ухищрений (вроде хранения функций в ListModel).В тех случаях, когда этот вариант подходит, мы можем реализовать все компоненты MVC на одном языке и тем самым снизить сложность программы.
Но, хочу обратить внимание на одну вещь.
Не следует перетаскивать все в QML; вам следует руководствоваться практическими соображениями.
Некоторые вещи может быть проще реализовать на C++.
Именно модели C++ мы рассмотрим в следующей части.
Теги: #Qt #qt5 #qml #qt fast #контроллер представления модели #MVC #программирование #Qt #проектирование и рефакторинг
-
Интернет И Ответственность
19 Oct, 24 -
Касперский Против Ddos
19 Oct, 24 -
Стандартная Модель Частиц Для Начинающих
19 Oct, 24 -
Пост-Опрос О Фрилансе
19 Oct, 24