Программирование модели/представления
Введение в программирование модели/представления
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 = 0)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
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 QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
} Опять же, мы возвращаем корректное значение QVariant только в том случае, если роль поддерживается. При определении возвращаемых данных также учитывается ориентация заголовка.
Не все представления отображают заголовки с данными элементов, а те, что отображают, могут быть сконфигурированы на их скрытие. Тем не менее, рекомендуется реализовать функцию headerData() для предоставления релевантной информации о данных, предоставляемых моделью.
Элемент может иметь несколько ролей, предоставляя различные данные в зависимости от указанной роли. Элементы в нашей модели имеют только одну роль, DisplayRole, поэтому мы возвращаем данные для элементов независимо от указанной роли. Однако мы можем повторно использовать данные, которые мы предоставляем для роли DisplayRole, в других ролях, таких как ToolTipRole, которую представления могут использовать для отображения информации об элементах в подсказках.
Редактируемая модель
Только для чтения модель демонстрирует, как можно просто представить выбор пользователю, но для многих приложений модель со списком, который можно редактировать, гораздо полезнее. Мы можем изменить модель только для чтения, сделав элементы редактируемыми, изменив функцию data() для модели только для чтения и реализовав две дополнительные функции: flags() и setData(). В определении класса добавляются следующие объявления функций:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole); Делаем модель редактируемой
Делегат проверяет, редактируем ли элемент, прежде чем создавать редактор. Модель должна сообщить делегату, что ее элементы редактируемы. Мы делаем это, возвращая правильные флаги для каждого элемента в модели; в этом случае мы активируем все элементы и делаем их как выбираемыми, так и редактируемыми:
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);
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());
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); Так как строки в этой модели соответствуют строкам в списке, функция 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. - Чтобы разрешить пользователю отпускать внутренние или внешние элементы внутри представления, установите свойство acceptDrops области просмотра представления viewport() в
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(). Например, модель, которая обрабатывает простой список строк, может предоставить реализацию, которая обрабатывает данные, отпущенные на существующие элементы отдельно от данных, отпущенных в верхний уровень модели (т. е. на недопустимый элемент).
Модели могут запретить отпускание на определенные элементы или, в зависимости от отпущенных данных, путем переопределения 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 и ничего не делает.
Классы модели и представления
Эти классы используют шаблон проектирования модель/представление, в котором данные (в модели) хранятся отдельно от способа их представления и обработки пользователем (в представлении).
Абстрактный интерфейс для классов моделей элементов |
|
Абстрактная модель, которая может быть расширена для создания одномерных списковых моделей |
|
Абстрактная модель, которая может быть расширена для создания табличных моделей |
|
Используется для поиска данных в модели данных |
|
Используется для поиска данных в модели данных |
|
Базовый класс для прокси-моделей элементов, которые могут выполнять сортировку, фильтрацию или другие задачи обработки данных |
|
Проксирует свою исходную модель без изменений |
|
Управляет информацией о выбранных элементах в модели |
|
Отслеживает выбранные элементы представления |
|
Управляет информацией о диапазоне выбранных элементов в модели |
|
Поддержка сортировки и фильтрации данных, передаваемых между другой моделью и представлением |
|
Модель, которая предоставляет строки для представлений |
|
Элемент для использования с классом QStandardItemModel |
|
Общая модель для хранения пользовательских данных |
|
Модель данных для локальной файловой системы |
|
Используется для отображения и редактирования элементов данных из модели |
|
Основные функции для классов представлений элементов |
|
Реализация модели/представления для представления столбца |
|
Сопоставление части модели данных с виджетами |
|
Строка заголовка или столбец заголовка для представлений элементов |
|
Средства отображения и редактирования элементов данных из модели |
|
Позволяет создавать создатели редакторов элементов без наследования QItemEditorCreatorBase |
|
Абстрактный базовый класс, который необходимо расширить при реализации новых создателей редакторов элементов |
|
Виджеты для редактирования данных элементов в представлениях и делегатах |
|
Возможность регистрации виджетов без необходимости наследования QItemEditorCreatorBase |
|
Представление списка или значков на модели |
|
Виджет списка на основе элементов |
|
Элемент для использования с классом представления QListWidget |
|
Средства отображения и редактирования элементов данных из модели |
|
Стандартная модель/представление для табличного представления |
|
Представление таблицы на основе элементов с моделью по умолчанию |
|
Элемент для использования с классом QTableWidget |
|
Способ взаимодействия с выделением в модели без использования индексов модели и модели выделения |
|
Стандартная модель/представление для древовидного представления |
|
Представление дерева, использующее предопределенную модель дерева |
|
Элемент для использования с удобным классом QTreeWidget |
|
Способ итерации по элементам в экземпляре QTreeWidget |
Связанные примеры
См. также Пример головоломки представлений элементов.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.9/model-view-programming.html