Реклама

четверг, 6 марта 2014 г.

Qt на Android
Приложение на Qt для Android состоит из двух частей:
  1. Native-часть, которая содержит одну или несколько .so-библиотек, в которые компилируется ваше приложение. Если вы хотите, чтобы у приложения не было внешних зависимостей, здесь также будут находиться библиотеки Qt.
  2. Android-часть. Она включает в себя несколько разделов:
  • Android manifest. Это точка входа вашего приложения. Android использует этот файл, чтобы определить, какие Приложения или Activity запустить, какие вашему приложению установлены разрешения (permissions), определяет минимальную версию Android API, необходимую для приложения и так далее.
  • Два Java-класса, которые загружают зависимости и ваше приложение.
  • Файлы .aidl сервиса Ministro. Эти два интерфейса используются для обмена сообщениями с сервисом Ministro. Этот сервис является одним из решений для развёртывания (о нём будет сказано позже).
  • Остальные ресурсы (строки, изображения и прочее).

Все эти части собираются в один пакет, который представляет собой ваше приложение. Теперь давайте рассмотрим, как различные части взаимодействуют между собой.

При открытии приложения, Android использует manifest-файл для запуска activity. Эта кастомная activity представляет собой кусочек магии в мире Java вашего приложения. Для того, чтобы была возможность обновления библиотек Qt без переустановки существующего приложения, Java-часть разделена надвое: очень маленькая часть, которая хранится вместе с приложеним (как его часть) и другая часть (библиотека Java, файл .jar), которая содержит всю логику для плагина QPA.

Первая Java-часть, которая поставляется вместе с приложением, отвечает за поиск необходимых библиотек (Qt и Java) и загружает их. Кроме этого, она пересылает все события (нажатия, изменения состояния приложения, изменения экрана и так далее) во вторую Java-часть.

Вторая Java-часть ответственна за связь с Qt. Она содержит всю необходимую логику QPA-плагина для Android, например, создание поверхности для рисования и управление ею, обработку виртуальной клавиатуры и так далее.

Таким образом, первая (Java) часть находит и загружает все необходимые библиотеки и ваше приложение, после чего перенаправляет события второй части, но кто же тогда вызывает метод «main»? Это делает QPA плагин. Нет, я не сумасшедший! И да, плагин QPA загружается до запуска вашего приложения (вообще, он загружается даже до того, как ваше приложение будет загружено).

Позвольте мне объяснить, чем вызван такой безумный дизайн.

Моей мечтой было найти способ, при котором разработчики смогли бы всего лишь откомпилировать их существующие приложения для Android, так что я был вынужден каким-то образом вызывать метод «main» (я не хочу заставлять вас создавать другую входную точку в приложение, наподобие «WinMain»).

Проблема заключается в том, что для того, чтобы вызвать метод из Java, кто-то должен сперва этот метод зарегистрировать, иначе вызвать его будет нельзя. Здесь на сцене и появляется QPA. После загрузки, плагин QPA регистрирует несколько нативных функций, которые используются Java-частью для вызовов в нативной среде. После завершения загрузки всех библиотек и вашего приложения, Java-часть вызывает метод «startQtApplication», зарегистрированный ранее плагином QPA. Этот метод ищет символ метода «main», после чего создаёт поток и вызывает в нём выполнение метода «main». Создание параллельного потока здесь необходимо потому, что вызов «main» блокирует остальные вызовы, пока продолжает существовать, а мы должны сохранять UI-поток Android свободным для нормального функционирования цикла обработки событий Android.

В следующей статье я расскажу о том, как использовать JNI для вызовов в/из Java в/из нативную (C/C++) среду.

В заключение, взглянем на текущее состояние Qt на Android, что вы получите в Qt 5.2 и каковы планы на Qt 5.3 для Android.

Статус Qt Essentials:


Статус Qt Add-Ons:

среда, 5 марта 2014 г.

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

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

По тому, как модели реализуются, я разделю их на три категории:

  • модели на C++;
  • модели на QML;
  • модели на JavaScript.

JavaScript-модели я вынес в отдельную категорию, т.к. у них есть определенные особенности, про них я расскажу чуть позже.
Начнем рассмотрение с моделей, реализованных средствами QML.

1. ListModel

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

1) Статический

Когда мы определяем элементы модели статически, нам нужно определить данные в дочерних элементах, которые имеют тип ListElement и определяются внутри модели. Данные определяются в свойствах объекта ListElement и доступны как роли в делегате.
При статическом определении данных в ListModel, типы данных, которые можно записать в ListElement весьма ограничены. По сути, все данные должны быть константами. Т.е. можно использовать строки или числа, а вот объект или функцию использовать не получится. В таком случае вы получите ошибку «ListElement: cannot use script for property value». Но можно использовать список, элементами которого будут все те же объекты 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 мы используем внутри делегата в качестве модели, таким образом можно достичь нескольких уровней вложенности.
В результате мы получим примерно такое:



Еще один важный момент. В статически описанной модели во всех объектах ListElement каждая роль должна хранить данные только одного типа. Т.е. нельзя в одном элементе записать в нее число, а в другом строку. Например, рассмотрим немного измененную модель из самого первого примера:

ListModel {
    id: dataModel

    ListElement {
        color: "orange"
        text: 1
    }
    ListElement {
        color: "skyblue"
        text: "second"
    }
}

Мы получим такую ошибку: «Can't assign to existing role 'text' of different type [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)
    }
}

