Программирование модели/представления
Введение в программирование модели/представления
Qt содержит набор классов представлений элементов, которые используют архитектуру модели/представления для управления отношением между данными и способом их отображения пользователю. Разделение функциональности, введенное этой архитектурой, предоставляет разработчикам большую гибкость в настройке отображения элементов и обеспечивает стандартный интерфейс модели, позволяющий использовать широкий спектр источников данных с существующими представлениями элементов. В этом документе мы дадим краткое введение в парадигму модели/представления, опишем вовлеченные концепции и опишем архитектуру системы представления элементов. Каждый из компонентов архитектуры объясняется, и приводятся примеры, показывающие, как использовать предоставленные классы.
Архитектура модели/представления
Модель-Представление-Контроллер (MVC) — это шаблон проектирования, зародившийся в Smalltalk, который часто используется при создании пользовательских интерфейсов. В Шаблонах проектирования, Гамма и др. пишут:
MVC состоит из трех типов объектов. Модель — это объект приложения, представление — его экранное представление, а контроллер определяет, как пользовательский интерфейс реагирует на ввод пользователя. До MVC проекты пользовательских интерфейсов обычно объединяли эти объекты. MVC развязывает их, чтобы повысить гибкость и повторное использование.
Если объекты представления и контроллера объединены, результатом является архитектура модели/представления. Это все еще отделяет способ хранения данных от способа их отображения пользователю, но обеспечивает более простую структуру, основанную на тех же принципах. Это разделение позволяет отображать одни и те же данные в нескольких разных представлениях и реализовывать новые типы представлений без изменения базовых структур данных. Для гибкого управления вводом пользователя мы вводим понятие делегата. Преимущество наличия делегата в этой структуре заключается в том, что это позволяет настраивать способ отрисовки и редактирования элементов данных.
|
Архитектура модели/представления Модель взаимодействует с источником данных, предоставляя интерфейс для других компонентов в архитектуре. Характер взаимодействия зависит от типа источника данных и способа реализации модели. Представление получает индексы модели от модели; это ссылки на элементы данных. Предоставляя индексы модели модели, представление может извлекать элементы данных из источника данных. В стандартных представлениях делегат отображает элементы данных. При редактировании элемента делегат взаимодействует с моделью напрямую, используя индексы модели. |
В целом, классы модели/представления можно разделить на три группы, описанные выше: модели, представления и делегаты. Каждый из этих компонентов определяется абстрактными классами, которые обеспечивают общие интерфейсы и, в некоторых случаях, стандартные реализации функций. Абстрактные классы предназначены для наследования, чтобы обеспечить полный набор функциональности, ожидаемый другими компонентами; это также позволяет писать специализированные компоненты.
Модели, представления и делегаты взаимодействуют друг с другом с помощью сигналов и слотов:
- Сигналы от модели информируют представление об изменениях данных, хранящихся в источнике данных.
- Сигналы от представления предоставляют информацию о взаимодействии пользователя с отображаемыми элементами.
- Сигналы от делегата используются во время редактирования, чтобы сообщить модели и представлению о состоянии редактора.
Модели
Все модели элементов основаны на классе QAbstractItemModel. Этот класс определяет интерфейс, используемый представлениями и делегатами для доступа к данным. Сами данные не обязательно должны храниться в модели; они могут храниться в структуре данных или репозитории, предоставляемом отдельным классом, файлом, базой данных или каким-либо другим компонентом приложения.
Основные понятия, связанные с моделями, представлены в разделе Классы моделей.
QAbstractItemModel предоставляет интерфейс к данным, достаточно гибкий, чтобы обрабатывать представления, представляющие данные в виде таблиц, списков и деревьев. Однако при реализации новых моделей для структур данных типа список и таблица классы QAbstractListModel и QAbstractTableModel являются лучшими стартовыми точками, потому что они обеспечивают соответствующие стандартные реализации общих функций. Каждый из этих классов может быть расширен, чтобы предоставить модели, поддерживающие специализированные типы списков и таблиц.
Процесс наследования моделей обсуждается в разделе Создание новых моделей.
Qt предоставляет некоторые готовые модели, которые могут использоваться для обработки элементов данных:
- QStringListModel используется для хранения простого списка элементов QString.
- QStandardItemModel управляет более сложными древовидными структурами элементов, каждый из которых может содержать произвольные данные.
- QFileSystemModel предоставляет информацию о файлах и каталогах в локальной файловой системе.
- QSqlQueryModel, QSqlTableModel и QSqlRelationalTableModel используются для доступа к базам данных с использованием соглашений модели/представления.
Если эти стандартные модели не соответствуют вашим требованиям, вы можете расширить QAbstractItemModel, QAbstractListModel или QAbstractTableModel, чтобы создать свои собственные пользовательские модели.
Представления
Для разных типов представлений предоставляются полные реализации: QListView отображает список элементов, QTableView отображает данные из модели в таблице, а QTreeView отображает элементы модели данных в иерархическом списке. Каждый из этих классов основан на абстрактном базовом классе QAbstractItemView. Хотя эти классы являются готовыми к использованию реализациями, они также могут быть расширены для предоставления настраиваемых представлений.
Доступные представления рассматриваются в разделе Классы представлений.
Делегаты
QAbstractItemDelegate — это абстрактный базовый класс для делегатов в рамках модели/представления. Стандартная реализация делегата предоставляется классом QStyledItemDelegate, и он используется в качестве стандартного делегата в стандартных представлениях Qt. Однако QStyledItemDelegate и QItemDelegate — это независимые альтернативы для рисования и предоставления редакторов для элементов в представлениях. Разница между ними заключается в том, что QStyledItemDelegate использует текущий стиль для рисования своих элементов. Поэтому мы рекомендуем использовать QStyledItemDelegate в качестве базового класса при реализации пользовательских делегатов или при работе с таблицами стилей Qt.
Делегаты описаны в разделе Классы делегатов.
Сортировка
Существует два способа подхода к сортировке в архитектуре модели/представления; выбор подхода зависит от вашей базовой модели.
Если ваша модель может сортироваться, т. е. если она повторно реализует функцию QAbstractItemModel::sort(), как QTableView, так и QTreeView предоставляют API, позволяющий программно сортировать данные вашей модели. Кроме того, вы можете включить интерактивную сортировку (т. е. позволяющую пользователям сортировать данные щелчком по заголовкам представления), подключив сигнал QHeaderView::sortIndicatorChanged() к слоту QTableView::sortByColumn() или QTreeView::sortByColumn() соответственно.
Альтернативный подход, если ваша модель не имеет требуемого интерфейса или если вы хотите использовать представление списка для представления данных, заключается в использовании модели прокси для преобразования структуры вашей модели перед представлением данных в представлении. Это подробно описано в разделе Модели прокси.
Классы-утилиты
Несколько утилитарных классов получены от стандартных классов представлений для удобства приложений, которые полагаются на классы представлений элементов и таблиц Qt на основе элементов. Они не предназначены для наследования.
Примеры таких классов включают QListWidget, QTreeWidget и QTableWidget.
Эти классы менее гибкие, чем классы представлений, и не могут использоваться с произвольными моделями. Мы рекомендуем использовать подход модели/представления для обработки данных в представлениях элементов, если у вас нет сильной потребности в наборе классов на основе элементов.
Если вы хотите использовать преимущества подхода модели/представления, сохраняя при этом интерфейс на основе элементов, рассмотрите использование классов представлений, таких как QListView, QTableView и QTreeView с QStandardItemModel.
Использование моделей и представлений
В следующих разделах поясняется, как использовать шаблон модели/представления в Qt. Каждый раздел содержит пример и следует за разделом, показывающим, как создавать новые компоненты.
Две модели, включенные в Qt
Две стандартные модели, предоставляемые Qt, — это QStandardItemModel и QFileSystemModel. QStandardItemModel — это многоцелевая модель, которая может использоваться для представления различных структур данных, необходимых для представлений списка, таблицы и дерева. Эта модель также содержит элементы данных. QFileSystemModel — это модель, которая сохраняет информацию о содержимом каталога. В результате она не хранит сами элементы данных, а просто представляет файлы и каталоги в локальной файловой системе.
QFileSystemModel предоставляет готовую модель для экспериментов и может быть легко настроена для использования существующих данных. Используя эту модель, мы можем показать, как настроить модель для использования с готовыми представлениями и исследовать, как манипулировать данными с помощью индексов модели.
Использование представлений с существующей моделью
Классы QListView и QTreeView являются наиболее подходящими представлениями для использования с QFileSystemModel. Приведенный ниже пример отображает содержимое каталога в представлении дерева рядом с той же информацией в представлении списка. Представления делят выбор пользователя, так что выбранные элементы выделены в обоих представлениях.
Мы создаём QFileSystemModel, чтобы он был готов к использованию, и создаём несколько представлений для отображения содержимого каталога. Это демонстрирует самый простой способ использования модели. Создание и использование модели выполняется в рамках одной main() функции:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath()); Модель настроена для использования данных с определённой файловой системы. Вызов setRootPath() сообщает модели, какой диск на файловой системе следует отобразить для представлений.
Мы создаём два представления, чтобы иметь возможность исследовать элементы, хранящиеся в модели, двумя разными способами:
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath())); Представления создаются так же, как и другие виджеты. Настройка представления для отображения элементов в модели сводится к вызову его функции setModel() с моделью каталога в качестве аргумента. Мы фильтруем данные, предоставляемые моделью, вызывая функцию setRootIndex() для каждого представления, передавая соответствующий индекс модели из модели файловой системы для текущего каталога.
Функция index() , используемая в данном случае, уникальна для QFileSystemModel; мы передаём ей каталог, и она возвращает индекс модели. Индексы модели обсуждаются в разделе Классы моделей.
Остальная часть функции просто отображает представления внутри виджета-разделителя и запускает цикл обработки событий приложения:
splitter->setWindowTitle("Two views onto the same file system model");
splitter->show();
return app.exec();
} В приведённом выше примере мы не упомянули, как обрабатывать выделения элементов. Этот вопрос рассматривается более подробно в разделе о Обработке выделений в представлениях элементов.
Классы моделей
Прежде чем изучать, как обрабатываются выделения, вам может быть полезно изучить концепции, используемые в модели/представлении.
Основные концепции
В архитектуре модель/представление модель предоставляет стандартный интерфейс, который представления и делегаты используют для доступа к данным. В Qt стандартный интерфейс определяется классом QAbstractItemModel. Независимо от того, как элементы данных хранятся в любой базовой структуре данных, все подклассы QAbstractItemModel представляют данные как иерархическую структуру, содержащую таблицы элементов. Представления используют эту конвенцию для доступа к элементам данных в модели, но они не ограничены в том, как они представляют эту информацию пользователю.
Модели также уведомляют любые подключённые представления об изменениях данных через механизм сигналов и слотов.
В этом разделе описываются некоторые основные концепции, которые являются центральными для того, как компоненты получают доступ к элементам данных через класс модели. Более сложные концепции рассматриваются в последующих разделах.
Индексы модели
Для обеспечения того, чтобы представление данных было отделено от способа доступа к ним, вводится понятие индекса модели. Каждый элемент информации, который можно получить через модель, представляется индексом модели. Представления и делегаты используют эти индексы для запроса элементов данных для отображения.
В результате только модель должна знать, как получить данные, и тип данных, управляемых моделью, может быть определён достаточно обобщённо. Индексы модели содержат указатель на модель, которая их создала, и это предотвращает путаницу при работе с более чем одной моделью.
QAbstractItemModel *model = index.model();
Индексы модели обеспечивают временные ссылки на части информации и могут быть использованы для извлечения или изменения данных через модель. Поскольку модели могут время от времени перестраивать свои внутренние структуры, индексы модели могут стать недействительными и не должны храниться. Если требуется долговременная ссылка на элемент информации, необходимо создать постоянный индекс модели. Это обеспечивает ссылку на информацию, которую модель поддерживает в актуальном состоянии. Временные индексы модели предоставляются классом QModelIndex, а постоянные индексы модели — классом QPersistentModelIndex.
Для получения индекса модели, соответствующего элементу данных, модели необходимо указать три свойства: номер строки, номер столбца и индекс модели родительского элемента.
Строки и столбцы
В своей наиболее простой форме к модели можно обратиться как к простой таблице, в которой элементы находятся по номерам строки и столбца. Это не означает, что базовые элементы данных хранятся в структуре массива; использование номеров строк и столбцов — всего лишь соглашение для взаимодействия компонентов. Мы можем получить информацию о любом заданном элементе, указав его номера строки и столбца модели, и получим индекс, который представляет этот элемент:
QModelIndex index = model->index(row, column, ...);
Модели, которые предоставляют интерфейсы к простым структурам данных одного уровня, таким как списки и таблицы, не нуждаются в предоставлении какой-либо другой информации, но, как показано в коде выше, нам нужно предоставить больше информации при получении индекса модели.
|
Строки и столбцы Диаграмма показывает представление базовой табличной модели, в которой каждый элемент находится по паре номеров строки и столбца. Мы получаем индекс модели, который ссылается на элемент данных, передав соответствующие номера строки и столбца модели. QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexB = model->index(1, 1, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex()); Элементы верхнего уровня в модели всегда ссылаются, указав |
Родители элементов
Табличный интерфейс доступа к данным элементов, предоставляемый моделями, идеально подходит для использования данных в представлениях таблиц или списков; система номеров строк и столбцов точно соответствует тому, как представления отображают элементы. Однако структуры, такие как древовидные представления, требуют, чтобы модель предоставила более гибкий интерфейс к элементам внутри. В результате каждый элемент может также быть родителем другой таблицы элементов, так же, как и элемент верхнего уровня в древовидном представлении может содержать другой список элементов.
При запросе индекса элемента модели мы должны предоставить некоторую информацию о родителе элемента. Вне модели единственный способ сослаться на элемент — через индекс модели, поэтому также необходимо указать родительский индекс модели:
QModelIndex index = model->index(row, column, parent);
|
Родители, строки и столбцы На диаграмме показано представление древовидной модели, в которой каждый элемент обозначается родителем, номером строки и номером столбца. Элементы "A" и "C" представлены как элементы верхнего уровня в модели: QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex()); Элемент "A" имеет несколько дочерних элементов. Индекс модели для элемента "B" получается следующим кодом: QModelIndex indexB = model->index(1, 0, indexA); |
Роли элементов
Элементы в модели могут выполнять различные роли для других компонентов, что позволяет предоставлять различные типы данных для разных ситуаций. Например, Qt::DisplayRole используется для доступа к строке, которая может быть отображена как текст в представлении. Обычно элементы содержат данные для ряда различных ролей, и стандартные роли определяются Qt::ItemDataRole.
Мы можем запросить у модели данные элемента, передав ему индекс модели, соответствующий элементу, и указав роль, чтобы получить тип данных, который нам нужен:
QVariant value = model->data(index, role);
|
Роли элементов Роль указывает модели, к какому типу данных относится ссылка. Представления могут отображать роли различными способами, поэтому важно предоставить соответствующую информацию для каждой роли. Раздел Создание новых моделей более подробно рассматривает некоторые конкретные применения ролей. |
Большинство распространённых применений данных элементов покрываются стандартными ролями, определёнными в Qt::ItemDataRole. Предоставляя соответствующие данные элементов для каждой роли, модели могут давать подсказки представлениям и делегатам о том, как элементы должны быть представлены пользователю. Различные виды представлений имеют свободу интерпретировать или игнорировать эту информацию по мере необходимости. Также возможно определить дополнительные роли для целей, специфичных для приложения.
Резюме
- Индексы моделей предоставляют представлениям и делегатам информацию о местоположении элементов, предоставляемых моделями, способом, независимым от любых базовых структур данных.
- Элементы обозначаются по номерам строк и столбцов, а также по индексу модели их родительских элементов.
- Индексы модели создаются моделями по запросу других компонентов, таких как представления и делегаты.
- Если для родительского элемента указан действительный индекс модели при запросе индекса с помощью index(), возвращаемый индекс относится к элементу под этим родительским элементом в модели. Полученный индекс относится к дочернему элементу.
- Если для родительского элемента указан недействительный индекс модели при запросе индекса с помощью index(), возвращаемый индекс относится к элементу верхнего уровня в модели.
- Роль role различает различные типы данных, ассоциированных с элементом.
Использование индексов моделей
Чтобы продемонстрировать, как можно извлечь данные из модели с помощью индексов модели, мы настраиваем QFileSystemModel без представления и отображаем имена файлов и каталогов в виджете. Хотя это не демонстрирует обычный способ использования модели, это демонстрирует соглашения, используемые моделями при работе с индексами модели.
QFileSystemModel загружает данные асинхронно, чтобы минимизировать использование системных ресурсов. При работе с этой моделью необходимо это учитывать.
Мы создаём модель файловой системы следующим образом:
QFileSystemModel *model = new QFileSystemModel;
connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
QModelIndex parentIndex = model->index(directory);
int numRows = model->rowCount(parentIndex);
});
model->setRootPath(QDir::currentPath); В этом случае мы начинаем с настройки стандартной QFileSystemModel. Мы подключаем её к лямбда-функции, в которой получим родительский индекс, используя специфическую реализацию index(), предоставленную этой моделью. В лямбда-функции мы подсчитываем количество строк в модели с помощью функции rowCount(). Наконец, мы устанавливаем путь к корневому каталогу QFileSystemModel, чтобы начать загрузку данных и вызвать лямбда-функцию.
Для простоты нас интересуют только элементы в первом столбце модели. Мы рассматриваем каждую строку по очереди, получаем индекс модели для первого элемента в каждой строке и считываем данные, хранящиеся для этого элемента в модели.
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex); Для получения индекса модели мы указываем номер строки, номер столбца (ноль для первого столбца) и соответствующий индекс модели для родительского элемента всех элементов, которые нам нужны. Текст, хранящийся в каждом элементе, извлекается с помощью функции data() модели. Мы указываем индекс модели и DisplayRole, чтобы получить данные для элемента в виде строки.
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
} Приведённый выше пример демонстрирует основные принципы извлечения данных из модели:
- Размеры модели можно получить, используя rowCount() и columnCount(). Для работы этих функций обычно требуется указать индекс родительской модели.
- Индексы модели используются для доступа к элементам в модели. Для определения элемента необходимы номер строки, номер столбца и индекс родительской модели.
- Чтобы получить доступ к элементам верхнего уровня в модели, укажите нулевой индекс модели в качестве родительского индекса с помощью
QModelIndex(). - Элементы содержат данные для различных ролей. Для получения данных для определённой роли необходимо предоставить модели как индекс элемента, так и роль.
Дополнительная информация
Новые модели могут быть созданы путём реализации стандартного интерфейса, предоставляемого классом QAbstractItemModel. В разделе Создание новых моделей мы покажем это на примере создания удобной готовой модели для хранения списков строк.
Классы представлений
Концепции
В архитектуре модель/представление представление получает данные из модели и отображает их пользователю. Способ отображения данных не обязательно должен соответствовать представлению данных, предоставляемому моделью, и может быть полностью иным, чем структура данных, используемая для хранения элементов.
Разделение содержимого и представления достигается с помощью стандартного интерфейса модели, предоставляемого классом QAbstractItemModel, стандартного интерфейса представления, предоставляемого классом QAbstractItemView, и использования индексов моделей, которые представляют элементы данных обобщённым способом. Представления обычно управляют общим представлением данных, полученных из моделей. Они могут рендерить отдельные элементы данных сами или использовать делегаты для обработки функций рендеринга и редактирования.
Помимо отображения данных, представления обрабатывают навигацию между элементами и некоторые аспекты выбора элементов. Представления также реализуют базовые функции пользовательского интерфейса, такие как контекстные меню и перетаскивание. Представление может предоставлять стандартные возможности редактирования элементов или работать с делегатом для предоставления пользовательского редактора.
Представление может быть создано без модели, но модель должна быть предоставлена перед тем, как оно сможет отобразить полезную информацию. Представления отслеживают элементы, выбранные пользователем, с помощью выбора, которые могут быть сохранены отдельно для каждого представления или совместно между несколькими представлениями.
Некоторые представления, такие как QTableView и QTreeView, отображают заголовки, а также элементы. Эти элементы также реализуются классом представления QHeaderView. Заголовки обычно обращаются к той же модели, что и представление, которое их содержит. Они извлекают данные из модели, используя функцию QAbstractItemModel::headerData(), и обычно отображают заголовок в виде метки. Новые заголовки могут быть подклассами класса QHeaderView для предоставления более специализированных меток для представлений.
Использование существующего представления
Qt предоставляет три готовых класса представлений, которые отображают данные из моделей в форматах, знакомых большинству пользователей. QListView может отображать элементы из модели как простой список или в виде классического представления с иконками. QTreeView отображает элементы модели в виде иерархии списков, что позволяет компактным образом представлять глубоко вложенные структуры. QTableView представляет элементы модели в виде таблицы, подобно расположению в приложении для работы со сводными таблицами.
Стандартное поведение представлений, показанных выше, должно быть достаточным для большинства приложений. Они обеспечивают базовые возможности редактирования и могут быть настроены в соответствии с потребностями более специализированных пользовательских интерфейсов.
Использование модели
Мы используем модель списка строк, которую мы создали в качестве примера модели, настроим её с некоторыми данными и создадим представление для отображения содержимого модели. Всё это может быть выполнено в одной функции:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = new StringListModel(numbers); Обратите внимание, что StringListModel объявлена как QAbstractItemModel. Это позволяет нам использовать абстрактный интерфейс к модели и гарантирует, что код по-прежнему работает, даже если мы заменим модель списка строк другой моделью.
Список представлений, предоставляемый классом QListView, является достаточным для отображения элементов в модели списка строк. Мы создаём представление и настраиваем модель с помощью следующих строк кода:
QListView *view = new QListView; view->setModel(model);
Представление отображается обычным способом:
view->show();
return app.exec();
} Представление рендерит содержимое модели, получая доступ к данным через интерфейс модели. Когда пользователь пытается отредактировать элемент, представление использует стандартный делегат для предоставления виджета редактора.
На рисунке выше показано, как QListView представляет данные в модели списка строк. Поскольку модель редактируема, представление автоматически позволяет редактировать каждый элемент в списке с помощью стандартного делегата.
Использование нескольких представлений одной модели
Предоставление нескольких представлений одной модели — это просто вопрос установки той же модели для каждого представления. В следующем коде мы создаём два представления таблиц, каждое из которых использует ту же простую модель таблицы, которую мы создали для этого примера:
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model); Использование сигналов и слотов в архитектуре модель/представление означает, что изменения в модели могут распространяться на все присоединённые представления, что гарантирует, что мы всегда можем получить доступ к тем же данным независимо от используемого представления.
На рисунке выше показаны два разных представления одной и той же модели, каждое из которых содержит несколько выбранных элементов. Хотя данные из модели отображаются последовательно через представление, каждое представление поддерживает свою внутреннюю модель выбора. Это может быть полезно в определённых ситуациях, но для многих приложений желательна общая модель выбора.
Обработка выбора элементов
Механизм обработки выбора элементов в представлениях предоставляется классом QItemSelectionModel. Все стандартные представления по умолчанию строят свои собственные модели выбора и взаимодействуют с ними обычным способом. Модель выбора, используемая представлением, может быть получена с помощью функции selectionModel(), а замена модели выбора может быть задана с помощью setSelectionModel(). Возможность управления моделью выбора, используемой представлением, полезна, когда мы хотим предоставить несколько согласованных представлений на одни и те же данные модели.
Как правило, если вы не подклассифицируете модель или представление, вам не нужно напрямую манипулировать содержимым выбора. Однако к интерфейсу модели выбора можно получить доступ при необходимости, и это рассматривается в разделе Обработка выбора в представлениях элементов.
Совместное использование выбора между представлениями
Хотя удобно, что классы представлений предоставляют свои собственные модели выбора по умолчанию, когда мы используем более одного представления на одну и ту же модель, часто желательно, чтобы и данные модели, и выбор пользователя отображались согласованно во всех представлениях. Поскольку классы представлений позволяют заменить их внутренние модели выбора, мы можем достичь согласованного выбора между представлениями с помощью следующей строки:
secondTableView->setSelectionModel(firstTableView->selectionModel());
Второе представление получает модель выбора первого представления. Теперь оба представления работают с одной и той же моделью выбора, поддерживая синхронизацию как данных, так и выбранных элементов.
В приведённом выше примере использовались два представления одного типа для отображения данных одной и той же модели. Однако если бы использовались два представления разных типов, выбранные элементы могли бы быть представлены очень по-разному в каждом представлении; например, непрерывный выбор в представлении таблицы может быть представлен как фрагментированный набор выделенных элементов в представлении дерева.
Классы делегатов
Концепции
В отличие от шаблона MVC, модель/представление не включает полностью отдельный компонент для управления взаимодействием с пользователем. Обычно представление отвечает за представление данных модели пользователю и за обработку входных данных пользователя. Для обеспечения некоторой гибкости в способе получения этого ввода взаимодействие осуществляется с помощью делегатов. Эти компоненты обеспечивают возможности ввода и также отвечают за рендеринг отдельных элементов в некоторых представлениях. Стандартный интерфейс управления делегатами определяется в классе QAbstractItemDelegate.
Ожидается, что делегаты смогут рендерить своё содержимое самостоятельно, реализуя функции paint() и sizeHint(). Однако простые делегаты на основе виджетов могут вместо QAbstractItemDelegate подклассировать класс QStyledItemDelegate и воспользоваться стандартными реализациями этих функций.
Редакторы для делегатов могут быть реализованы либо с помощью виджетов для управления процессом редактирования, либо путём прямого обработки событий. Первый подход рассматривается позже в этом разделе, а также показан в примере Spin Box Delegate.
Пример Pixelator демонстрирует, как создать пользовательского делегата, который выполняет специализированный рендеринг для представления таблицы.
Использование существующего делегата
Стандартные представления, предоставляемые Qt, используют экземпляры QStyledItemDelegate для предоставления возможностей редактирования. Эта стандартная реализация интерфейса делегата рендерит элементы в обычном стиле для каждого из стандартных представлений: QListView, QTableView и QTreeView.
Все стандартные роли обрабатываются стандартным делегатом, используемым стандартными представлениями. Способ их интерпретации описан в документации QStyledItemDelegate.
Делегат, используемый представлением, возвращается функцией itemDelegate(). Функция setItemDelegate() позволяет установить пользовательского делегата для стандартного представления, и её необходимо использовать при установке делегата для пользовательского представления.
Простой делегат
Реализация делегата здесь использует QSpinBox для предоставления возможностей редактирования и предназначена в основном для использования с моделями, отображающими целые числа. Хотя мы создали пользовательскую модель таблицы на основе целых чисел для этой цели, мы могли бы легко использовать QStandardItemModel вместо нее, так как пользовательский делегат контролирует ввод данных. Мы создаем представление таблицы для отображения содержимого модели, и оно будет использовать пользовательский делегат для редактирования.
Мы наследуем делегат от QStyledItemDelegate, потому что не хотим писать пользовательские функции отображения. Однако мы все равно должны предоставить функции для управления виджетом редактора:
class SpinBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
}; Обратите внимание, что виджеты редактора не создаются при построении делегата. Мы создаем виджет редактора только тогда, когда он необходим.
Предоставление редактора
В этом примере, когда представление таблицы нужно предоставить редактор, оно просит делегата предоставить виджет редактора, который подходит для изменяемого элемента. Функция createEditor() предоставляет делегату все необходимое для настройки подходящего виджета:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setFrame(false);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
} Обратите внимание, что нам не нужно хранить указатель на виджет редактора, так как представление берет на себя ответственность за его уничтожение, когда он больше не нужен.
Мы устанавливаем стандартный обработчик событий делегата на виджет редактора, чтобы гарантировать, что он предоставляет стандартные сочетания клавиш для редактирования, ожидаемые пользователями. Дополнительные сочетания клавиш могут быть добавлены в редактор для более сложного поведения; они обсуждаются в разделе Советы по редактированию.
Представление гарантирует, что данные редактора и его геометрия устанавливаются корректно, вызывая функции, которые мы определим позже для этих целей. Мы можем создавать разные редакторы в зависимости от индекса модели, предоставленного представлением. Например, если у нас есть столбец целых чисел и столбец строк, мы можем вернуть либо QSpinBox, либо QLineEdit, в зависимости от того, какой столбец редактируется.
Делегат должен предоставить функцию для копирования данных модели в редактор. В этом примере мы читаем данные, сохраненные в роли отображения, и соответствующим образом устанавливаем значение в спинбоксе.
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value);
} В этом примере мы знаем, что виджет редактора — это спинбокс, но мы могли бы предоставить разные редакторы для разных типов данных в модели, в этом случае нам нужно было бы привести виджет к соответствующему типу перед доступом к его членам.
Отправка данных в модель
Когда пользователь закончил редактирование значения в спинбоксе, представление просит делегата сохранить измененное значение в модели, вызвав функцию setModelData().
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
} Так как представление управляет виджетами редактора для делегата, нам нужно только обновить модель содержимым предоставленного редактора. В этом случае мы гарантируем, что спинбокс обновлен, и обновляем модель с помощью содержащегося в нем значения, используя указанный индекс.
Стандартный класс QStyledItemDelegate информирует представление о завершении редактирования, испуская сигнал closeEditor(). Представление гарантирует закрытие и уничтожение виджета редактора. В этом примере мы предоставляем только простые возможности редактирования, поэтому нам никогда не нужно испускать этот сигнал.
Все операции с данными выполняются через интерфейс, предоставляемый QAbstractItemModel. Это делает делегат в основном независимым от типа обрабатываемых данных, но необходимо сделать некоторые предположения для использования определенных типов виджетов редактора. В этом примере мы предположили, что модель всегда содержит целые значения, но мы все равно можем использовать этот делегат с различными типами моделей, так как QVariant предоставляет разумные значения по умолчанию для неожиданных данных.
Обновление геометрии редактора
Делегат отвечает за управление геометрией редактора. Геометрия должна быть установлена при создании редактора и при изменении размера или положения элемента в представлении. К счастью, представление предоставляет всю необходимую информацию о геометрии внутри объекта параметра отображения.
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
} В этом случае мы просто используем информацию о геометрии, предоставленную параметром представления в прямоугольнике элемента. Делегат, отображающий элементы с несколькими элементами, не будет использовать прямоугольник элемента напрямую. Он будет позиционировать редактор относительно других элементов в элементе.
Советы по редактированию
После редактирования делегаты должны предоставлять другим компонентам подсказки о результате процесса редактирования и подсказки, которые помогут в последующих операциях редактирования. Это достигается путем испускания сигнала closeEditor() с подходящей подсказкой. За это отвечает стандартный обработчик событий QStyledItemDelegate, который мы установили на спинбокс при его создании.
Поведение спинбокса можно настроить, чтобы сделать его более удобным для пользователя. В стандартном обработчике событий, предоставляемом QStyledItemDelegate, если пользователь нажимает Возврат для подтверждения своего выбора в спинбоксе, делегат подтверждает значение в модели и закрывает спинбокс. Мы можем изменить это поведение, установив собственный обработчик событий на спинбокс и предоставив подсказки по редактированию, которые соответствуют нашим потребностям; например, мы можем испустить closeEditor() с подсказкой EditNextItem, чтобы автоматически начать редактирование следующего элемента в представлении.
Другой подход, который не требует использования обработчика событий, заключается в предоставлении собственного виджета редактора, возможно, наследуя QSpinBox для удобства. Этот альтернативный подход даст нам больший контроль над поведением виджета редактора ценой написания дополнительного кода. Обычно легче установить обработчик событий в делегате, если вам нужно настроить поведение стандартного виджета Qt редактора.
Делегаты не обязаны испускать эти подсказки, но те, кто этого не делает, будут менее интегрированы в приложения и будут менее удобными в использовании, чем те, которые испускают подсказки для поддержки общих действий редактирования.
Обработка выделений в представлениях элементов
Концепции
Модель выделения, используемая в классах представлений элементов, предоставляет общее описание выделений на основе возможностей архитектуры модели/представления. Хотя стандартные классы для управления выделениями достаточны для представлений элементов, модель выделения позволяет создавать специализированные модели выделения, соответствующие требованиям к собственным моделям и представлениям элементов.
Информация об элементах, выделенных в представлении, хранится в экземпляре класса QItemSelectionModel. Он сохраняет индексы модели для элементов в одной модели и независим от каких-либо представлений. Поскольку может быть много представлений одной модели, можно обмениваться выделениями между представлениями, что позволяет приложениям показывать несколько представлений согласованным образом.
Выделения состоят из диапазонов выделения. Они эффективно сохраняют информацию о больших выделениях элементов, записывая только начальные и конечные индексы модели для каждого диапазона выделенных элементов. Несмежные выделения элементов создаются с использованием более чем одного диапазона выделения для описания выделения.
Выделения применяются к набору индексов модели, хранимых моделью выделения. Последнее примененное выделение элементов известно как текущее выделение. Влияние этого выделения можно изменить даже после его применения с помощью определенных типов команд выделения. Они обсуждаются позже в этом разделе.
Текущий элемент и выделенные элементы
В представлении всегда есть текущий элемент и выделенный элемент — два независимых состояния. Элемент может быть одновременно текущим и выделенным.
Представление отвечает за обеспечение наличия всегда текущего элемента, так как навигация с клавиатуры, например, требует текущего элемента.
В таблице ниже показаны различия между текущим элементом и выделенными элементами.
| Текущий элемент | Выделенные элементы |
|---|---|
| Может быть только один текущий элемент. | Может быть несколько выделенных элементов. |
| Текущий элемент будет изменен с помощью навигации по клавиатуре или щелчка кнопкой мыши. | Состояние выделения элементов устанавливается или сбрасывается в зависимости от нескольких предопределенных режимов — например, однократное выделение, множественное выделение и т. д. — при взаимодействии пользователя с элементами. |
| Текущий элемент будет редактироваться, если нажата клавиша редактирования, F2, или элемент дважды щелкнут (при условии, что редактирование включено). | Текущий элемент может использоваться вместе с якорем для определения диапазона, который должен быть выделен или снят с выделения (или их комбинации). |
| Текущий элемент обозначается прямоугольником фокуса. | Выделенные элементы обозначаются прямоугольником выделения. |
При манипулировании выделениями часто полезно рассматривать QItemSelectionModel как запись о состоянии выделения всех элементов в модели элементов. После настройки модели выделения можно выделять, снимать выделение или переключать состояния выделения набора элементов без необходимости знать, какие элементы уже выделены. Индексы всех выделенных элементов можно получить в любое время, и другие компоненты могут быть проинформированы о изменениях в модели выделения с помощью механизма сигналов и слотов.
Использование модели выделения
Стандартные классы представлений предоставляют модели выделения по умолчанию, которые можно использовать в большинстве приложений. Модель выделения, принадлежащая одному представлению, может быть получена с помощью функции selectionModel() представления и обмениваться между многими представлениями с помощью setSelectionModel(), поэтому создание новых моделей выделения обычно не требуется.
Выделение создается путем указания модели и пары индексов модели в QItemSelection. Она использует индексы для ссылки на элементы в заданной модели и интерпретирует их как верхний левый и нижний правый элементы в блоке выделенных элементов. Для применения выделения к элементам в модели необходимо отправить выделение в модель выделения; это можно сделать несколькими способами, каждое из которых имеет разное влияние на выделения, уже присутствующие в модели выделения.
Выделение элементов
Чтобы продемонстрировать некоторые основные функции выделений, мы создаем экземпляр пользовательской модели таблицы с 32 элементами в целом и открываем представление таблицы на ее данные:
TableModel *model = new TableModel(8, 4, &app);
QTableView *table = new QTableView(0);
table->setModel(model);
QItemSelectionModel *selectionModel = table->selectionModel(); Модель выбора по умолчанию для представления таблицы извлекается для дальнейшего использования. Мы не изменяем элементы в модели, а вместо этого выбираем несколько элементов, которые представление отобразит в левом верхнем углу таблицы. Для этого нам нужно получить индексы модели, соответствующие элементам в левом верхнем и правом нижнем углах области, которую необходимо выбрать:
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex()); Чтобы выбрать эти элементы в модели и увидеть соответствующее изменение в представлении таблицы, нам нужно создать объект выбора, а затем применить его к модели выбора:
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select); Выбор применяется к модели выбора с помощью команды, определяемой комбинацией флагов выбора. В этом случае используемые флаги заставляют элементы, записанные в объекте выбора, включаться в модель выбора независимо от их предыдущего состояния. Результирующий выбор отображается представлением.
Выбор элементов может быть изменён с помощью различных операций, которые определяются флагами выбора. Результат этих операций может иметь сложную структуру, но он эффективно представлен моделью выбора. Использование различных флагов выбора для управления выбранными элементами описывается при рассмотрении способа обновления выбора.
Чтение состояния выбора
Индексы модели, хранящиеся в модели выбора, можно прочитать, используя функцию selectedIndexes(). Это возвращает неупорядоченный список индексов модели, по которому мы можем итерироваться, зная, к какой модели они относятся:
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
} В приведенном выше коде используется цикл for с диапазоном для итерации по и изменения элементов, соответствующим индексам, возвращаемым моделью выбора.
Модель выбора испускает сигналы, чтобы указать изменения в выборе. Эти сигналы уведомляют другие компоненты об изменениях как в выборе в целом, так и в текущем фокусированном элементе в модели элементов. Мы можем подключить сигнал selectionChanged() к слоту и изучить элементы в модели, которые выбраны или сняты с выбора при изменении выбора. Слот вызывается с двумя объектами QItemSelection: один содержит список индексов, соответствующих новым выбранным элементам; другой содержит индексы, соответствующие новым элементам, снятым с выбора.
В следующем коде мы предоставляем слот, который получает сигнал selectionChanged(), заполняет выбранные элементы строкой и очищает содержимое элементов, снятых с выбора.
void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndexList items = selected.indexes();
for (const QModelIndex &index : qAsConst(items)) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
for (const QModelIndex &index : qAsConst(items)) {
model->setData(index, QString());
} Мы можем отслеживать текущий фокусированный элемент, подключив сигнал currentChanged() к слоту, который вызывается с двумя индексами модели. Эти индексы соответствуют ранее фокусированному элементу и текущему фокусированному элементу.
В следующем коде мы предоставляем слот, который получает сигнал currentChanged(), и использует предоставленную информацию для обновления строки состояния QMainWindow:
void MainWindow::changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous)
{
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
} Мониторинг выборов, сделанных пользователем, прост с помощью этих сигналов, но мы также можем напрямую обновлять модель выбора.
Обновление выбора
Команды выбора предоставляются комбинацией флагов выбора, определённых в QItemSelectionModel::SelectionFlag. Каждый флаг выбора указывает модели выбора, как обновить её внутренний список выбранных элементов, когда вызываются функции select(). Наиболее часто используемый флаг — флаг Select, который сообщает модели выбора записать указанные элементы как выбранные. Флаг Toggle заставляет модель выбора инвертировать состояние указанных элементов, выбирая любые снятые с выбора элементы и снимая с выбора любые текущие выбранные элементы. Флаг Deselect снимает с выбора все указанные элементы.
Индивидуальные элементы в модели выбора обновляются путём создания выбора элементов и его применения к модели выбора. В следующем коде мы применяем второй выбор элементов к модели таблицы, показанной выше, используя команду Toggle для инвертирования состояния выбора указанных элементов.
QItemSelection toggleSelection;
topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle); Результаты этой операции отображаются в представлении таблицы, что обеспечивает удобный способ визуализации полученных результатов:
По умолчанию команды выбора действуют только на отдельные элементы, указанные индексами модели. Однако флаг, используемый для описания команды выбора, может быть объединён с дополнительными флагами для изменения целых строк и столбцов. Например, если вы вызываете select() только с одним индексом, но с командой, которая является комбинацией Select и Rows, вся строка, содержащая указанный элемент, выбирается. Следующий код демонстрирует использование флагов Rows и Columns:
QItemSelection columnSelection;
topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());
columnSelection.select(topLeft, bottomRight);
selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);
QItemSelection rowSelection;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());
rowSelection.select(topLeft, bottomRight);
selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows); Хотя в модель выбора передано только четыре индекса, использование флагов выбора Columns и Rows означает, что выбраны два столбца и две строки. На следующем изображении показан результат этих двух выборов:
Выполненные над моделью примеры команды все включали накопление выбора элементов в модели. Также можно очистить выбор или заменить текущий выбор новым.
Чтобы заменить текущий выбор новым, объедините другие флаги выбора с флагом Current. Команда с этим флагом сообщает модели выбора заменить её текущий набор индексов модели теми, что указаны в вызове select(). Чтобы очистить все выборы перед добавлением новых, объедините другие флаги выбора с флагом Clear. Это имеет эффект сброса набора индексов модели выбора.
Выбор всех элементов в модели
Чтобы выбрать все элементы в модели, необходимо создать выбор для каждого уровня модели, охватывающего все элементы на этом уровне. Мы делаем это, извлекая индексы, соответствующие элементам в левом верхнем и правом нижнем углах с заданным индексом родителя:
QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent); Выбор создаётся с этими индексами и моделью. Соответствующие элементы выбираются в модели выбора:
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select); Это необходимо выполнить для всех уровней в модели. Для элементов верхнего уровня мы определили бы индекс родителя обычным способом:
QModelIndex parent = QModelIndex();
Для иерархических моделей используется функция hasChildren() для определения того, является ли данный элемент родителем другого уровня элементов.
Создание новых моделей
Разделение функциональности между компонентами модели/представления позволяет создавать модели, которые могут использовать существующие представления. Этот подход позволяет нам представлять данные из различных источников с помощью стандартных компонентов графического интерфейса пользователя, таких как QListView, QTableView и QTreeView.
Класс QAbstractItemModel предоставляет интерфейс, достаточно гибкий для поддержки источников данных, организующих информацию в иерархических структурах, что позволяет вставлять, удалять, изменять или сортировать данные каким-либо образом. Он также поддерживает операции перетаскивания.
Классы QAbstractListModel и QAbstractTableModel обеспечивают поддержку интерфейсов для более простых неиерархических структур данных и легче используются в качестве отправной точки для простых моделей списка и таблицы.
В этом разделе мы создаём простую только для чтения модель, чтобы изучить основные принципы архитектуры модели/представления. Позже в этом разделе мы адаптируем эту простую модель так, чтобы пользователем можно было изменять элементы.
Пример более сложной модели см. в примере Simple Tree Model.
Требования к подклассам QAbstractItemModel описаны более подробно в документе Model Subclassing Reference.
Проектирование модели
При создании новой модели для существующей структуры данных важно учитывать, какой тип модели следует использовать для предоставления интерфейса к данным. Если структура данных может быть представлена как список или таблица элементов, вы можете использовать подкласс QAbstractListModel или QAbstractTableModel, так как эти классы предоставляют подходящие реализованные по умолчанию для многих функций.
Однако, если структура данных может быть представлена только в виде иерархической древовидной структуры, необходимо использовать подкласс QAbstractItemModel. Этот подход используется в примере Simple Tree Model.
В этом разделе мы реализуем простую модель, основанную на списке строк, поэтому QAbstractListModel является идеальным базовым классом для разработки.
Какую форму ни принимала бы основная структура данных, обычно полезно дополнить стандартный API QAbstractItemModel в специализированных моделях API, который позволяет более естественный доступ к базовой структуре данных. Это облегчает заполнение модели данными, но всё ещё позволяет другим общим компонентам модели/представления взаимодействовать с ней с помощью стандартного API. Описанная ниже модель предоставляет пользовательский конструктор именно для этой цели.
Пример модели только для чтения
Здесь реализуется простая, неиерархическая, только для чтения модель данных, основанная на стандартном классе QStringListModel. Она имеет QStringList в качестве внутреннего источника данных и реализует только то, что необходимо для функционирующей модели. Для упрощения реализации используется подкласс QAbstractListModel, так как он определяет разумное поведение по умолчанию для моделей списков и предоставляет более простой интерфейс, чем класс QAbstractItemModel.
При реализации модели важно помнить, что QAbstractItemModel не хранит данных, а просто предоставляет интерфейс, который представления используют для доступа к данным. Для минимальной модели только для чтения необходимо реализовать только несколько функций, так как для большинства интерфейса существуют реализации по умолчанию. Объявление класса приведено ниже:
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
private:
QStringList stringList;
}; Помимо конструктора модели, нам нужно реализовать только две функции: rowCount() возвращает количество строк в модели, а data() возвращает элемент данных, соответствующий указанному индексу модели.
Хорошо себя ведущие модели также реализуют headerData(), чтобы дать виджеты дерева и таблицы что-то для отображения в своих заголовках.
Обратите внимание, что это модель неиерархическая, поэтому нам не нужно беспокоиться о родительско-дочерних отношениях. Если наша модель была иерархической, нам также пришлось бы реализовать функции index() и parent().
Список строк хранится внутри в частном члене stringList.
Размеры модели
Мы хотим, чтобы количество строк в модели было таким же, как количество строк в списке строк. Мы реализуем функцию rowCount() с учетом этого:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
} Поскольку модель неиерархическая, мы можем безопасно проигнорировать индекс модели, соответствующий родительскому элементу. По умолчанию модели, производные от QAbstractListModel, содержат только один столбец, поэтому нам не нужно переопределять функцию columnCount().
Заголовки и данные модели
Для элементов в представлении мы хотим возвращать строки из списка строк. Функция data() отвечает за возвращение элемента данных, соответствующего аргументу индекса:
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
} Мы возвращаем допустимое значение QVariant только в том случае, если индекс модели действителен, номер строки находится в пределах диапазона элементов в списке строк и запрашиваемый роль поддерживается.
Некоторые представления, такие как QTreeView и QTableView, могут отображать заголовки вместе с данными элементов. Если наша модель отображается в представлении с заголовками, мы хотим, чтобы заголовки отображали номера строк и столбцов. Мы можем предоставить информацию о заголовках, подклассифицировав функцию headerData():
QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QStringLiteral("Column %1").arg(section);
else
return QStringLiteral("Row %1").arg(section);
} Опять же, мы возвращаем допустимое значение QVariant только в том случае, если роль поддерживается. При определении точных возвращаемых данных также учитывается ориентация заголовка.
Не все представления отображают заголовки с данными элементов, и те, которые это делают, могут быть настроены на их скрытие. Тем не менее, рекомендуется реализовывать функцию headerData() для предоставления соответствующей информации о данных, предоставляемых моделью.
Элемент может иметь несколько ролей, предоставляя различные данные в зависимости от указанной роли. Элементы в нашей модели имеют только одну роль, DisplayRole, поэтому мы возвращаем данные для элементов независимо от указанной роли. Однако мы могли бы повторно использовать данные, которые мы предоставляем для DisplayRole, в других ролях, таких как ToolTipRole, которые представления могут использовать для отображения информации об элементах в всплывающей подсказке.
Редактируемая модель
Модель только для чтения демонстрирует, как могут быть представлены простые варианты пользователю, но для многих приложений модель списка, поддающаяся редактированию, гораздо полезнее. Мы можем изменить модель только для чтения, чтобы сделать элементы редактируемыми, изменив функцию data(), которую мы реализовали для модели только для чтения, и реализовав две дополнительные функции: flags() и setData(). В определении класса добавляются следующие объявления функций:
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override; Делаем модель редактируемой
Делегат проверяет, редактируется ли элемент, прежде чем создавать редактор. Модель должна сообщить делегату, что ее элементы могут быть отредактированы. Для этого мы возвращаем правильные флаги для каждого элемента в модели; в этом случае мы активируем все элементы и делаем их как выбираемыми, так и редактируемыми:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
} Обратите внимание, что нам не нужно знать, как делегат выполняет фактический процесс редактирования. Мы должны только предоставить способ для делегата установить данные в модели. Это достигается с помощью функции setData():
bool StringListModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index, {role});
return true;
}
return false;
} В этой модели элемент в списке строк, соответствующий индексу модели, заменяется предоставленным значением. Однако прежде чем мы сможем изменить список строк, мы должны убедиться, что индекс действителен, элемент имеет правильный тип и роль поддерживается. По соглашению, мы настаиваем на том, что роль — это EditRole, поскольку это роль, используемая стандартным делегатом элементов. Для логических значений, однако, вы можете использовать Qt::CheckStateRole и установить флаг Qt::ItemIsUserCheckable; для редактирования значения используется поле выбора.
Базовые данные в этой модели одинаковы для всех ролей, поэтому эта деталь просто облегчает интеграцию модели со стандартными компонентами.
После установки данных модель должна сообщить представлениям, что некоторые данные изменились. Это делается путем отправки сигнала dataChanged(). Поскольку изменился только один элемент данных, диапазон элементов, указанный в сигнале, ограничен только одним индексом модели.
Также функцию data() необходимо изменить, добавив проверку на Qt::EditRole:
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
} Вставка и удаление строк
Можно изменить количество строк и столбцов в модели. В модели списка строк имеет смысл изменять только количество строк, поэтому мы переопределяем только функции для вставки и удаления строк. Эти функции объявлены в определении класса:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; Поскольку строки в этой модели соответствуют строкам в списке, функция insertRows() вставляет несколько пустых строк в список строк перед указанной позицией. Количество вставленных строк равно количеству указанных строк.
Индекс родительского элемента обычно используется для определения места в модели, где следует добавить строки. В данном случае у нас есть только один список строк верхнего уровня, поэтому мы просто вставляем пустые строки в этот список.
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
} Модель сначала вызывает функцию beginInsertRows(), чтобы сообщить другим компонентам, что количество строк собирается измениться. Функция указывает номера строк первой и последней новых строк, которые нужно вставить, и индекс модели для их родительского элемента. После изменения списка строк она вызывает endInsertRows(), чтобы завершить операцию и сообщить другим компонентам, что размеры модели изменились, возвращая true, чтобы указать на успех.
Функция удаления строк из модели также проста в написании. Строки, которые необходимо удалить из модели, определяются позицией и количеством строк. Мы игнорируем индекс родительского элемента, чтобы упростить нашу реализацию, и просто удаляем соответствующие элементы из списка строк.
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
} Функция beginRemoveRows() всегда вызывается перед удалением любых базовых данных и указывает первую и последнюю строки, которые нужно удалить. Это позволяет другим компонентам получить доступ к данным до того, как они станут недоступными. После удаления строк модель отправляет endRemoveRows(), чтобы завершить операцию и дать знать другим компонентам, что размеры модели изменились.
Следующие шаги
Мы можем отображать данные, предоставляемые этой моделью, или любой другой моделью, используя класс QListView для представления элементов модели в виде вертикального списка. Для модели списка строк это представление также предоставляет редактор по умолчанию, поэтому элементы могут быть изменены. Мы изучаем возможности, предоставляемые стандартными классами представлений в Классах представлений.
Документ Справочник по подклассированию моделей подробно рассматривает требования подклассов QAbstractItemModel и предоставляет руководство по виртуальным функциям, которые необходимо реализовать для включения различных функций в разных типах моделей.
Классы представлений элементов
У виджетов на основе элементов есть имена, которые отражают их использование: QListWidget предоставляет список элементов, QTreeWidget отображает многоуровневую иерархическую структуру, а QTableWidget предоставляет таблицу элементов ячеек. Каждый класс наследует поведение класса QAbstractItemView, который реализует общее поведение для выбора элементов и управления заголовками.
Список виджетов
Одноуровневые списки элементов обычно отображаются с использованием QListWidget и ряда QListWidgetItem. Виджет списка создается так же, как и любой другой виджет:
QListWidget *listWidget = new QListWidget(this);
Элементы списка могут быть добавлены непосредственно в виджет списка при их создании:
new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget); Они также могут быть созданы без родительского виджета списка и добавлены в список впоследствии:
QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem); Каждый элемент списка может отображать текстовую метку и значок. Можно изменить цвета и шрифт, используемые для отображения текста, чтобы настроить внешний вид элементов. Всплывающие подсказки, подсказки состояния и справка «Что это?» легко настраиваются, чтобы обеспечить надлежащую интеграцию списка в приложение.
newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText); По умолчанию элементы в списке представляются в порядке их создания. Список элементов можно отсортировать по критериям, указанным в Qt::SortOrder, чтобы получить список элементов, отсортированных в прямом или обратном алфавитном порядке:
listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder); Дерево виджетов
Деревья или иерархические списки элементов предоставляются классами QTreeWidget и QTreeWidgetItem. Каждый элемент в виджете дерева может иметь собственные дочерние элементы и может отображать несколько столбцов информации. Виджеты дерева создаются так же, как и любой другой виджет:
QTreeWidget *treeWidget = new QTreeWidget(this);
Прежде чем элементы могут быть добавлены в виджет дерева, необходимо установить количество столбцов. Например, мы могли бы определить два столбца и создать заголовок для предоставления меток вверху каждого столбца:
treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers); Самый простой способ установить метки для каждого раздела — предоставить список строк. Для более сложных заголовков можно создать элемент дерева, оформить его по своему усмотрению и использовать его в качестве заголовка виджета дерева.
Элементы верхнего уровня в виджете дерева создаются с виджетом дерева в качестве родительского виджета. Их можно вставлять в произвольном порядке, или можно убедиться, что они перечислены в определенном порядке, указав предыдущий элемент при создании каждого элемента:
QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));
QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities); Виджеты дерева обрабатывают элементы верхнего уровня несколько иначе, чем другие элементы, находящиеся глубже в дереве. Элементы могут быть удалены из верхнего уровня дерева, вызвав функцию виджета дерева takeTopLevelItem(), но элементы из нижних уровней удаляются, вызвав функцию родительского элемента takeChild(). Элементы вставляются на верхний уровень дерева с помощью функции insertTopLevelItem(). На нижних уровнях дерева используется функция родительского элемента insertChild().
Легко перемещать элементы между верхним уровнем и нижними уровнями в дереве. Нам просто нужно проверить, являются ли элементы элементами верхнего уровня или нет, и эта информация предоставляется функцией каждого элемента parent(). Например, мы можем удалить текущий элемент в виджете дерева независимо от его местоположения:
QTreeWidgetItem *parent = currentItem->parent();
int index;
if (parent) {
index = parent->indexOfChild(treeWidget->currentItem());
delete parent->takeChild(index);
} else {
index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
delete treeWidget->takeTopLevelItem(index);
} Вставка элемента в другое место в виджете дерева следует той же схеме:
QTreeWidgetItem *parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
else
newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem()); Табличные виджеты
Таблицы элементов, похожие на те, что встречаются в приложениях электронных таблиц, создаются с помощью QTableWidget и QTableWidgetItem. Они предоставляют виджет таблицы со скроллингом, заголовками и элементами для использования в нём.
Таблицы могут быть созданы с заданным количеством строк и столбцов, или они могут быть добавлены в неразмеренную таблицу по мере необходимости.
QTableWidget *tableWidget;
tableWidget = new QTableWidget(12, 3, this); Элементы создаются вне таблицы перед добавлением в таблицу в нужном месте:
QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
pow(row, column+1)));
tableWidget->setItem(row, column, newItem); Горизонтальные и вертикальные заголовки могут быть добавлены в таблицу путём создания элементов вне таблицы и использования их в качестве заголовков:
QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem); Обратите внимание, что строки и столбцы в таблице начинаются с нуля.
Общие возможности
Существует ряд функций, основанных на элементах, общих для каждого из удобных классов, которые доступны через одни и те же интерфейсы в каждом классе. Мы представляем их в следующих разделах с некоторыми примерами для разных виджетов. Обратитесь к списку Классы Модель/Представление для каждого из виджетов для получения более подробной информации об использовании каждой функции.
Скрытые элементы
Иногда полезно иметь возможность скрывать элементы в виджете представления элементов, вместо того, чтобы удалять их. Элементы для всех вышеперечисленных виджетов могут быть скрыты и позже снова показаны. Вы можете определить, скрыт ли элемент, вызвав функцию isItemHidden(), а элементы могут быть скрыты с помощью setItemHidden().
Поскольку эта операция основана на элементе, та же функция доступна для всех трёх удобных классов.
Выделения
Способ выделения элементов контролируется режимом выделения виджета (QAbstractItemView::SelectionMode). Этот параметр управляет тем, может ли пользователь выделять один или несколько элементов, и, при выборе нескольких элементов, требуется ли, чтобы выбор был непрерывным диапазоном элементов. Режим выбора работает одинаково для всех вышеперечисленных виджетов.
Выделение одного элемента: В тех случаях, когда пользователю нужно выбрать один элемент из виджета, наиболее подходящим является режим SingleSelection. В этом режиме текущий элемент и выделенный элемент совпадают. |
|
| Выделение нескольких элементов: В этом режиме пользователь может переключать состояние выделения любого элемента в виджете, не изменяя существующего выделения, так же, как можно независимо переключать неисключительные флажки. | |
Расширенное выделение: Виджеты, которые часто требуют выбора нескольких смежных элементов, такие как те, что встречаются в электронных таблицах, требуют режима ExtendedSelection. В этом режиме можно выбрать непрерывные диапазоны элементов в виджете с помощью мыши и клавиатуры. Также можно создать сложные выделения, включающие множество элементов, которые не смежны с другими выбранными элементами в виджете, если используются клавиши модификатора. Если пользователь выбирает элемент без использования клавиши модификатора, существующее выделение сбрасывается. |
Выбранные элементы в виджете считываются с помощью функции selectedItems(), предоставляя список соответствующих элементов, по которым можно итерироваться. Например, мы можем найти сумму всех числовых значений в списке выбранных элементов с помощью следующего кода:
const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
int number = 0;
double total = 0;
for (QTableWidgetItem *item : selected) {
bool ok;
double value = item->text().toDouble(&ok);
if (ok && !item->text().isEmpty()) {
total += value;
number++;
}
} Обратите внимание, что для режима единичного выделения текущий элемент будет в выделении. В режимах множественного и расширенного выделения текущий элемент может не находиться в выделении, в зависимости от способа, которым пользователь сформировал выделение.
Поиск
Часто полезно иметь возможность находить элементы в виджете представления элементов, как для разработчика, так и для предоставления пользователю. Все три удобных класса представления элементов предоставляют общую функцию findItems() для обеспечения максимальной согласованности и простоты.
Элементы ищутся по тексту, который они содержат, в соответствии с критериями, указанными выбором значений из Qt::MatchFlags. Мы можем получить список соответствующих элементов с помощью функции findItems():
const QList<QTreeWidgetItem *> found = treeWidget->findItems(
itemText, Qt::MatchWildcard);
for (QTreeWidgetItem *item : found) {
item->setSelected(true);
// Show the item->text(0) for each item.
} Вышеприведенный код вызывает выделение элементов в виджете дерева, если они содержат текст, заданный в строке поиска. Эта схема также может быть использована в виджетах списка и таблицы.
Использование перетаскивания с помощью представлений элементов
Инфраструктура перетаскивания Qt полностью поддерживается моделью/представлением. Элементы в списках, таблицах и деревьях могут быть перетаскиваются в пределах представлений, а данные могут быть импортированы и экспортированы в виде данных MIME.
Стандартные представления автоматически поддерживают внутреннее перетаскивание, где элементы перемещаются, чтобы изменить порядок их отображения. По умолчанию перетаскивание для этих представлений не включено, потому что они настроены для наиболее простых и распространённых случаев использования. Чтобы разрешить перетаскивание элементов, необходимо включить определённые свойства представления, и сами элементы также должны позволить это перетаскивание.
Требования к модели, которая позволяет экспортировать элементы только из представления и не позволяет помещать данные в него, меньше, чем для полностью включённой модели перетаскивания.
См. также Справочник по наследованию от моделей для получения дополнительной информации о включении поддержки перетаскивания в новые модели.
Использование удобных представлений
Каждый тип элемента, используемый с QListWidget, QTableWidget и QTreeWidget, настроен по умолчанию на использование другого набора флагов. Например, каждый QListWidgetItem или QTreeWidgetItem изначально включён, может быть отмечен, выделен и может быть использован в качестве источника операции перетаскивания; каждый QTableWidgetItem также может быть отредактирован и использован в качестве цели операции перетаскивания.
Хотя все стандартные элементы имеют один или оба флага, установленные для перетаскивания, вам обычно необходимо установить различные свойства в самом представлении, чтобы воспользоваться встроенной поддержкой перетаскивания:
- Чтобы включить перетаскивание элементов, установите свойство представления dragEnabled в
true. - Чтобы разрешить пользователю помещать внутренние или внешние элементы в представление, установите свойство viewport() представления acceptDrops в
true. - Чтобы показать пользователю, куда будет помещен перемещаемый элемент при падении, установите свойство представления showDropIndicator. Это предоставляет пользователю постоянно обновляемую информацию о размещении элемента в представлении.
Например, мы можем включить перетаскивание в виджете списка с помощью следующих строк кода:
QListWidget *listWidget = new QListWidget(this); listWidget->setSelectionMode(QAbstractItemView::SingleSelection); listWidget->setDragEnabled(true); listWidget->viewport()->setAcceptDrops(true); listWidget->setDropIndicatorShown(true);
Результат — виджет списка, который позволяет копировать элементы в пределах представления, а также позволяет пользователю перетаскивать элементы между представлениями, содержащими тот же тип данных. В обоих случаях элементы копируются, а не перемещаются.
Чтобы разрешить пользователю перемещать элементы в пределах представления, мы должны установить dragDropMode виджета списка:
listWidget->setDragDropMode(QAbstractItemView::InternalMove);
Использование классов модель/представление
Настройка представления для перетаскивания следует той же схеме, что и при использовании удобных представлений. Например, QListView можно настроить так же, как и QListWidget:
QListView *listView = new QListView(this); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); listView->setDragEnabled(true); listView->setAcceptDrops(true); listView->setDropIndicatorShown(true);
Поскольку доступ к данным, отображаемым представлением, контролируется моделью, модель, используемая для поддержки операций перетаскивания. Действия, поддерживаемые моделью, могут быть указаны путём переопределения функции QAbstractItemModel::supportedDropActions(). Например, операции копирования и перемещения включаются с помощью следующего кода:
Qt::DropActions DragDropListModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
} Хотя можно указать любую комбинацию значений из Qt::DropActions, модель должна быть написана для поддержки этих значений. Например, для правильного использования Qt::MoveAction с моделью списка, модель должна предоставить реализацию QAbstractItemModel::removeRows(), либо напрямую, либо унаследовав реализацию от базового класса.
Включение перетаскивания для элементов
Модели указывают представлениям, какие элементы можно перетаскивать, а какие будут принимать падения, переопределяя функцию QAbstractItemModel::flags() для обеспечения соответствующих флагов.
Например, модель, которая предоставляет простой список на основе QAbstractListModel, может включить перетаскивание для каждого из элементов, убедившись, что возвращаемые флаги содержат значения Qt::ItemIsDragEnabled и Qt::ItemIsDropEnabled:
Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QStringListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
} Обратите внимание, что элементы могут быть помещены на верхний уровень модели, но перетаскивание включено только для допустимых элементов.
В приведенном выше коде, поскольку модель получена от QStringListModel, мы получаем набор флагов по умолчанию, вызывая его реализацию функции flags().
Кодирование экспортируемых данных
Когда элементы данных экспортируются из модели в операции перетаскивания, они кодируются в соответствующий формат, соответствующий одному или нескольким типам MIME. Модели объявляют типы MIME, которые они могут использовать для предоставления элементов, переопределяя функцию QAbstractItemModel::mimeTypes(), возвращая список стандартных типов MIME.
Например, модель, которая предоставляет только обычный текст, будет иметь следующую реализацию:
QStringList DragDropListModel::mimeTypes() const
{
QStringList types;
types << "application/vnd.text.list";
return types;
} Модель также должна предоставлять код для кодирования данных в объявленном формате. Это достигается путем повторной реализации функции QAbstractItemModel::mimeData() для предоставления объекта QMimeData, как и при любой другой операции перетаскивания.
Следующий код демонстрирует, как каждый элемент данных, соответствующий заданному списку индексов, кодируется как обычный текст и хранится в объекте QMimeData.
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
} Поскольку функции предоставляется список индексов модели, этот подход достаточно универсален для использования как в иерархических, так и в неиерархических моделях.
Обратите внимание, что пользовательские типы данных должны быть объявлены как метаобъекты, и для них должны быть реализованы операторы потоков. Подробности см. в описании класса QMetaObject.
Вставка опущенных данных в модель
Способ, которым любая модель обрабатывает опущенные данные, зависит как от её типа (список, таблица или дерево), так и от способа представления её содержимого пользователю. В целом, подход к обработке опущенных данных должен наилучшим образом соответствовать внутренней структуре данных модели.
Разные типы моделей обрабатывают опущенные данные по-разному. Модели списков и таблиц предоставляют только плоскую структуру, в которой хранятся элементы данных. В результате они могут вставлять новые строки (и столбцы), когда данные опускаются на существующий элемент в представлении, или они могут перезаписывать содержимое элемента в модели, используя часть предоставленных данных. Модели деревьев часто могут добавлять дочерние элементы, содержащие новые данные, в свои внутренние структуры данных, и поэтому будут вести себя более предсказуемо с точки зрения пользователя.
Обработка опущенных данных выполняется при повторной реализации модели функции QAbstractItemModel::dropMimeData(). Например, модель, которая обрабатывает простой список строк, может предоставить реализацию, которая обрабатывает данные, опущенные на существующие элементы отдельно от данных, опущенных в верхний уровень модели (т. е. на недействительный элемент).
Модели могут запретить опускание на определенные элементы или, в зависимости от опущенных данных, путем повторной реализации QAbstractItemModel::canDropMimeData().
Модель сначала должна убедиться, что операция должна быть выполнена, предоставленные данные находятся в формате, который может быть использован, и что их местоположение в модели является допустимым:
bool DragDropListModel::canDropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(parent);
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction)
return true; Простая модель списка строк с одним столбцом может указывать на ошибку, если предоставленные данные не являются обычным текстом или если номер столбца, заданный для опускания, недействителен.
Данные, которые должны быть вставлены в модель, обрабатываются по-разному, в зависимости от того, опускаются ли они на существующий элемент или нет. В этом простом примере мы хотим разрешить опускание между существующими элементами, перед первым элементом в списке и после последнего.
Когда происходит опускание, индекс модели, соответствующий родительскому элементу, либо будет действительным, указывая на то, что опускание произошло на элементе, либо он будет недействительным, указывая на то, что опускание произошло где-то в представлении, что соответствует верхнему уровню модели.
int beginRow;
if (row != -1)
beginRow = row; Сначала мы проверяем номер строки, чтобы определить, можем ли мы использовать его для вставки элементов в модель, независимо от того, является ли родительский индекс действительным или нет.
else if (parent.isValid())
beginRow = parent.row(); Если родительский индекс модели действителен, опускание произошло на элементе. В этой простой модели списка мы выясним номер строки элемента и воспользуемся этим значением для вставки опущенных элементов в верхний уровень модели.
else
beginRow = rowCount(QModelIndex()); Когда опускание происходит в другом месте представления, а номер строки неприменим, мы добавляем элементы в верхний уровень модели.
В иерархических моделях, когда происходит опускание на элемент, было бы лучше вставить новые элементы в модель как потомков этого элемента. В данном примере модель имеет только один уровень, поэтому этот подход не подходит.
Декодирование импортированных данных
Каждая реализация функции dropMimeData() также должна декодировать данные и вставить их в внутреннюю структуру данных модели.
Для простой модели списка строк закодированные элементы могут быть декодированы и введены в QStringList:
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
} Затем строки могут быть вставлены во внутреннюю структуру данных. Для согласованности это можно сделать через собственный интерфейс модели:
insertRows(beginRow, rows, QModelIndex());
for (const QString &text : qAsConst(newItems)) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
} Обратите внимание, что модели, как правило, потребуется предоставить реализации функций QAbstractItemModel::insertRows() и QAbstractItemModel::setData().
Прокси-модели
В рамках модели/представления данные, предоставляемые одной моделью, могут быть использованы любым количеством представлений, и каждое из них может представлять ту же информацию совершенно по-разному. Пользовательские представления и делегаты являются эффективными способами предоставления совершенно разных представлений одних и тех же данных. Однако приложениям часто необходимо предоставлять обычные представления на обработанных версиях тех же данных, например, отсортированные по-разному представления списка элементов.
Хотя кажется уместным выполнять операции сортировки и фильтрации как внутренние функции представлений, этот подход не позволяет нескольким представлениям совместно использовать результаты таких потенциально дорогостоящих операций. Альтернативный подход, включающий сортировку в самой модели, приводит к аналогичной проблеме, где каждое представление должно отображать элементы данных, организованные в соответствии с последней операцией обработки.
Для решения этой проблемы в рамках модели/представления используются прокси-модели для управления информацией, передаваемой между отдельными моделями и представлениями. Прокси-модели — это компоненты, которые ведут себя как обычные модели с точки зрения представления и обращаются к данным из исходных моделей от имени этого представления. Сигналы и слоты, используемые в рамках модели/представления, гарантируют, что каждое представление обновляется должным образом, независимо от того, сколько прокси-моделей находится между ним и исходной моделью.
Использование прокси-моделей
Прокси-модели могут быть вставлены между существующей моделью и любым количеством представлений. Qt поставляется со стандартной прокси-моделью QSortFilterProxyModel, которая обычно создается и используется напрямую, но также может быть унаследована для предоставления пользовательского поведения фильтрации и сортировки. Класс QSortFilterProxyModel можно использовать следующим образом:
QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
filterModel->setSourceModel(stringListModel);
QListView *filteredView = new QListView;
filteredView->setModel(filterModel); Поскольку прокси-модели наследуют от QAbstractItemModel, они могут подключаться к любому типу представления и могут использоваться несколькими представлениями. Они также могут использоваться для обработки информации, полученной от других прокси-моделей в конвейерной организации.
Класс QSortFilterProxyModel предназначен для создания и прямого использования в приложениях. Более специализированные прокси-модели можно создавать, наследуя от этого класса и реализуя необходимые операции сравнения.
Настройка прокси-моделей
В общем случае обработка в прокси-модели включает отображение каждого элемента данных из его исходного расположения в исходной модели в другое расположение в прокси-модели. В некоторых моделях некоторые элементы могут не иметь соответствующего расположения в прокси-модели; такие модели являются фильтрующими прокси-моделями. Представления обращаются к элементам, используя индексы модели, предоставляемые прокси-моделью, и они не содержат информации об исходной модели или расположении исходных элементов в этой модели.
QSortFilterProxyModel позволяет фильтровать данные из исходной модели перед их предоставлением представлениям, а также позволяет предоставить представлениям данные исходной модели в виде предварительно отсортированных данных.
Настраиваемые модели фильтрации
Класс QSortFilterProxyModel предоставляет довольно универсальную модель фильтрации, которую можно использовать в различных распространенных ситуациях. Для продвинутых пользователей QSortFilterProxyModel можно наследовать, предоставляя механизм для реализации пользовательских фильтров.
Подклассы QSortFilterProxyModel могут повторно реализовать две виртуальные функции, которые вызываются всякий раз, когда запрашивается или используется индекс модели из прокси-модели:
- filterAcceptsColumn() используется для фильтрации определенных столбцов из части исходной модели.
- filterAcceptsRow() используется для фильтрации определенных строк из части исходной модели.
По умолчанию реализация этих функций в QSortFilterProxyModel возвращает true, чтобы гарантировать, что все элементы передаются представлениям; повторная реализация этих функций должна возвращать false для фильтрации отдельных строк и столбцов.
Настраиваемые модели сортировки
Экземпляры QSortFilterProxyModel используют функцию std::stable_sort() для настройки отображений между элементами исходной модели и элементами прокси-модели, что позволяет отобразить отсортированную иерархию элементов в представлениях без изменения структуры исходной модели. Для обеспечения пользовательского поведения сортировки переопределите функцию lessThan() для выполнения пользовательских сравнений.
Справочник по наследованию от моделей
Подклассы моделей должны предоставить реализации многих виртуальных функций, определенных в базовом классе QAbstractItemModel. Количество этих функций, которые необходимо реализовать, зависит от типа модели — предоставляет ли она представлениям простой список, таблицу или сложную иерархию элементов. Модели, наследующие от QAbstractListModel и QAbstractTableModel, могут использовать стандартные реализации функций, предоставляемых этими классами. Модели, которые отображают элементы данных в виде древовидных структур, должны предоставить реализации многих виртуальных функций в QAbstractItemModel.
Функции, которые необходимо реализовать в подклассе модели, можно разделить на три группы:
- Обработка данных элементов: все модели должны реализовывать функции для того, чтобы представления и делегаты могли запрашивать размеры модели, проверять элементы и получать данные.
- Навигация и создание индексов: иерархическим моделям нужно предоставить функции, которые представления могут вызывать для навигации по древовидным структурам, которые они отображают, и получения индексов модели для элементов.
- Поддержка перетаскивания и обработки типов MIME: модели наследуют функции, которые управляют тем, как выполняются внутренние и внешние операции перетаскивания. Эти функции позволяют описывать элементы данных с точки зрения типов MIME, которые могут понимать другие компоненты и приложения.
Обработка данных элементов
Модели могут предоставлять различные уровни доступа к предоставляемым ими данным: они могут быть простыми компонентами только для чтения, некоторые модели могут поддерживать операции изменения размера, а другие могут позволять редактировать элементы.
Только чтение
Для предоставления только чтения к данным, предоставляемым моделью, следующие функции должны быть реализованы в подклассе модели:
| flags() | Используется другими компонентами для получения информации о каждом элементе, предоставленном моделью. Во многих моделях комбинация флагов должна включать Qt::ItemIsEnabled и Qt::ItemIsSelectable. |
| data() | Используется для предоставления данных элементам представлениям и делегатам. Как правило, модели должны предоставлять данные только для Qt::DisplayRole и любых пользовательских ролей, специфичных для приложения, но также рекомендуется предоставлять данные для Qt::ToolTipRole, Qt::AccessibleTextRole и Qt::AccessibleDescriptionRole. См. документацию перечисления Qt::ItemDataRole для получения информации о типах, связанных с каждой ролью. |
| headerData() | Предоставляет представлениям информацию для отображения в заголовках. Информация извлекается только представлениями, которые могут отображать информацию о заголовках. |
| rowCount() | Предоставляет количество строк данных, предоставляемых моделью. |
Эти четыре функции должны быть реализованы во всех типах моделей, включая списочные модели (QAbstractListModel подклассы) и табличные модели (QAbstractTableModel подклассы).
Кроме того, следующие функции обязательно должны быть реализованы в прямых подклассах QAbstractTableModel и QAbstractItemModel:
| columnCount() | Предоставляет количество столбцов данных, предоставляемых моделью. Списочные модели не предоставляют эту функцию, поскольку она уже реализована в QAbstractListModel. |
Редактируемые элементы
Редактируемые модели позволяют изменять элементы данных и могут также предоставлять функции для вставки и удаления строк и столбцов. Для включения редактирования необходимо правильно реализовать следующие функции:
| flags() | Должна возвращать соответствующую комбинацию флагов для каждого элемента. В частности, возвращаемое значение этой функции должно включать Qt::ItemIsEditable в дополнение к значениям, применяемым к элементам в модели только для чтения. |
| setData() | Используется для изменения элемента данных, связанного с указанным индексом модели. Чтобы иметь возможность принимать пользовательский ввод от элементов пользовательского интерфейса, эта функция должна обрабатывать данные, связанные с Qt::EditRole. Реализация также может принимать данные, связанные со многими различными типами ролей, указанными в Qt::ItemDataRole. После изменения элемента данных модели должны излучать сигнал dataChanged(), чтобы уведомить другие компоненты об изменении. |
| setHeaderData() | Используется для изменения горизонтальной и вертикальной информации заголовка. После изменения элемента данных модели должны излучать сигнал headerDataChanged(), чтобы уведомить другие компоненты об изменении. |
Изменяемые модели
Все типы моделей могут поддерживать вставку и удаление строк. Табличные и иерархические модели также могут поддерживать вставку и удаление столбцов. Важно уведомлять другие компоненты об изменениях размеров модели как до, так и после их возникновения. В результате для изменения размера модели могут быть реализованы следующие функции, но реализации должны гарантировать, что вызываются соответствующие функции для уведомления присоединенных представлений и делегатов:
| insertRows() | Используется для добавления новых строк и элементов данных во все типы моделей. Реализации должны вызывать beginInsertRows() перед вставкой новых строк в любые базовые структуры данных и вызывать endInsertRows() непосредственно после. |
| removeRows() | Используется для удаления строк и содержащихся в них элементов данных из всех типов моделей. Реализации должны вызывать beginRemoveRows() перед удалением строк из любых базовых структур данных и вызывать endRemoveRows() непосредственно после. |
| insertColumns() | Используется для добавления новых столбцов и элементов данных в табличные и иерархические модели. Реализации должны вызывать beginInsertColumns() перед вставкой новых столбцов в любые базовые структуры данных и вызывать endInsertColumns() непосредственно после. |
| removeColumns() | Используется для удаления столбцов и содержащихся в них элементов данных из табличных и иерархических моделей. Реализации должны вызывать beginRemoveColumns() перед удалением столбцов из любых базовых структур данных и вызывать endRemoveColumns() непосредственно после. |
В целом, эти функции должны возвращать true, если операция выполнена успешно. Однако могут быть случаи, когда операция выполняется только частично; например, если вставлено меньше указанного количества строк. В таких случаях модель должна возвращать false, чтобы сообщить любым присоединённым компонентам о неудаче, что позволит им обработать ситуацию.
Сигналы, излучаемые функциями, вызываемыми в реализациях API изменения размера, дают присоединённым компонентам возможность предпринять действия перед тем, как какие-либо данные станут недоступными. Использование функций begin и end для операций вставки и удаления также позволяет модели правильно управлять постоянными индексами модели.
Обычно функции begin и end могут сообщать другим компонентам об изменениях в базовой структуре модели. Для более сложных изменений структуры модели, возможно, включающих внутреннюю реорганизацию, сортировку данных или любые другие изменения структуры, необходимо выполнить следующую последовательность действий:
- Вызвать сигнал layoutAboutToBeChanged()
- Обновить внутренние данные, представляющие структуру модели.
- Обновить постоянные индексы с помощью changePersistentIndexList()
- Вызвать сигнал layoutChanged().
Эта последовательность может быть использована для любого обновления структуры вместо более высокоуровневых и удобных защищённых методов. Например, если модель из двух миллионов строк должна удалить все строки с нечётными номерами, то это 1 миллион разрозненных диапазонов по 1 элементу каждый. Можно было бы использовать beginRemoveRows и endRemoveRows по миллиону раз, но это очевидно неэффективно. Вместо этого это можно сигнализировать как одно изменение макета, которое обновит все необходимые постоянные индексы сразу.
Ленивая загрузка данных модели
Ленивая загрузка данных модели фактически позволяет отложить запросы информации о модели до тех пор, пока она не понадобится представлениям.
Некоторые модели должны получать данные из удалённых источников или должны выполнять длительные операции для получения информации о способе организации данных. Поскольку представления обычно запрашивают как можно больше информации для точного отображения данных модели, может быть полезно ограничить количество возвращаемой им информации для уменьшения ненужных последующих запросов данных.
В иерархических моделях, где поиск количества дочерних элементов заданного элемента является дорогостоящей операцией, полезно убедиться, что реализация rowCount() модели вызывается только при необходимости. В таких случаях функция hasChildren() может быть переопределена для предоставления экономичного способа для представлений проверить наличие дочерних элементов и, в случае QTreeView, нарисовать соответствующее украшение для родительского элемента.
Независимо от того, возвращает ли переопределение hasChildren() true или false, представлению может не понадобиться вызывать rowCount(), чтобы узнать, сколько дочерних элементов присутствует. Например, QTreeView не нуждается в знании количества дочерних элементов, если родительский элемент не был развернут для их отображения.
Если известно, что у многих элементов есть дочерние элементы, переопределение hasChildren() на безусловное возвращение true иногда является полезным подходом. Это гарантирует, что каждый элемент может быть впоследствии проверен на наличие дочерних элементов, делая начальную загрузку данных модели максимально быстрой. Единственный недостаток заключается в том, что элементы без дочерних элементов могут быть отображены неправильно в некоторых представлениях до тех пор, пока пользователь не попытается просмотреть несуществующие дочерние элементы.
Навигация и создание индексов модели
Иерархические модели должны предоставлять функции, которые представления могут вызывать для навигации по древовидным структурам, которые они представляют, и получения индексов модели для элементов.
Родители и дочерние элементы
Поскольку структура, представленная представлениям, определяется базовой структурой данных, каждый подкласс модели должен создавать свои собственные индексы модели, реализуя следующие функции:
| index() | При заданном индексе модели для родительского элемента эта функция позволяет представлениям и делегатам получить доступ к дочерним элементам этого элемента. Если не найдено ни одного допустимого дочернего элемента — соответствующего заданной строке, столбцу и родительскому индексу модели, функция должна вернуть QModelIndex(), что является недопустимым индексом модели. |
| parent() | Предоставляет индекс модели, соответствующий родителю любого данного дочернего элемента. Если указанный индекс модели соответствует элементу верхнего уровня в модели или если в модели нет допустимого родительского элемента, функция должна возвращать недопустимый индекс модели, созданный с помощью конструктора QModelIndex(). |
Обе функции выше используют функцию-фабрику createIndex() для генерации индексов, которые могут использовать другие компоненты. Для моделей обычно требуется предоставить какой-то уникальный идентификатор этой функции, чтобы модель могла повторно связать индекс с соответствующим элементом позже.
Поддержка перетаскивания и обработка типов MIME
Классы модели/представления поддерживают операции перетаскивания, предоставляя поведение по умолчанию, достаточное для многих приложений. Однако также можно настроить способ кодирования элементов во время операций перетаскивания, копируются ли они или перемещаются по умолчанию, и как они вставляются в существующие модели.
Кроме того, удобные классы представления реализуют специализированное поведение, которое должно соответствовать ожидаемому поведению существующих разработчиков. Раздел Удобные представления предоставляет обзор этого поведения.
Данные MIME
По умолчанию встроенные модели и представления используют внутренний тип MIME (application/x-qabstractitemmodeldatalist) для передачи информации об индексах модели. Это определяет данные для списка элементов, содержащих номера строки и столбца каждого элемента, а также информацию о ролях, которые поддерживает каждый элемент.
Данные, закодированные с помощью этого типа MIME, можно получить, вызвав QAbstractItemModel::mimeData() с QModelIndexList, содержащей элементы, подлежащие сериализации.
При реализации поддержки перетаскивания в пользовательской модели можно экспортировать элементы данных в специализированных форматах, переопределяя следующую функцию:
| mimeData() | Эту функцию можно переопределить, чтобы она возвращала данные в форматах, отличных от внутреннего типа MIME application/x-qabstractitemmodeldatalist по умолчанию.Подклассы могут получить стандартный объект QMimeData из базового класса и добавить данные в дополнительные форматы. |
Для многих моделей полезно предоставить содержимое элементов в общем формате, представленном типами MIME, такими как text/plain и image/png. Обратите внимание, что изображения, цвета и HTML-документы можно легко добавить в объект QMimeData с помощью функций QMimeData::setImageData(), QMimeData::setColorData() и QMimeData::setHtml().
Прием опущенных данных
При выполнении операции перетаскивания над представлением к базовой модели обращаются для определения поддерживаемых типов операций и типов MIME, которые она может принять. Эта информация предоставляется функциями QAbstractItemModel::supportedDropActions() и QAbstractItemModel::mimeTypes(). Модели, которые не переопределяют реализации, предоставляемые QAbstractItemModel, поддерживают операции копирования и стандартный внутренний тип MIME для элементов.
При опускании сериализованных данных элемента на представление данные вставляются в текущую модель с помощью её реализации QAbstractItemModel::dropMimeData(). Реализация по умолчанию этой функции никогда не перезаписывает данные в модели; вместо этого она пытается вставить элементы данных либо как элементы-побратимы элемента, либо как дочерние элементы этого элемента.
Для использования реализации по умолчанию QAbstractItemModel для встроенного типа MIME новые модели должны предоставить переопределения следующих функций:
| insertRows() | Эти функции позволяют модели автоматически вставлять новые данные с использованием существующей реализации, предоставленной QAbstractItemModel::dropMimeData(). |
| insertColumns() | |
| setData() | Позволяет заполнить новые строки и столбцы элементами. |
| setItemData() | Эта функция обеспечивает более эффективную поддержку заполнения новых элементов. |
Для поддержки других форм данных эти функции должны быть переопределены:
| supportedDropActions() | Используется для возврата комбинации действий перетаскивания, указывающей типы операций перетаскивания, которые принимает модель. |
| mimeTypes() | Используется для возврата списка типов MIME, которые могут быть декодированы и обработаны моделью. Как правило, типы MIME, поддерживаемые для ввода в модель, совпадают с теми, которые она может использовать при кодировании данных для использования внешними компонентами. |
| dropMimeData() | Выполняет фактическое декодирование данных, передаваемых операциями перетаскивания, определяет, где в модели они будут установлены, и вставляет новые строки и столбцы при необходимости. Реализация этой функции в подклассах зависит от требований к данным, предоставляемым каждой моделью. |
Если реализация функции dropMimeData() изменяет размер модели путем вставки или удаления строк или столбцов, или если элементы данных изменяются, необходимо позаботиться о том, чтобы были выпущены все соответствующие сигналы. Может быть полезно просто вызвать переопределения других функций в подклассе, таких как setData(), insertRows() и insertColumns(), чтобы обеспечить согласованность поведения модели.
Для правильной работы операций перетаскивания важно переопределить следующие функции, удаляющие данные из модели:
Дополнительную информацию о перетаскивании с помощью представлений элементов см. в разделе Использование перетаскивания с элементами-представлениями.
Удобные представления
Удобные представления (QListWidget, QTableWidget и QTreeWidget) переопределяют стандартное поведение перетаскивания, чтобы обеспечить менее гибкое, но более естественное поведение, подходящее для многих приложений. Например, поскольку в QTableWidget чаще происходит опускание данных в ячейки, заменяя существующее содержимое передаваемыми данными, базовая модель установит данные целевых элементов, а не вставит новые строки и столбцы в модель. Дополнительную информацию о перетаскивании в удобных представлениях см. в разделе Использование перетаскивания с элементами-представлениями.
Оптимизация производительности для больших объемов данных
Функция canFetchMore() проверяет, есть ли у родительского элемента больше доступных данных, и возвращает соответственно true или false. Функция fetchMore() извлекает данные на основе указанного родительского элемента. Обе эти функции можно объединить, например, в запросе к базе данных, предполагающем поэтапное получение данных для заполнения QAbstractItemModel. Мы переопределяем canFetchMore(), чтобы указать, есть ли больше данных для извлечения, и fetchMore(), чтобы заполнить модель по мере необходимости.
Другим примером являются динамически заполненные древовидные модели, где мы переопределяем fetchMore() при раскрытии ветви в древовидной модели.
Если ваше переопределение fetchMore() добавляет строки в модель, необходимо вызвать beginInsertRows() и endInsertRows(). Кроме того, как canFetchMore(), так и fetchMore() должны быть переопределены, поскольку их реализация по умолчанию возвращает false и ничего не делает.
Классы модели/представления
Эти классы используют шаблон проектирования модель/представление, в котором базовые данные (в модели) хранятся отдельно от способа представления и обработки данных пользователем (в представлении).
END_OF_DOCUMENT_MARKERИспользуется для отображения и редактирования элементов данных из модели |
|
Абстрактный интерфейс для классов моделей элементов |
|
Базовые возможности для классов представлений элементов |
|
Абстрактная модель, которую можно наследовать для создания одномерных моделей списков |
|
Базовый класс для моделей прокси-элементов, которые могут выполнять сортировку, фильтрацию или другие задачи обработки данных |
|
Абстрактная модель, которую можно наследовать для создания моделей таблиц |
|
Реализация модели/представления столбца |
|
Проксирует несколько исходных моделей, конкатенируя их строки |
|
Сопоставление части модели данных с виджетами |
|
Модель данных для локальной файловой системы |
|
Строка или столбец заголовка для представлений элементов |
|
Проксирует исходную модель без изменений |
|
Средства отображения и редактирования элементов данных из модели |
|
Позволяет создавать создатели редакторов элементов без наследования QItemEditorCreatorBase |
|
Абстрактный базовый класс, который необходимо наследовать при реализации новых создателей редакторов элементов |
|
Виджеты для редактирования данных элементов в представлениях и делегатах |
|
Управляет информацией об выбранных элементах в модели |
|
Отслеживает выбранные элементы представления |
|
Управляет информацией о диапазоне выбранных элементов в модели |
|
Список или представление значков на модели |
|
Виджет списка на основе элементов |
|
Элемент для использования с классом представления элементов QListWidget |
|
Используется для поиска данных в модели данных |
|
Содержит роль и данные, связанные с этой ролью |
|
Диапазон QModelRoleData объектов |
|
Используется для поиска данных в модели данных |
|
Поддержка сортировки и фильтрации данных, передаваемых между другой моделью и представлением |
|
Элемент для использования с классом QStandardItemModel |
|
Возможность регистрации виджетов без наследования QItemEditorCreatorBase |
|
Общедоступная модель для хранения пользовательских данных |
|
Модель, которая предоставляет строки для представлений |
|
Средства отображения и редактирования элементов данных из модели |
|
Стандартная реализация модели/представления таблицы |
|
Представление таблицы на основе элементов с предустановленной моделью |
|
Элемент для использования с классом QTableWidget |
|
Способ взаимодействия с выделением в модели без использования индексов модели и модели выделения |
|
Стандартная реализация модели/представления дерева |
|
Представление дерева, использующее предопределенную модель дерева |
|
Элемент для использования с удобным классом QTreeWidget |
|
Способ итерирования по элементам в экземпляре QTreeWidget |
Связанные примеры
См. такжеПример головоломки представлений элементов.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/model-view-programming.html