Программирование по модели/представлению
Введение в программирование по модели/представлению
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 *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex); В этом случае мы настраиваем модель QFileSystemModel по умолчанию, получаем индекс родительского элемента с помощью конкретной реализации index(), предоставляемой этой моделью, и подсчитываем количество строк в модели с помощью функции rowCount().
Для простоты мы интересуемся только элементами в первом столбце модели. Мы последовательно рассматриваем каждую строку, получая индекс модели для первого элемента в каждой строке и читаем данные, сохраненные для этого элемента в модели.
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());
Второе представление получает модель выбора для первого представления. Оба представления теперь работают с одной моделью выбора, сохраняя синхронизацию данных и выбранных элементов.
В приведенном выше примере использовались два представления одного и того же типа для отображения данных одной и той же модели. Однако, если используются два разных типа представлений, выбранные элементы могут быть представлены совершенно по-разному в каждом представлении; например, непрерывный выбор в представлении таблицы может быть представлен как фрагментированный набор выделенных элементов в представлении дерева.
Классы делегатов
Концепции
В отличие от шаблона Model-View-Controller, в проектировании модель/представление не включается полностью отдельный компонент для управления взаимодействием с пользователем. Как правило, представление отвечает за отображение данных модели пользователю и за обработку пользовательского ввода. Для обеспечения гибкости в способе получения этого ввода взаимодействие выполняется делегатами. Эти компоненты предоставляют возможности ввода и также отвечают за отображение отдельных элементов в некоторых представлениях. Стандартный интерфейс управления делегатами определен в классе QAbstractItemDelegate.
Ожидается, что делегаты смогут отображать свое содержимое сами, реализуя функции paint() и sizeHint(). Однако простые делегаты на основе виджетов могут подклассифицировать QItemDelegate вместо QAbstractItemDelegate и воспользоваться стандартными реализациями этих функций.
Редакторы для делегатов могут быть реализованы либо с использованием виджетов для управления процессом редактирования, либо путем непосредственной обработки событий. Первый подход рассматривается позже в этом разделе, и он также показан в примере Spin Box Delegate.
Пример Pixelator показывает, как создать пользовательский делегат, выполняющий специализированное отображение для представления таблицы.
Использование существующего делегата
Стандартные представления, предоставляемые Qt, используют экземпляры QItemDelegate для предоставления средств редактирования. Эта стандартная реализация интерфейса делегата отображает элементы в обычном стиле для каждого из стандартных представлений: QListView, QTableView и QTreeView.
Все стандартные роли обрабатываются стандартным делегатом, используемым стандартными представлениями. Способ их интерпретации описан в документации QItemDelegate.
Делегат, используемый представлением, возвращается функцией itemDelegate(). Функция setItemDelegate() позволяет установить пользовательского делегата для стандартного представления, и ее необходимо использовать при установке делегата для пользовательского представления.
Простой делегат
Реализованный здесь делегат использует QSpinBox для предоставления средств редактирования и предназначен в основном для использования с моделями, отображающими целые числа. Хотя мы настроили пользовательскую модель таблицы на основе целых чисел для этой цели, мы могли бы легко использовать QStandardItemModel вместо этого, так как пользовательский делегат управляет вводом данных. Мы создаем представление таблицы для отображения содержимого модели, и оно будет использовать пользовательского делегата для редактирования.
Мы подклассифицируем делегат от QStyledItemDelegate, потому что не хотим писать пользовательские функции отображения. Однако мы все равно должны предоставить функции для управления виджетом редактора:
class SpinBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = 0);
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);
} Поскольку представление управляет виджетами редактора для делегата, нам нужно обновить модель только с содержимым предоставленного редактора. В этом случае мы гарантируем, что поле ввода обновлено, и обновим модель со значением, которое оно содержит, используя указанный индекс.
Стандартный класс QItemDelegate уведомляет представление, когда редактирование завершено, испуская сигнал closeEditor(). Представление гарантирует, что виджет редактора закрыт и уничтожен. В этом примере мы предоставляем только простые средства редактирования, поэтому нам никогда не нужно испускать этот сигнал.
Все операции с данными выполняются через интерфейс, предоставленный QAbstractItemModel. Это делает делегата в основном независимым от типа данных, которые он обрабатывает, но некоторые предположения должны быть сделаны, чтобы использовать определенные типы виджетов редактора. В этом примере мы предположили, что модель всегда содержит целые значения, но мы все равно можем использовать этого делегата с разными типами моделей, потому что QVariant предоставляет разумные значения по умолчанию для неожиданных данных.
Обновление геометрии редактора
Делегат отвечает за управление геометрией редактора. Геометрия должна быть установлена при создании редактора и при изменении размера или положения элемента в представлении. К счастью, представление предоставляет всю необходимую геометрическую информацию внутри объекта параметров представления.
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
} В этом случае мы просто используем геометрическую информацию, предоставленную представлением в прямоугольнике элемента. Делегат, отображающий элементы с несколькими элементами, не будет использовать прямоугольник элемента напрямую. Он будет позиционировать редактор относительно других элементов в элементе.
Рекомендации по редактированию
После редактирования делегаты должны предоставить подсказки другим компонентам о результате процесса редактирования и предоставить подсказки, которые помогут в последующих операциях редактирования. Это достигается путем испускания сигнала closeEditor() с подходящей подсказкой. За это отвечает стандартный фильтр событий QItemDelegate, который мы установили на поле ввода при его создании.
Поведение поля ввода можно настроить для повышения удобства использования. В стандартном фильтре событий, предоставляемом QItemDelegate, если пользователь нажимает Возврат, чтобы подтвердить свой выбор в поле ввода, делегат сохраняет значение в модели и закрывает поле ввода. Мы можем изменить это поведение, установив собственный фильтр событий на поле ввода и предоставив подсказки по редактированию, которые соответствуют нашим потребностям; например, мы можем испустить 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(). Она возвращает несортированный список индексов модели, по которому мы можем итерироваться, если знаем, к какой модели они относятся:
QModelIndexList indexes = selectionModel->selectedIndexes();
QModelIndex index;
foreach(index, indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
} Вышеприведенный код использует удобный метод Qt foreach для итерации по элементам и их модификации, соответствующим индексам, возвращаемым моделью выбора.
Модель выбора отправляет сигналы, чтобы указывать изменения в выборе. Эти сигналы уведомляют другие компоненты об изменениях как в выборе в целом, так и в текущем выделенном элементе в модели элементов. Мы можем подключить сигнал selectionChanged() к слоту и проверить элементы в модели, которые были выделены или убраны из выделения при изменении выбора. Слот вызывается с двумя объектами QItemSelection: один содержит список индексов, соответствующих новым выделенным элементам; другой содержит индексы, соответствующие элементам, которые были убраны из выделения.
В следующем коде мы предоставляем слот, который получает сигнал selectionChanged(), заполняет выделенные элементы строкой и очищает содержимое убранных из выделения элементов.
void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndex index;
QModelIndexList items = selected.indexes();
foreach (index, items) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
foreach (index, items)
model->setData(index, "");
} Мы можем отслеживать текущий выделенный элемент, подключив сигнал 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. Описанная ниже модель предоставляет пользовательский конструктор именно для этой цели.
Пример модели только для чтения
Реализованная здесь модель — это простая, неиерархическая, модель данных только для чтения, основанная на стандартном классе 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(), предоставляя список соответствующих элементов, по которым можно выполнить итерацию. Например, мы можем найти сумму всех числовых значений в списке выделенных элементов с помощью следующего кода:
QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
QTableWidgetItem *item;
int number = 0;
double total = 0;
foreach (item, selected) {
bool ok;
double value = item->text().toDouble(&ok);
if (ok && !item->text().isEmpty()) {
total += value;
number++;
}
} Обратите внимание, что в режиме выбора одного элемента текущий элемент будет в выделении. В режимах множественного и расширенного выбора текущий элемент может не находиться в выделении, в зависимости от того, как пользователь сформировал выделение.
Поиск
Часто бывает полезно искать элементы в виджете представления элементов, как для разработчика, так и для предоставления пользователям. Все три удобных класса представления элементов предоставляют общую функцию findItems() для максимально возможной согласованности и простоты.
Элементы ищутся по тексту, который они содержат, в соответствии с критериями, указанными выбором значений из Qt::MatchFlags. Мы можем получить список соответствующих элементов с помощью функции findItems():
QTreeWidgetItem *item;
QList<QTreeWidgetItem *> found = treeWidget->findItems(
itemText, Qt::MatchWildcard);
foreach (item, found) {
treeWidget->setItemSelected(item, true);
// Show the item->text(0) for each item.
} Вышеприведенный код приводит к выделению элементов в виджете дерева, если они содержат текст, указанный в строке поиска. Этот шаблон также может использоваться в виджетах списка и таблицы.
Использование перетаскивания с представлением элементов
Инфраструктура перетаскивания Qt полностью поддерживается моделью/представлением. Элементы в списках, таблицах и деревьях могут быть перетаскиваются в представлениях, а данные могут импортироваться и экспортироваться в виде данных, закодированных в формате MIME.
Стандартные представления автоматически поддерживают внутреннее перетаскивание, где элементы перемещаются для изменения порядка их отображения. По умолчанию перетаскивание для этих представлений отключено, так как они настроены для наиболее простых и распространенных случаев использования. Чтобы разрешить перетаскивание элементов, необходимо включить определенные свойства представления, а также сами элементы должны допускать перетаскивание.
Требования к модели, которая позволяет только экспортировать элементы из представления и не позволяет опускать данные в него, меньше, чем для полностью включенной модели перетаскивания.
См. также Справочник по наследованию модели для получения дополнительной информации об включении поддержки перетаскивания в новые модели.
Использование удобных представлений
Каждый тип элемента, используемого с QListWidget, QTableWidget и QTreeWidget, настраивается по умолчанию на использование другого набора флагов. Например, каждый QListWidgetItem или QTreeWidgetItem изначально включен, имеет возможность выбора, может быть выделен и может использоваться в качестве источника операции перетаскивания; каждый QTableWidgetItem также может быть изменен и использоваться в качестве целевого объекта операции перетаскивания.
Хотя у всех стандартных элементов установлен один или оба флага для перетаскивания, вам обычно нужно задать различные свойства в самом представлении, чтобы воспользоваться встроенной поддержкой перетаскивания:
- Чтобы включить перетаскивание элементов, установите свойство dragEnabled представления в
true. - Чтобы разрешить пользователю опускать внутренние или внешние элементы в представлении, установите свойство viewport()'s 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);
foreach (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(). Например, модель, обрабатывающая простой список строк, может предоставить реализацию, которая обрабатывает данные, опущенные на существующие элементы отдельно от данных, опущенных в верхний уровень модели (т. е. на недействительный элемент).
END_OF_DOCUMENT_MARKERМодели могут запретить опускание на определённые элементы или в зависимости от переносимых данных, переопределяя 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());
foreach (const QString &text, 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 используют встроенную функцию Qt qStableSort() для настройки соответствий между элементами исходной модели и элементами прокси-модели, что позволяет отобразить отсортированную иерархию элементов для представлений без изменения структуры исходной модели. Чтобы обеспечить пользовательское поведение сортировки, переопределите функцию 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 1 миллион раз, но это было бы явно неэффективно. Вместо этого это может быть передано как единое изменение макета, которое обновит все необходимые постоянные индексы сразу.
Ленивая загрузка данных модели
Ленивая загрузка данных модели эффективно позволяет откладывать запросы о модели до тех пор, пока они фактически не понадобятся представлениям.
Некоторым моделям необходимо получать данные из удаленных источников или выполнять длительные операции для получения информации о структуре данных. Поскольку представления обычно запрашивают как можно больше информации для точного отображения данных модели, полезно ограничивать количество возвращаемой им информации, чтобы уменьшить ненужные последующие запросы данных.
В иерархических моделях, где поиск количества дочерних элементов заданного элемента является дорогостоящей операцией, полезно убедиться, что реализация 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 и ничего не делает.
Классы модели и представления
Эти классы используют паттерн проектирования модель/представление, в котором данные, лежащие в основе (в модели), хранятся отдельно от того, как пользователь видит и манипулирует данными (в представлении).
Используется для отображения и редактирования элементов данных из модели |
|
Абстрактный интерфейс для классов моделей элементов |
|
Основные возможности для классов представлений элементов |
|
Абстрактная модель, которую можно наследовать для создания одномерных моделей списков |
|
Базовый класс для прокси-моделей элементов, которые могут выполнять сортировку, фильтрацию или другие задачи обработки данных |
|
Абстрактная модель, которую можно наследовать для создания моделей таблиц |
|
Реализация модели/представления для представления столбцов |
|
Сопоставление части модели данных с виджетами |
|
Модель данных для локальной файловой системы |
|
Строка или столбец заголовка для представлений элементов |
|
Проксирует исходную модель без изменений |
|
Средства отображения и редактирования элементов данных из модели |
|
Предоставляет возможность создавать создатели редакторов элементов без наследования QItemEditorCreatorBase |
|
Абстрактный базовый класс, который необходимо наследовать при реализации новых создателей редакторов элементов |
|
Виджеты для редактирования данных элементов в представлениях и делегатах |
|
Управляет информацией о выделенных элементах в модели |
|
Отслеживает выделенные элементы представления |
|
Управляет информацией о диапазоне выделенных элементов в модели |
|
Список или представление значков на модели |
|
Список виджетов на основе элементов |
|
Элемент для использования с классом представления элементов QListWidget |
|
Используется для поиска данных в модели данных |
|
Используется для поиска данных в модели данных |
|
Поддержка сортировки и фильтрации данных, передаваемых между другой моделью и представлением |
|
Элемент для использования с классом QStandardItemModel |
|
Возможность регистрации виджетов без необходимости наследования QItemEditorCreatorBase |
|
Общедоступная модель для хранения пользовательских данных |
|
Модель, которая предоставляет строки представлениям |
|
Средства отображения и редактирования элементов данных из модели |
|
Стандартная реализация модели/представления табличного представления |
|
Представление таблицы на основе элементов с моделью по умолчанию |
|
Элемент для использования с классом QTableWidget |
|
Способ взаимодействия с выделением в модели без использования индексов модели и модели выделения |
|
Стандартная реализация модели/представления древовидного представления |
|
Древовидное представление, использующее предварительно определенную модель дерева |
|
Элемент для использования с удобным классом QTreeWidget |
|
Способ перебора элементов в экземпляре QTreeWidget |
Примеры по теме
См. также Пример пазла для представлений элементов.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/model-view-programming.html