Model-View в QML. Часть третья: Модели в QML и JavaScript
Модель у нас отвечает за доступ к данным. Модель может быть реализована как в самом QML, так и на C++. Выбор тут больше всего зависит от того, где находится источник данных. Если в качестве источника данных используется код на C++, то там удобнее сделать и модель. Если же данные поступают напрямую в QML (например получаются из сети при помощи XMLHttpRequest), то лучше и модель реализовать на QML. Иначе придется передавать данные в C++, чтобы затем обратно их получать для отображения, что только усложнит код.
По тому, как модели реализуются, я разделю их на три категории:
JavaScript-модели я вынес в отдельную категорию, т.к. у них есть определенные особенности, про них я расскажу чуть позже.
Начнем рассмотрение с моделей, реализованных средствами QML.
Это достаточно простой и, в то же время, функциональный компонент. Элементы в ListModel можно как определять статически (это продемонстрировано в первом примере), так и добавлять/удалять динамически (соответственно, во втором примере). Разберем оба способа подробнее.
Когда мы определяем элементы модели статически, нам нужно определить данные в дочерних элементах, которые имеют тип ListElement и определяются внутри модели. Данные определяются в свойствах объекта ListElement и доступны как роли в делегате.
При статическом определении данных в ListModel, типы данных, которые можно записать в ListElement весьма ограничены. По сути, все данные должны быть константами. Т.е. можно использовать строки или числа, а вот объект или функцию использовать не получится. В таком случае вы получите ошибку «ListElement: cannot use script for property value». Но можно использовать список, элементами которого будут все те же объекты ListElement.
Роль texts мы используем внутри делегата в качестве модели, таким образом можно достичь нескольких уровней вложенности.
В результате мы получим примерно такое:
Еще один важный момент. В статически описанной модели во всех объектах ListElement каждая роль должна хранить данные только одного типа. Т.е. нельзя в одном элементе записать в нее число, а в другом строку. Например, рассмотрим немного измененную модель из самого первого примера:
Мы получим такую ошибку: «Can't assign to existing role 'text' of different type [String -> Number]» и вместо текста во втором делегате получим 0.
Этот способ дает нам гораздо больше возможностей, чем статический. Не все они описаны в документации и могут быть очевидными, поэтому рассмотрим их поподробнее.
Интерфейс для манипуляции элементами в ListModel похож на интерфейс обычного списка. Элементы можно добавлять/удалять/перемещать, можно получать их значение и заменять или редактировать.
ListModel принимает значение элемента в виде JavaScript-объекта. Соответственно, свойства этого объекта станут ролями в делегате.
Если взять самый первый пример, то модель можно переписать так, чтобы она наполнялась динамически:
Объект можно задавать не только литералом, а передать переменную, которая этот объект содержит:
Когда я писал про статическое наполнение, я сказал, что типы данных, которые можно поместить в модель должны быть константами. У меня есть хорошая новость :) Когда мы наполняем модель динамически, эти ограничения не действуют. Мы можем в качестве значения свойства и массивы, и объекты. Даже функции, но с небольшими особенностями. Возьмем все этот же пример и немного его перепишем:
Поскольку мы поместили свойства color и text в объект data, то в делегате они будут как свойства этого объекта, т.е. model.data.color.
С функциями немного сложнее. Если мы просто сделаем свойство в объекте и присвоим ему функцию, то внутри делегата мы увидим, что эта функция превратилась в пустой объект. Но если использовать тип QtObject, то внутри него все сохраняется и ничего не пропадает. Так что в определении компонента мы можем добавить такую строчку:
и эта функция вызовется после создания компонента.
Помещение функций в данные больше походит на хак и я рекомендую не сильно увлекаться такими вещами, а вот помещение объектов в модель очень нужная вещь. Например, если приходят данные из сети прямо в QML (при помощи XMLHttpRequest) в формате JSON (а при работе с веб-ресурсами обычно так и происходит), то декодировав JSON, мы получим JavaScript-объект, который можно просто добавить в ListModel.
Я уже писал про то, что во всех статически определенных элементах ListModel роли должны быть одних и тех же типов. По умолчанию, для элементов, добавляемых в ListModel динамически это правило тоже действует. И первый добавленный элемент определяет, какого типа будут роли. Но в Qt 5 добавилась возможность сделать типы ролей динамическими. Для этого нужно установить у ListModel свойство dynamicRoles в true.
Удобная штука, но есть пару важных моментов, которые стоит помнить. Ценой за такое удобство является производительность — разработчики Qt утверждают, что она будет в 4-6 раз меньше. Кроме того, динамические типы ролей не будут работать у модели со статически определенными элементами.
Еще один очень важный момент. Первый добавляемый в модель элемент определяет не только типы ролей, но и какие роли вообще в модели будут. Если в нем какие-то роли отсутствуют, то их потом не получится добавить. Но есть одно исключение. Если элементы добавляются на этапе создания модели (т.е. в обработчике Component.onCompleted), то в итоге у модели будут все роли, которые были во всех этих элементах.
Возьмем второй пример и немного его переделаем так, чтобы при создании модели добавлялся один элемент без свойства text, а затем по нажатию на кнопку будем добавлять элементы с текстом «new».
В результате, у всех новых элементов текста не будет и будет в качестве текста «old»:
Перепишем определение модели и добавим на этапе создания еще один элемент со свойством text, но без свойства color:
Подправим определении делегата, чтобы использовался цвет по умолчанию, если он не указан:
В итоге модель сформирована с обеими ролями и при добавлении новых элементов все отображается так, как задумано:
Мы также можем комбинировать статическое и динамической наполнение модели. Но использование статического способа накладывает все его ограничения и динамически мы сможем добавлять только объекты с ролями тех же типов.
Небольшая новость: в Qt 5.1 эта модель вынесена из QtQuick в отдельный модуль QtQml.Models. Чтобы ее использовать, надо подключить этот модуль:
Но бросаться все переписывать не обязательно —для совместимости с существующем кодом модель будет доступна и в модуле QtQuick.
ListModel можно считать QML-версией моделей из Qt. Она имеет похожий функционал, позволяет манипулировать данными и является активной моделью. Могу сказать, что в QML это наиболее функциональный и удобный компонент для создания моделей.
Архитектура Model-View фреймворка Qt выделяет две основных сущности: модель и представление и одну вспомогательную — делегат. Поскольку представление здесь является контейнером для экземпляров делегата, то делегат обычно определяется там же.
Этот компонент позволяет перенести делегат из представления в саму модель. Реализуется это тем, что в модель помещаются не данные, а уже готовые визуальные элементы. Соответственно, представлению в таком случае делегат не нужен и оно используется только как контейнер, обеспечивая позиционирование элементов и навигацию по ним.
Одной интересной особенностью VisualItemModel является то, что в нее можно положить объекты разный типов. Обычная модель с делегатом использует для отображения всех данных объекты одного и того же типа. Когда требуется отображать в одном представлении элементы разных типов, такая модель является одним из вариантов решения данной проблемы.
В качестве примера, поместим в модель объекты типов Rectangle и Text и отобразим их при помощи ListView:
В Qt 5.1 эта модель вынесена из QtQuick в отдельный модуль QtQml.Models и называется ObjectModel. Точно также, как и с ListModel, для использования этой модели надо подключить соответствующий модуль. Интерфейс остался тот же, достаточно просто заменить VisualDataModel на ObjectModel.
Модель будет все также доступна и через VisualDataModel, чтобы не ломать совместимость со старым кодом. Но если разрабатывать под новую версию, лучше сразу использовать новое название.
При работе с веб-ресурсами нередко применяется формат XML. В частности, он используется в таких вещах, как RSS, XSPF, различных подкастах и т.п. А значит, у нас появляется задача получить этот файл и его распарсить. Еще XML может содержать список элементов (например список песен в случае XSPF), из которых нам нужно будет создать модель. Перебирать дерево элементов и наполнять модель вручную не самый удобный способ, так что нужна возможность задать выбрать элементы из XML-файла автоматически и представить их в виде модели. Эти задачи и реализует XmlListModel.
От нас требуется указать адрес XML-файла, указать критерий, по которому нужно отобрать элементы и определить, какие роли должны быть видны в делегате. В качестве критерия для отбора элементов мы пишем запрос в формате XPath. Для ролей делегата мы указываем тоже XPath-запрос, на основании которого из элемента будут получены данные для роли. Для простых случаев, вроде разбора RSS, эти запросы тоже будут простыми и по сути описывают путь в XML-файле. Я не буду здесь углубляться в дебри XPath и если вам пока не особо понятно, что это за зверь, я рекомендую почитать соответствующий раздел в документации по Qt. Здесь же я буду использовать примеры, которые не делают никакой хитрой выборки, так что я надеюсь, что все будет достаточно понятно.
В качестве примера, мы получим RSS-фид Хабра и отобразим заголовки статей.
Нужные нам элементы — это блоки , который вложены в , а тот в свою очередь в . Из этого пути мы конструируем наше первое выражение XPath. У нас будет всего одна роль, содержащая заголовок статьи. Чтобы его получить, нужно у элемента взять и привести его в строку. Из этого мы и формируем второе выражение XPath. На этом формирование модели закончено, осталось только ее отобразить. В итоге мы получим примерно такой результат:
Эта модель вынесена в отдельный модуль, для ее использования, надо дополнительно подключать этот модуль:
Для многих приложений совсем не лишним будет доступ к файловой системе. В QML есть для этого экспериментальный компонент, представляющий каталог файловой системы в виде модели — FileSystemModel. Чтобы его использовать, надо подключит одноименный модуль:
Пока он экспериментальный, он входит в Qt Labs, но в будущем его могут переместить в Qt Quick или куда-нибудь еще.
Для того, чтобы использовать модель нам надо, в первую очередь, задать каталог при помощи свойства folder. Путь надо задавать в формате URL, т.е. путь к каталог у файловой системы задается через «file:». Можно указать путь для ресурсов при помощи «qrc:».
Можно задать фильтры для имен файлов при помощи свойства nameFilters, принимающего список масок для отбора нужных файлов. Можно настраивать также попадание в модель каталогов и сортировку файлов.
Для примера, получим список файлов в каталоге и выведем информацию об этих файлах в виде таблицы:
Мы убираем из модели каталоги и оставляем только файлы *.jpg и *.png.
С этой моделью у делегата в качестве данных доступна информация о файле: путь, имя и т.п. Мы используем здесь имя, размер и время модификации.
К файловой системе мы доступ получать научились. Но смотреть на имена картинок может быть не так чтобы уж очень захватывающе, так что в качестве бонуса сделаем чуть более интересное их отображение :) Мы уже рассматривали такую вещь, как CoverFlow. Самое время тут ее применить.
Итак, возьмем пример CoverFlow и немного его поменяем. Модель мы возьмем из предыдущего примера. Увеличим размер элемента:
И поменяем делегата:
Ну а теперь посмотрим на прикольную штуку, которая у нас получилось:
FolderListModel — очень полезный компонент, дающий нам доступ к файловой системе и, несмотря на свою экспериментальность, его вполне можно использовать уже сейчас.
Помимо специально разработанных для создания моделей компонентов, немало других объектов может также выступать в качестве модели. И такой вариант может даже получится проще, чем использование для модели специальных компонентов.
В основном, такие модели получаются пассивными, и подходят, когда количество элементов фиксированное или редко меняется.
Мы рассмотрим такие типы в качестве модели:
Можно использовать обыкновенные JavaScript-массивы в качестве модели. Для каждого элемента массива будет создан делегат и данные самого элемент массива будут доступны в делегате через свойство modelData.
Если в массиве находятся объекты, то modelData тоже будет объектом и будет содержать все свойства исходного объекта. Если в качестве элементов будут простые значения, то они и будут в качестве modelData. Например:
и в делегате обращаемся к данным модели так:
И точно также как и в ListModel, мы можем в данные модели поместить функцию. Как и в случае с ListModel, если ее поместить в обычный JavaScript-объект, то в делегате она будет видна как пустой объект. Поэтому здесь тоже используем трюк с QtObject.
И в делегате вызываем функцию:
Я уже говорил, что почти все JavaScript-модели являются пассивными и эта не исключение. При изменении элементов и их добавлении/удалении представление не будет знать, что они поменялись. Так происходит потому, что у свойств JavaScript-объектов нет сигналов, которые вызываются при изменении свойства, в отличие от Qt-объектов и, соответственно QML-объектов. Представление получит сигнал, если мы изменим само свойство, используемое в качестве модели, заменим модель. Но тут есть одна хитрость: мы можем не только присвоить этому свойству новую модель но и переприсвоить старую. Например:
Такая модель хорошо подходит для данных, которые поступают с веб-ресурсов и обновляются редко и/или полностью.
JavaScript-объекты и объекты QML могут выступать моделью. У этой модели будет один элемент и свойства объекта будут ролями в делегате.
Возьмем самый первый пример и переделаем для использовании JavaScript-объекта в качестве модели:
Свойства объекта в делегате доступны через modelData:
Как и с JavaScript-массивами, изменение объекта после того, как он был установлен в качестве модели никак не влияет на отображение, т.е. это тоже пассивная модель.
К JavaScript-моделям я отнес и использование одного QML-объекта в качестве модели. Хотя эти объекты могут использоваться как полноценная QML-модель, по функциональности это почти аналог использования обычного JavaScript-объекта, с некоторыми особенностями. Поэтому я и рассматриваю их вместе.
Поменяем тот же пример для использования в качестве модели QML-объекта:
Item здесь выбран чтобы показать, что в качестве модели может быть любой QML-объект. На практике, если нужно хранить только данные, то лучше всего подойдет QtObject. Это самый базовый и, соответственно, самый легкий QML-объект. Item же, в данном случае, содержит слишком много лишнего.
У такой модели данные в делегате доступны как через model, так и через modelData.
Также, эта модель является единственной активной из JavaScript-моделей. Поскольку у свойств QML-объектов есть сигналы, вызывающиеся при изменении свойства, то изменение свойства в объекте приведет к изменению данных в делегате.
Самая простая модель :) Мы можем в качестве модели использовать целое число. Это число является количеством элементов модели.
Или можно напрямую указать в качестве модели константу:
В делегате будет доступно свойство modelData, которое содержит индекс. Индекс также будет доступен через model.index.
Такая модель хорошо подойдет, когда надо создать некоторое количество одинаковых элементов.
Мы рассмотрели модели, которые реализуются средствами QML и JavaScript. Вариантов много, но от себя скажу, что наиболее часто используемые — это ListModel и JavaScript-массивы.
Рассмотренные модели реализуются достаточно просто, если нам не требуются какие-то особые хитрости (вроде хранения функций в ListModel). В тех случаях, где такой вариант подходит, мы можем реализовать все компоненты MVC на одном языке и тем самым уменьшить сложность программы.
Но, я хочу обратить внимание на одну вещь. Не стоит все тащить все в QML, стоит руководствоваться практическими соображениями. Некоторые вещи может быть проще реализовать на C++. Именно C++-модели мы рассмотрим в следующей части.
По тому, как модели реализуются, я разделю их на три категории:
- модели на C++;
- модели на QML;
- модели на JavaScript.
JavaScript-модели я вынес в отдельную категорию, т.к. у них есть определенные особенности, про них я расскажу чуть позже.
Начнем рассмотрение с моделей, реализованных средствами QML.
1. ListModel
Это достаточно простой и, в то же время, функциональный компонент. Элементы в ListModel можно как определять статически (это продемонстрировано в первом примере), так и добавлять/удалять динамически (соответственно, во втором примере). Разберем оба способа подробнее.
1) Статический
Когда мы определяем элементы модели статически, нам нужно определить данные в дочерних элементах, которые имеют тип ListElement и определяются внутри модели. Данные определяются в свойствах объекта ListElement и доступны как роли в делегате.
При статическом определении данных в ListModel, типы данных, которые можно записать в ListElement весьма ограничены. По сути, все данные должны быть константами. Т.е. можно использовать строки или числа, а вот объект или функцию использовать не получится. В таком случае вы получите ошибку «ListElement: cannot use script for property value». Но можно использовать список, элементами которого будут все те же объекты ListElement.
Роль texts мы используем внутри делегата в качестве модели, таким образом можно достичь нескольких уровней вложенности.
В результате мы получим примерно такое:
Еще один важный момент. В статически описанной модели во всех объектах ListElement каждая роль должна хранить данные только одного типа. Т.е. нельзя в одном элементе записать в нее число, а в другом строку. Например, рассмотрим немного измененную модель из самого первого примера:
Мы получим такую ошибку: «Can't assign to existing role 'text' of different type [String -> Number]» и вместо текста во втором делегате получим 0.
2) Динамический
Этот способ дает нам гораздо больше возможностей, чем статический. Не все они описаны в документации и могут быть очевидными, поэтому рассмотрим их поподробнее.
Интерфейс для манипуляции элементами в ListModel похож на интерфейс обычного списка. Элементы можно добавлять/удалять/перемещать, можно получать их значение и заменять или редактировать.
ListModel принимает значение элемента в виде JavaScript-объекта. Соответственно, свойства этого объекта станут ролями в делегате.
Если взять самый первый пример, то модель можно переписать так, чтобы она наполнялась динамически:
Объект можно задавать не только литералом, а передать переменную, которая этот объект содержит:
Когда я писал про статическое наполнение, я сказал, что типы данных, которые можно поместить в модель должны быть константами. У меня есть хорошая новость :) Когда мы наполняем модель динамически, эти ограничения не действуют. Мы можем в качестве значения свойства и массивы, и объекты. Даже функции, но с небольшими особенностями. Возьмем все этот же пример и немного его перепишем:
Поскольку мы поместили свойства color и text в объект data, то в делегате они будут как свойства этого объекта, т.е. model.data.color.
С функциями немного сложнее. Если мы просто сделаем свойство в объекте и присвоим ему функцию, то внутри делегата мы увидим, что эта функция превратилась в пустой объект. Но если использовать тип QtObject, то внутри него все сохраняется и ничего не пропадает. Так что в определении компонента мы можем добавить такую строчку:
и эта функция вызовется после создания компонента.
Помещение функций в данные больше походит на хак и я рекомендую не сильно увлекаться такими вещами, а вот помещение объектов в модель очень нужная вещь. Например, если приходят данные из сети прямо в QML (при помощи XMLHttpRequest) в формате JSON (а при работе с веб-ресурсами обычно так и происходит), то декодировав JSON, мы получим JavaScript-объект, который можно просто добавить в ListModel.
Я уже писал про то, что во всех статически определенных элементах ListModel роли должны быть одних и тех же типов. По умолчанию, для элементов, добавляемых в ListModel динамически это правило тоже действует. И первый добавленный элемент определяет, какого типа будут роли. Но в Qt 5 добавилась возможность сделать типы ролей динамическими. Для этого нужно установить у ListModel свойство dynamicRoles в true.
Удобная штука, но есть пару важных моментов, которые стоит помнить. Ценой за такое удобство является производительность — разработчики Qt утверждают, что она будет в 4-6 раз меньше. Кроме того, динамические типы ролей не будут работать у модели со статически определенными элементами.
Еще один очень важный момент. Первый добавляемый в модель элемент определяет не только типы ролей, но и какие роли вообще в модели будут. Если в нем какие-то роли отсутствуют, то их потом не получится добавить. Но есть одно исключение. Если элементы добавляются на этапе создания модели (т.е. в обработчике Component.onCompleted), то в итоге у модели будут все роли, которые были во всех этих элементах.
Возьмем второй пример и немного его переделаем так, чтобы при создании модели добавлялся один элемент без свойства text, а затем по нажатию на кнопку будем добавлять элементы с текстом «new».
В результате, у всех новых элементов текста не будет и будет в качестве текста «old»:
Перепишем определение модели и добавим на этапе создания еще один элемент со свойством text, но без свойства color:
Подправим определении делегата, чтобы использовался цвет по умолчанию, если он не указан:
В итоге модель сформирована с обеими ролями и при добавлении новых элементов все отображается так, как задумано:
Мы также можем комбинировать статическое и динамической наполнение модели. Но использование статического способа накладывает все его ограничения и динамически мы сможем добавлять только объекты с ролями тех же типов.
Небольшая новость: в Qt 5.1 эта модель вынесена из QtQuick в отдельный модуль QtQml.Models. Чтобы ее использовать, надо подключить этот модуль:
Но бросаться все переписывать не обязательно —для совместимости с существующем кодом модель будет доступна и в модуле QtQuick.
ListModel можно считать QML-версией моделей из Qt. Она имеет похожий функционал, позволяет манипулировать данными и является активной моделью. Могу сказать, что в QML это наиболее функциональный и удобный компонент для создания моделей.
2. VisualItemModel (ObjectModel)
Архитектура Model-View фреймворка Qt выделяет две основных сущности: модель и представление и одну вспомогательную — делегат. Поскольку представление здесь является контейнером для экземпляров делегата, то делегат обычно определяется там же.
Этот компонент позволяет перенести делегат из представления в саму модель. Реализуется это тем, что в модель помещаются не данные, а уже готовые визуальные элементы. Соответственно, представлению в таком случае делегат не нужен и оно используется только как контейнер, обеспечивая позиционирование элементов и навигацию по ним.
Одной интересной особенностью VisualItemModel является то, что в нее можно положить объекты разный типов. Обычная модель с делегатом использует для отображения всех данных объекты одного и того же типа. Когда требуется отображать в одном представлении элементы разных типов, такая модель является одним из вариантов решения данной проблемы.
В качестве примера, поместим в модель объекты типов Rectangle и Text и отобразим их при помощи ListView:
В Qt 5.1 эта модель вынесена из QtQuick в отдельный модуль QtQml.Models и называется ObjectModel. Точно также, как и с ListModel, для использования этой модели надо подключить соответствующий модуль. Интерфейс остался тот же, достаточно просто заменить VisualDataModel на ObjectModel.
Модель будет все также доступна и через VisualDataModel, чтобы не ломать совместимость со старым кодом. Но если разрабатывать под новую версию, лучше сразу использовать новое название.
3. XmlListModel
При работе с веб-ресурсами нередко применяется формат XML. В частности, он используется в таких вещах, как RSS, XSPF, различных подкастах и т.п. А значит, у нас появляется задача получить этот файл и его распарсить. Еще XML может содержать список элементов (например список песен в случае XSPF), из которых нам нужно будет создать модель. Перебирать дерево элементов и наполнять модель вручную не самый удобный способ, так что нужна возможность задать выбрать элементы из XML-файла автоматически и представить их в виде модели. Эти задачи и реализует XmlListModel.
От нас требуется указать адрес XML-файла, указать критерий, по которому нужно отобрать элементы и определить, какие роли должны быть видны в делегате. В качестве критерия для отбора элементов мы пишем запрос в формате XPath. Для ролей делегата мы указываем тоже XPath-запрос, на основании которого из элемента будут получены данные для роли. Для простых случаев, вроде разбора RSS, эти запросы тоже будут простыми и по сути описывают путь в XML-файле. Я не буду здесь углубляться в дебри XPath и если вам пока не особо понятно, что это за зверь, я рекомендую почитать соответствующий раздел в документации по Qt. Здесь же я буду использовать примеры, которые не делают никакой хитрой выборки, так что я надеюсь, что все будет достаточно понятно.
В качестве примера, мы получим RSS-фид Хабра и отобразим заголовки статей.
Нужные нам элементы — это блоки , который вложены в , а тот в свою очередь в . Из этого пути мы конструируем наше первое выражение XPath. У нас будет всего одна роль, содержащая заголовок статьи. Чтобы его получить, нужно у элемента взять и привести его в строку. Из этого мы и формируем второе выражение XPath. На этом формирование модели закончено, осталось только ее отобразить. В итоге мы получим примерно такой результат:
Эта модель вынесена в отдельный модуль, для ее использования, надо дополнительно подключать этот модуль:
4. FolderListModel
Для многих приложений совсем не лишним будет доступ к файловой системе. В QML есть для этого экспериментальный компонент, представляющий каталог файловой системы в виде модели — FileSystemModel. Чтобы его использовать, надо подключит одноименный модуль:
Пока он экспериментальный, он входит в Qt Labs, но в будущем его могут переместить в Qt Quick или куда-нибудь еще.
Для того, чтобы использовать модель нам надо, в первую очередь, задать каталог при помощи свойства folder. Путь надо задавать в формате URL, т.е. путь к каталог у файловой системы задается через «file:». Можно указать путь для ресурсов при помощи «qrc:».
Можно задать фильтры для имен файлов при помощи свойства nameFilters, принимающего список масок для отбора нужных файлов. Можно настраивать также попадание в модель каталогов и сортировку файлов.
Для примера, получим список файлов в каталоге и выведем информацию об этих файлах в виде таблицы:
Мы убираем из модели каталоги и оставляем только файлы *.jpg и *.png.
С этой моделью у делегата в качестве данных доступна информация о файле: путь, имя и т.п. Мы используем здесь имя, размер и время модификации.
К файловой системе мы доступ получать научились. Но смотреть на имена картинок может быть не так чтобы уж очень захватывающе, так что в качестве бонуса сделаем чуть более интересное их отображение :) Мы уже рассматривали такую вещь, как CoverFlow. Самое время тут ее применить.
Итак, возьмем пример CoverFlow и немного его поменяем. Модель мы возьмем из предыдущего примера. Увеличим размер элемента:
И поменяем делегата:
Ну а теперь посмотрим на прикольную штуку, которая у нас получилось:
FolderListModel — очень полезный компонент, дающий нам доступ к файловой системе и, несмотря на свою экспериментальность, его вполне можно использовать уже сейчас.
5. JavaScript-модели
Помимо специально разработанных для создания моделей компонентов, немало других объектов может также выступать в качестве модели. И такой вариант может даже получится проще, чем использование для модели специальных компонентов.
В основном, такие модели получаются пассивными, и подходят, когда количество элементов фиксированное или редко меняется.
Мы рассмотрим такие типы в качестве модели:
- списки/массивы;
- объекты JavaScript и QML-компоненты;
- целые числа.
1) Списки/массивы
Можно использовать обыкновенные JavaScript-массивы в качестве модели. Для каждого элемента массива будет создан делегат и данные самого элемент массива будут доступны в делегате через свойство modelData.
Если в массиве находятся объекты, то modelData тоже будет объектом и будет содержать все свойства исходного объекта. Если в качестве элементов будут простые значения, то они и будут в качестве modelData. Например:
и в делегате обращаемся к данным модели так:
И точно также как и в ListModel, мы можем в данные модели поместить функцию. Как и в случае с ListModel, если ее поместить в обычный JavaScript-объект, то в делегате она будет видна как пустой объект. Поэтому здесь тоже используем трюк с QtObject.
И в делегате вызываем функцию:
Я уже говорил, что почти все JavaScript-модели являются пассивными и эта не исключение. При изменении элементов и их добавлении/удалении представление не будет знать, что они поменялись. Так происходит потому, что у свойств JavaScript-объектов нет сигналов, которые вызываются при изменении свойства, в отличие от Qt-объектов и, соответственно QML-объектов. Представление получит сигнал, если мы изменим само свойство, используемое в качестве модели, заменим модель. Но тут есть одна хитрость: мы можем не только присвоить этому свойству новую модель но и переприсвоить старую. Например:
Такая модель хорошо подходит для данных, которые поступают с веб-ресурсов и обновляются редко и/или полностью.
2) объекты
JavaScript-объекты и объекты QML могут выступать моделью. У этой модели будет один элемент и свойства объекта будут ролями в делегате.
Возьмем самый первый пример и переделаем для использовании JavaScript-объекта в качестве модели:
Свойства объекта в делегате доступны через modelData:
Как и с JavaScript-массивами, изменение объекта после того, как он был установлен в качестве модели никак не влияет на отображение, т.е. это тоже пассивная модель.
К JavaScript-моделям я отнес и использование одного QML-объекта в качестве модели. Хотя эти объекты могут использоваться как полноценная QML-модель, по функциональности это почти аналог использования обычного JavaScript-объекта, с некоторыми особенностями. Поэтому я и рассматриваю их вместе.
Поменяем тот же пример для использования в качестве модели QML-объекта:
Item здесь выбран чтобы показать, что в качестве модели может быть любой QML-объект. На практике, если нужно хранить только данные, то лучше всего подойдет QtObject. Это самый базовый и, соответственно, самый легкий QML-объект. Item же, в данном случае, содержит слишком много лишнего.
У такой модели данные в делегате доступны как через model, так и через modelData.
Также, эта модель является единственной активной из JavaScript-моделей. Поскольку у свойств QML-объектов есть сигналы, вызывающиеся при изменении свойства, то изменение свойства в объекте приведет к изменению данных в делегате.
3) Целое число
Самая простая модель :) Мы можем в качестве модели использовать целое число. Это число является количеством элементов модели.
Или можно напрямую указать в качестве модели константу:
В делегате будет доступно свойство modelData, которое содержит индекс. Индекс также будет доступен через model.index.
Такая модель хорошо подойдет, когда надо создать некоторое количество одинаковых элементов.
В качестве вывода
Мы рассмотрели модели, которые реализуются средствами QML и JavaScript. Вариантов много, но от себя скажу, что наиболее часто используемые — это ListModel и JavaScript-массивы.
Рассмотренные модели реализуются достаточно просто, если нам не требуются какие-то особые хитрости (вроде хранения функций в ListModel). В тех случаях, где такой вариант подходит, мы можем реализовать все компоненты MVC на одном языке и тем самым уменьшить сложность программы.
Но, я хочу обратить внимание на одну вещь. Не стоит все тащить все в QML, стоит руководствоваться практическими соображениями. Некоторые вещи может быть проще реализовать на C++. Именно C++-модели мы рассмотрим в следующей части.
Комментариев нет:
Отправить комментарий