Поскольку мы поместили свойства color и text в объект data, то в делегате они будут как свойства этого объекта, т.е. model.data.color.

С функциями немного сложнее. Если мы просто сделаем свойство в объекте и присвоим ему функцию, то внутри делегата мы увидим, что эта функция превратилась в пустой объект. Но если использовать тип QtObject, то внутри него все сохраняется и ничего не пропадает. Так что в определении компонента мы можем добавить такую строчку:

Component.onCompleted: model.functions.alive()

и эта функция вызовется после создания компонента.

Помещение функций в данные больше походит на хак и я рекомендую не сильно увлекаться такими вещами, а вот помещение объектов в модель очень нужная вещь. Например, если приходят данные из сети прямо в QML (при помощи XMLHttpRequest) в формате JSON (а при работе с веб-ресурсами обычно так и происходит), то декодировав JSON, мы получим JavaScript-объект, который можно просто добавить в ListModel.

Я уже писал про то, что во всех статически определенных элементах ListModel роли должны быть одних и тех же типов. По умолчанию, для элементов, добавляемых в ListModel динамически это правило тоже действует. И первый добавленный элемент определяет, какого типа будут роли. Но в Qt 5 добавилась возможность сделать типы ролей динамическими. Для этого нужно установить у ListModel свойство dynamicRoles в true.

ListModel {
    id: dataModel

    dynamicRoles: true

    Component.onCompleted: {
    append({ color: "orange", text: "first" })
    append({ color: "skyblue", text: 2 })
    }
}

Удобная штука, но есть пару важных моментов, которые стоит помнить. Ценой за такое удобство является производительность — разработчики Qt утверждают, что она будет в 4-6 раз меньше. Кроме того, динамические типы ролей не будут работать у модели со статически определенными элементами.

Еще один очень важный момент. Первый добавляемый в модель элемент определяет не только типы ролей, но и какие роли вообще в модели будут. Если в нем какие-то роли отсутствуют, то их потом не получится добавить. Но есть одно исключение. Если элементы добавляются на этапе создания модели (т.е. в обработчике Component.onCompleted), то в итоге у модели будут все роли, которые были во всех этих элементах.

Возьмем второй пример и немного его переделаем так, чтобы при создании модели добавлялся один элемент без свойства text, а затем по нажатию на кнопку будем добавлять элементы с текстом «new».

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

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



Перепишем определение модели и добавим на этапе создания еще один элемент со свойством 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. VisualItemModel (ObjectModel)

Архитектура 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. XmlListModel

При работе с веб-ресурсами нередко применяется формат 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. FolderListModel

Для многих приложений совсем не лишним будет доступ к файловой системе. В QML есть для этого экспериментальный компонент, представляющий каталог файловой системы в виде модели — FileSystemModel. Чтобы его использовать, надо подключит одноименный модуль:

import Qt.labs.folderlistmodel 1.0

Пока он экспериментальный, он входит в Qt Labs, но в будущем его могут переместить в Qt Quick или куда-нибудь еще.
Для того, чтобы использовать модель нам надо, в первую очередь, задать каталог при помощи свойства folder. Путь надо задавать в формате 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-компоненты;
  • целые числа.

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-массивами, изменение объекта после того, как он был установлен в качестве модели никак не влияет на отображение, т.е. это тоже пассивная модель.

К JavaScript-моделям я отнес и использование одного QML-объекта в качестве модели. Хотя эти объекты могут использоваться как полноценная QML-модель, по функциональности это почти аналог использования обычного JavaScript-объекта, с некоторыми особенностями. Поэтому я и рассматриваю их вместе.

Поменяем тот же пример для использования в качестве модели QML-объекта:

Item {
    id: dataModel

    property color color: "orange"
    property string text: "some text"
}

Item здесь выбран чтобы показать, что в качестве модели может быть любой QML-объект. На практике, если нужно хранить только данные, то лучше всего подойдет QtObject. Это самый базовый и, соответственно, самый легкий QML-объект. Item же, в данном случае, содержит слишком много лишнего.

У такой модели данные в делегате доступны как через model, так и через modelData.

Также, эта модель является единственной активной из JavaScript-моделей. Поскольку у свойств QML-объектов есть сигналы, вызывающиеся при изменении свойства, то изменение свойства в объекте приведет к изменению данных в делегате.

3) Целое число

Самая простая модель :) Мы можем в качестве модели использовать целое число. Это число является количеством элементов модели.

property int dataModel: 5

Или можно напрямую указать в качестве модели константу:

model: 5

В делегате будет доступно свойство modelData, которое содержит индекс. Индекс также будет доступен через model.index.

Такая модель хорошо подойдет, когда надо создать некоторое количество одинаковых элементов.

В качестве вывода

Мы рассмотрели модели, которые реализуются средствами QML и JavaScript. Вариантов много, но от себя скажу, что наиболее часто используемые — это ListModel и JavaScript-массивы.

Рассмотренные модели реализуются достаточно просто, если нам не требуются какие-то особые хитрости (вроде хранения функций в ListModel). В тех случаях, где такой вариант подходит, мы можем реализовать все компоненты MVC на одном языке и тем самым уменьшить сложность программы.

Но, я хочу обратить внимание на одну вещь. Не стоит все тащить все в QML, стоит руководствоваться практическими соображениями. Некоторые вещи может быть проще реализовать на C++. Именно C++-модели мы рассмотрим в следующей части.