Введение
Многие реальные приложения отображают список или сетку однородных элементов (список сообщений, каталог товаров, список файлов) — в этой статье разберём систему моделей и видов Qt Quick: Repeater для простого повторения элементов по фиксированному или динамическому списку, и более продвинутые ListView/GridView, оптимизированные для больших, потенциально прокручиваемых наборов данных.
Концепция
Repeater создаёт экземпляр заданного делегата (шаблона визуального представления, статья 484) для каждого элемента модели — простой, но не оптимизированный для очень больших наборов данных способ, поскольку создаёт визуальные объекты для абсолютно всех элементов модели сразу, независимо от того, видны ли они реально на экране в данный момент. ListView/GridView решают эту проблему через виртуализацию — создают (и удерживают в памяти) визуальные представления только для элементов, реально видимых в текущей области прокрутки (плюс небольшой буфер сверху/снизу для плавности), что даёт принципиально лучшую производительность и потребление памяти для списков с большим количеством элементов (тысячи и более), для которых Repeater создал бы избыточное количество визуальных объектов одновременно.
Пример кода: Модели и виды (Models/Views) в Qt Quick
// Repeater — простой случай, подходящий для небольшого, фиксированного набора элементов
import QtQuick
Column {
Repeater {
model: ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница"]
delegate: Text {
text: "День: " + modelData // modelData — встроенное имя для текущего элемента простой модели
}
}
}
// ListView с моделью объектов — типичный, идиоматичный паттерн отображения списка
import QtQuick
ListView {
width: 300; height: 400
model: ListModel {
ListElement { name: "Заказ #1001"; amount: 1500 }
ListElement { name: "Заказ #1002"; amount: 2300 }
ListElement { name: "Заказ #1003"; amount: 890 }
}
delegate: Rectangle {
width: ListView.view.width
height: 50
color: index % 2 === 0 ? "white" : "lightgray" // index — встроенное имя текущего индекса
Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
spacing: 20
Text { text: name } // name — поле текущего элемента модели, доступное напрямую по имени
Text { text: amount + " ₽" }
}
}
// Виртуализация в действии — только видимые delegate реально существуют как объекты,
// независимо от того, сколько ListElement реально объявлено в модели
}
// GridView — аналогичная виртуализация, но для сеточного, а не линейного расположения
GridView {
width: 400; height: 400
cellWidth: 100; cellHeight: 100
model: 50 // числовая модель — просто 50 элементов с индексами от 0 до 49
delegate: Rectangle {
width: 90; height: 90
color: Qt.hsla(index / 50, 0.7, 0.5, 1.0) // цвет, зависящий от индекса элемента
}
}
Пояснения к коду
Repeater с простым JavaScript-массивом строк показывает базовое использование с modelData — для простых, не структурированных моделей (массив примитивных значений) modelData даёт прямой доступ к текущему элементу без необходимости в более сложной структуре модели. ListView с ListModel/ListElement показывает более структурированный, типичный для реальных приложений случай — каждый ListElement определяет набор именованных полей (name, amount), доступных в делегате непосредственно по имени (без необходимости явно писать modelData.name), что упрощает синтаксис делегата. width: ListView.view.width в делегате ListView — идиоматичный способ заставить делегат заполнять всю доступную ширину списка, автоматически адаптируясь при изменении размера самого ListView.
Подводные камни
- Использование
Repeaterдля очень большого количества элементов (тысячи и более) вместоListView/GridView— посколькуRepeaterсоздаёт визуальные объекты для всех элементов сразу, независимо от их видимости, это может привести к заметному замедлению создания сцены и избыточному потреблению памяти для действительно больших наборов данных, тогда как виртуализацияListView/GridViewспроектирована именно для эффективной работы с такими объёмами. - Создание сложных, «тяжёлых» делегатов с большим количеством вложенных элементов или дорогих в вычислении привязок для
ListView/GridViewс очень частой прокруткой — хотя виртуализация ограничивает количество ОДНОВРЕМЕННО существующих визуальных объектов, каждое появление нового делегата при прокрутке (когда элемент входит в видимую область) требует создания и инициализации этого визуального объекта, и слишком «тяжёлые» делегаты могут заметно снижать плавность быстрой прокрутки, несмотря на сам факт использования виртуализации. - Изменение модели (добавление/удаление/изменение элементов) напрямую, «в обход» методов, специально предназначенных для уведомления
ListView/GridViewоб этом изменении (дляListModel—append(),remove(),setProperty(), а не прямая, неконтролируемая модификация внутреннего состояния) — корректное, ожидаемое поведение виртуализированного вида (правильное обновление визуального представления, анимация добавления/удаления, статья 487) зависит от использования предназначенных для этого методов изменения модели, информирующих вид о характере произошедшего изменения, а не произвольной модификации данных, способной оставить вид в несогласованном с реальными данными состоянии. - Игнорирование разницы между
index(порядковый номер элемента в текущем представлении модели) и каким-либо стабильным, уникальным идентификатором элемента данных (если модель поддерживает сортировку/фильтрацию, статья 489) —indexможет меняться при изменении порядка отображения элементов (сортировка, фильтрация), и код, ошибочно полагающийся наindexкак на стабильный, неизменный идентификатор конкретного элемента данных (а не его текущую, потенциально изменяющуюся позицию в списке), может работать некорректно после применения сортировки или фильтрации к той же модели.