Модель-Представление В Qml. Часть Третья: Модели В Qml И Javascript

Наша модель отвечает за доступ к данным.

Модель может быть реализована как на самом QML, так и на C++.

Выбор здесь больше всего зависит от того, где находится источник данных.

Если в качестве источника данных используется код C++, то модель удобнее создавать там.

Если данные поступают напрямую в QML (например, полученные из сети с помощью XMLHttpRequest), то модель лучше реализовать в QML. В противном случае вам придется передавать данные в C++, а затем возвращать их для отображения, что только усложнит код. В зависимости от того, как реализованы модели, я разделю их на три категории:

  • модели на C++;
  • QML-модели;
  • Модели JavaScript.
Я вынес JavaScript-модели в отдельную категорию, потому что… у них есть определенные особенности, о них я расскажу чуть позже.

Начнем рассмотрение с моделей, реализованных с помощью QML. Представление модели в QML:

  1. Модель-представление в QML. Часть нулевая, вводная.

  2. Модель-представление в QML. Часть первая: представления на основе готовых компонентов
  3. Модель-представление в QML. Часть вторая: Пользовательские представления
  4. Модель-представление в QML. Часть третья: Модели в QML и JavaScript
  5. Модель-представление в QML. Часть четвертая: Модели C++


1. Модель списка
Это достаточно простой и в то же время функциональный компонент. Элементы в ListModel могут определяться как статически (это показано в первом примере), так и добавлять/удалять динамически (соответственно, во втором примере).

Давайте рассмотрим оба метода более подробно.

1) Статический Когда мы определяем элементы модели статически, нам необходимо определить данные в дочерних элементах, которые имеют тип ListElement и определены внутри модели.

Данные определяются в свойствах объекта ListElement и доступны как роли в делегате.

Когда вы определяете данные статически в ListModel, типы данных, которые можно записать в ListElement, весьма ограничены.

По сути, все данные должны быть константами.

Те.

Вы можете использовать строки или числа, но не можете использовать объект или функцию.

В этом случае вы получите сообщение об ошибке «ListElement: невозможно использовать скрипт для значения свойства».

Но вы можете использовать список, элементами которого будут те же объекты ListElement.

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

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 } } } } } }

Мы используем роль texts внутри делегата в качестве модели, поэтому можем добиться нескольких уровней вложенности.

В результате мы получим что-то вроде этого:

Модель-представление в QML. Часть третья: Модели в QML и JavaScript

Еще один важный момент. В статически описанной модели во всех объектах 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" }) } } } }

В результате все новые элементы не будут иметь текста и будут иметь «старый» текст:

Модель-представление в QML. Часть третья: Модели в QML и JavaScript

Перепишем определение модели и на этапе создания добавим еще один элемент со свойством text, но без свойства color:

ListModel { id: dataModel Component.onCompleted: { append({ color: "orange" }) append({ text: "another old" }) } }

Давайте настроим определение делегата так, чтобы использовался цвет по умолчанию, если он не указан:

color: model.color || "lightgray"

В результате модель формируется с обеими ролями и при добавлении новых элементов всё отображается как задумано:

Модель-представление в QML. Часть третья: Модели в QML и JavaScript

Также мы можем комбинировать статическое и динамическое заполнение модели.

Но использование статического метода накладывает все свои ограничения и динамически мы можем добавлять только объекты с ролями одних и тех же типов.

Маленькая новость: в 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. На этом формирование модели завершено; остается только показать это.

В результате мы получим что-то вроде этого:

Модель-представление в QML. Часть третья: Модели в QML и JavaScript

Данная модель вынесена в отдельный модуль; для его использования необходимо дополнительно подключить этот модуль:

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 } } } }



Модель-представление в QML. Часть третья: Модели в QML и JavaScript

Удаляем каталоги из модели и оставляем только файлы *.

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 } }

Ну а теперь посмотрим, какая классная штука у нас получилась:

Модель-представление в QML. Часть третья: Модели в QML и JavaScript

FolderListModel — очень полезный компонент, который дает нам доступ к файловой системе и, несмотря на его экспериментальный характер, его можно использовать прямо сейчас.



5. Модели JavaScript
Помимо компонентов, специально предназначенных для создания моделей, в качестве модели могут выступать и многие другие объекты.

И этот вариант может быть даже проще, чем использование специальных компонентов для модели.

В основном такие модели пассивны и подходят, когда количество элементов фиксировано или меняется редко.

В качестве модели мы будем рассматривать следующие типы:

  • списки/массивы;
  • объекты JavaScript и компоненты QML;
  • целые числа.

1) Списки/массивы В качестве модели вы можете использовать обычные массивы JavaScript. Для каждого элемента массива будет создан делегат и данные самого элемента массива будут доступны в делегате через свойство modelData.

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 #проектирование и рефакторинг

Вместе с данным постом часто просматривают: