Представления списков
Представления списков помогают привлекательно показывать объекты из модели. Чтобы использовать представление списка, нужно помнить о трёх вещах:
- Модель, содержащая данные, которые должно показывать представление списка
- Делегат, который определяет, как будет показываться каждый элемент модели
- Само представление списка, которое будет показывать сведения из модели в соответствии с делегатом
Для получения дополнительных разъяснений в документации Qt есть подробная страница по этой теме.
Основы моделей и представлений
Следует обратить внимание на два важных свойства представления списка:
- model, принимающее данные или
idобъекта, который хранит эти данные - delegate, принимающее компонент, который будет использоваться для показа данных в модели
Модель не видна, так как содержит только данные. Обычно делегат заключается в Component для повторного использования: он служит образцом для создания экземпляра каждого делегата.
Ниже приведён пример, содержащий ровно одно представление списка, одну модель и один делегат, с использованием Kirigami.SubtitleDelegate:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of Plasma products"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: plasmaProductsModel
delegate: listDelegate
}
ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
text: `${model.product} is KDE software developed for ${model.target} stored at index ${model.index} of this list`
}
}
}
}И точно такой же пример, но встроенный:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of Plasma products (inline)"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
delegate: Controls.ItemDelegate {
width: ListView.view.width
text: `${model.product} is KDE software developed for ${model.target} stored at index ${model.index} of this list`
}
}
}
}Понимание моделей
Модель содержит данные, которые будут использоваться для заполнения представления списка. Разные способы использования моделей предполагают разные способы доступа к данным:
| Способ использования | Получение доступа | Когда использовать |
|---|---|---|
| Модели Qt с несколькими ролями | model.index, model.somerole | В большинстве случаев |
| Модели Qt с одной ролью | model.index, model.somerole, model.modelData | В большинстве случаев — для прототипирования |
| Модель массива JavaScript | model.index, model.modelData | Для прототипирования |
| Модель целых чисел | model.index, model.modelData | Для прототипирования |
Прочитать о других способах использования моделей можно в документации Qt.
В таблице выше под «моделями Qt» подразумеваются как модели, специфичные для C++, например QAbstractListModel, так и модели, специфичные для QML, например ListModel. На этой странице руководства рассматриваются только модели, специфичные для QML. Далее приводится руководство по подключению моделей C++ к QML с помощью QAbstractListModel.
Свойство model.index доступно каждой модели и содержит индекс (позицию) каждого делегата. Для удобства его можно сократить до index.
Свойство model.somerole, упомянутое выше, — всего лишь заполнитель, а не конкретное свойство из QML: somerole может быть любой ролью, определённой моделью. В первом примере кода на этой странице, показанном над таблицей, модель plasmaProductsModel имеет роли product и target, к которым можно обратиться с помощью model.product и model.target соответственно.
Как model.index можно сократить до index, так и каждое свойство model.somerole для удобства можно сократить до somerole (например, product), но рекомендуется превратить их в обязательные свойства:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of Plasma products (shortened with required properties)"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: plasmaProductsModel
delegate: listDelegate
}
ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
required property string product
required property string target
required property int index
text: `${product} is KDE software developed for ${target} stored at index ${index} of this list`
}
}
}
}Кроме того, если модель содержит только одну роль или не содержит ролей вовсе, её данные также доступны через свойство model.modelData, которое можно сократить до modelData (в этом случае оно также должно быть обязательным свойством):
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of KDE software"
width: 400
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: kdeSoftwareModel
delegate: listDelegate
}
ListModel {
id: kdeSoftwareModel
ListElement { software: "Dolphin" }
ListElement { software: "Discover" }
ListElement { software: "KHelpCenter" }
ListElement { software: "KCalc" }
ListElement { software: "Ark" }
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
required property string modelData
text: modelData // Это соответствует model.software
}
}
}
}Для сравнения, вот как выглядел бы приведённый выше код с массивом JavaScript, без роли:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of KDE software (as JS array)"
width: 400
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: ["Dolphin", "Discover", "KHelpCenter", "KCalc", "Ark"]
delegate: listDelegate
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
required property string modelData
text: modelData
}
}
}
}Использование целого числа в качестве модели может быть полезно в очень специфичных случаях, а именно при прототипировании и тестировании:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "Simple list of indexes"
width: 400
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
anchors.fill: parent
model: 30
delegate: listDelegate
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
required property string modelData
text: `This delegate's index is: ${modelData}`
}
}
}
}Понимание представлений и делегатов
Вернёмся к исходному примеру:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
title: "List of Plasma products"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
// anchors.fill: parent
model: plasmaProductsModel
delegate: listDelegate
}
ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
text: `${model.product} is KDE software developed for ${model.target} stored at index ${model.index} of this list`
}
}
}
}В отличие от модели (которая лишь содержит данные) и делегата Component (который появляется только при создании экземпляра), представление — это визуальный компонент, создаваемый сразу, поэтому ему нужно либо задать размеры, либо использовать привязки или компоновки.
Поскольку представления обычно представляют собой списки содержимого, которое пользователь хотел бы прокручивать, при добавлении их в Kirigami.ScrollablePage представления становятся основным содержимым с небольшими отступами вокруг, и нет необходимости заставлять их заполнять страницу. Если представление добавлено в простую Kirigami.Page, потребуется правильно задать его размеры, прежде чем оно появится. Иными словами: на прокручиваемой странице выше anchors.fill: parent не требуется; если бы использовалась простая страница, она была бы необходима.
Существует несколько API представлений, которые можно использовать: одни из Qt, другие из Kirigami. Ниже перечислены наиболее часто используемые из них:
- ListView в Qt
- GridView из Qt
- TableView в Qt
- TreeView из Qt
- CardsListView в Kirigami
- ColumnView в Kirigami
С другой стороны, у делегата всегда должны быть заданы размеры. Обычно его размеры задаются так, чтобы он занимал всю ширину представления и только её.
Типичные ошибки
Из вышесказанного следует, что делегаты не должны иметь нижние привязки, поскольку делегату не требуется та же высота, что и у представления. Иными словами, скорее всего, никогда не потребуется использовать anchors.fill: parent.
Кроме того, хотя размеры можно задать с помощью родительского элемента и привязок, в качестве которого обычно выступает contentItem представления, как показано ниже:
Controls.ItemDelegate {
anchors.left: parent.left
anchors.right: parent.right
text: // …
}Не гарантируется, что родительским элементом делегата будет представление, поэтому этого следует избегать. Вместо этого используйте присоединённое свойство ListView.view, чтобы указать на родительское представление делегата:
Controls.ItemDelegate {
width: ListView.view.width
text: // …
}Чаще всего делегат используется внутри Component, который не создаёт экземпляр делегата сразу. При построении представления делегат применяется как шаблон для создания каждого элемента представления.
Хотя собственные компоненты для использования в качестве делегатов можно создавать и без специальных API Qt для делегатов (например, Layout, содержащий несколько элементов), QtQuick Controls всё же предоставляет API делегатов, которые проще в использовании:
- ItemDelegate (делегаты, содержащие только текст)
- CheckDelegate (делегаты с флажком)
- RadioDelegate (делегаты с радиокнопкой)
- SwitchDelegate (делегаты с переключателем)
- SwipeDelegate (делегаты с поддержкой смахивания)
По возможности следует отдавать предпочтение исходным делегатам Qt.
Помимо этих делегатов Qt, Kirigami предоставляет собственные аналоги с дополнительной функциональностью подзаголовков и значков:
- TitleSubtitle
- IconTitleSubtitle
- SubtitleDelegate
- CheckSubtitleDelegate
- RadioSubtitleDelegate
- SwitchSubtitleDelegate
API, оканчивающийся на «Delegate», можно назначить непосредственным делегатом представления — как и в предыдущих примерах, где использовался Controls.ItemDelegate:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.kirigami.delegates as KD
Kirigami.ApplicationWindow {
title: "List of Plasma products"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
model: plasmaProductsModel
delegate: listDelegate
}
ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
Component {
id: listDelegate
KD.CheckSubtitleDelegate {
width: ListView.view.width
text: `${model.product} is KDE software developed for ${model.target}.`
subtitle: `This delegate is stored at index ${model.index} of this list`
icon.name: "kde"
}
}
}
}Предполагается, что как TitleSubtitle, так и IconTitleSubtitle применяются для переопределения contentItem делегата Qt, например:
import QtQuick
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.kirigami.delegates as KD
Kirigami.ApplicationWindow {
title: "List of Plasma products"
width: 600
height: 400
pageStack.initialPage: Kirigami.ScrollablePage {
ListView {
// anchors.fill: parent
model: plasmaProductsModel
delegate: listDelegate
}
ListModel {
id: plasmaProductsModel
ListElement { product: "Plasma Desktop"; target: "desktop" }
ListElement { product: "Plasma Mobile"; target: "mobile" }
ListElement { product: "Plasma Bigscreen"; target: "TVs" }
}
Component {
id: listDelegate
Controls.ItemDelegate {
width: ListView.view.width
text: `${model.product} is KDE software developed for ${model.target}.`
contentItem: KD.IconTitleSubtitle {
title: parent.text
subtitle: `This delegate is stored at index ${model.index} of this list`
icon.name: "kde"
}
}
}
}
}Практический пример использования делегатов Kirigami приведён в файле ListItemTest в репозитории Kirigami.