Spec-Zone.ru › Qt 5.13

Программирование модели/представления

Введение в программирование модели/представления

Qt содержит набор классов представления элементов, использующих архитектуру модель/представление для управления отношениями между данными и способом их отображения пользователю. Разделение функциональности, введенное этой архитектурой, предоставляет разработчикам большую гибкость в настройке отображения элементов и предоставляет стандартный интерфейс модели, позволяющий использовать широкий спектр источников данных с существующими представлениями элементов. В этом документе мы кратко рассмотрим парадигму модель/представление, очертим вовлеченные понятия и опишем архитектуру системы представлений элементов. Каждый из компонентов архитектуры поясняется, и приводятся примеры, демонстрирующие, как использовать предоставленные классы.

Архитектура модель/представление

Модель-представление-контроллер (MVC) — это шаблон проектирования, зародившийся в Smalltalk, который часто используется при создании пользовательских интерфейсов. В Шаблонах проектирования, Gamma и др. пишут:

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() в качестве родительского элемента. Это обсуждается в следующем разделе.

Родительские элементы

Таблично-подобный интерфейс к данным элементов, предоставляемый моделями, идеально подходит для использования данных в представлении таблицы или списка; система нумерации строк и столбцов точно соответствует тому, как представления отображают элементы. Однако структуры, такие как древовидные представления, требуют, чтобы модель предоставляла более гибкий интерфейс к элементам внутри. В результате каждый элемент также может быть родителем другой таблицы элементов, так же, как и верхнеуровневый элемент в древовидном представлении может содержать другой список элементов.

При запросе индекса элемента модели необходимо предоставить некоторую информацию о родителе элемента. Вне модели единственный способ сослаться на элемент — через индекс модели, поэтому также должен быть задан индекс родительской модели:

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(). Однако простые делегаты на основе виджетов могут стать подклассом QStyledItemDelegate вместо QAbstractItemDelegate и воспользоваться стандартными реализациями этих функций.

Редакторы для делегатов могут быть реализованы либо с использованием виджетов для управления процессом редактирования, либо путем непосредственной обработки событий. Первый подход рассматривается позднее в этом разделе, и он также показан в примере Spin Box Delegate.

Пример Pixelator демонстрирует, как создать пользовательского делегата, выполняющего специализированное отображение для представления таблицы.

Использование существующего делегата

Стандартные представления, предоставляемые Qt, используют экземпляры QStyledItemDelegate для предоставления возможностей редактирования. Эта стандартная реализация интерфейса делегата отображает элементы в обычном стиле для каждого из стандартных представлений: QListView, QTableView и QTreeView.

Все стандартные роли обрабатываются по умолчанию делегатом, используемым стандартными представлениями. Способ их интерпретации описан в документации по QStyledItemDelegate.

Делегат, используемый представлением, возвращается функцией itemDelegate(). Функция setItemDelegate() позволяет установить пользовательского делегата для стандартного представления, и ее необходимо использовать при установке делегата для пользовательского представления.

Простой делегат

Реализованный здесь делегат использует QSpinBox для предоставления возможностей редактирования и предназначен в основном для использования с моделями, отображающими целые числа. Хотя мы настроили пользовательскую модель таблицы на основе целых чисел для этой цели, мы могли бы легко использовать QStandardItemModel вместо этого, так как пользовательский делегат управляет вводом данных. Мы создаем представление таблицы для отображения содержимого модели, и оно будет использовать пользовательский делегат для редактирования.

Мы наследуем делегат от QStyledItemDelegate, так как не хотим писать пользовательские функции отображения. Однако мы все равно должны предоставить функции для управления виджетом редактора:

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = nullptr);

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
};

Обратите внимание, что виджеты редактора не создаются при создании делегата. Мы создаем виджет редактора только тогда, когда он нужен.

Предоставление редактора

В этом примере, когда представлению таблицы нужен редактор, оно просит делегата предоставить виджет редактора, подходящий для изменяемого элемента. Функция createEditor() предоставляет делегату все необходимое для настройки подходящего виджета:

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                       const QStyleOptionViewItem &/* option */,
                                       const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(100);

    return editor;
}

Обратите внимание, что нам не нужно хранить указатель на виджет редактора, так как представление берет на себя ответственность за его уничтожение, когда он больше не нужен.

Мы устанавливаем стандартный фильтр событий делегата на редактор, чтобы гарантировать, что он предоставляет стандартные сочетания клавиш для редактирования, ожидаемые пользователями. Дополнительные сочетания клавиш можно добавить в редактор для более сложного поведения; об этом рассказывается в разделе Сочетания клавиш для редактирования.

Представление гарантирует, что данные редактора и его геометрия установлены правильно, вызывая функции, которые мы определим позже для этих целей. Мы можем создавать разные редакторы в зависимости от индекса модели, предоставленного представлением. Например, если у нас есть столбец целых чисел и столбец строк, мы можем вернуть либо QSpinBox , либо QLineEdit, в зависимости от того, какой столбец редактируется.

Делегат должен предоставить функцию для копирования данных модели в редактор. В этом примере мы считываем данные, хранящиеся в роли отображения, и соответствующим образом устанавливаем значение в спин-боксе.

void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.model()->data(index, Qt::EditRole).toInt();

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

В этом примере мы знаем, что виджет редактора — спин-бокс, но мы могли бы предоставить разные редакторы для разных типов данных в модели, в этом случае нам нужно было бы привести виджет к соответствующему типу перед доступом к его членам-функциям.

Отправка данных в модель

Когда пользователь закончил редактирование значения в спин-боксе, представление просит делегата сохранить измененное значение в модели, вызвав функцию setModelData().

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                   const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();

    model->setData(index, value, Qt::EditRole);
}

Поскольку представление управляет виджетами редактора для делегата, нам нужно только обновить модель содержимым предоставленного редактора. В этом случае мы убеждаемся, что спин-бокс обновлен, и обновляем модель значением, которое он содержит, используя указанный индекс.

Стандартный класс QStyledItemDelegate сообщает представлению, когда оно закончено редактированием, испуская сигнал closeEditor(). Представление гарантирует, что виджет редактора закрыт и уничтожен. В этом примере мы предоставляем только простые средства редактирования, поэтому нам никогда не нужно испускать этот сигнал.

Все операции с данными выполняются через интерфейс, предоставляемый QAbstractItemModel. Это делает делегат в основном независимым от типа данных, которые он обрабатывает, но некоторые предположения должны быть сделаны, чтобы использовать определенные типы виджетов редактора. В этом примере мы предположили, что модель всегда содержит целые значения, но мы все равно можем использовать этот делегат с разными видами моделей, потому что QVariant предоставляет разумные значения по умолчанию для неожиданных данных.

Обновление геометрии редактора

Делегат отвечает за управление геометрией редактора. Геометрия должна быть установлена при создании редактора и при изменении размера или положения элемента в представлении. К счастью, представление предоставляет всю необходимую геометрическую информацию в объекте варианта представления.

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

В этом случае мы просто используем геометрическую информацию, предоставленную вариантом представления, в прямоугольнике элемента. Делегат, отображающий элементы с несколькими элементами, не будет использовать прямоугольник элемента напрямую. Он будет размещать редактор относительно других элементов в элементе.

Сочетания клавиш для редактирования

После редактирования делегаты должны предоставить подсказки другим компонентам о результате процесса редактирования и предоставить подсказки, которые помогут в последующих операциях редактирования. Это достигается путем испускания сигнала closeEditor() с подходящей подсказкой. Это выполняется стандартным фильтром событий QStyledItemDelegate, который мы установили на спин-бокс при его создании.

Поведение спин-бокса можно настроить для повышения удобства пользователя. В стандартном фильтре событий, предоставляемом QStyledItemDelegate, если пользователь нажимает Return для подтверждения выбора в спин-боксе, делегат сохраняет значение в модели и закрывает спин-бокс. Мы можем изменить это поведение, установив собственный фильтр событий для спин-бокса и предоставив подсказки редактирования, которые подходят нашим потребностям; например, мы можем испустить closeEditor() с подсказкой EditNextItem, чтобы автоматически начать редактирование следующего элемента в представлении.

Другой подход, который не требует использования фильтра событий, заключается в предоставлении собственного редактора виджета, возможно, наследуя от QSpinBox для удобства. Этот альтернативный подход позволит нам больше контролировать поведение редактора виджета за счёт написания дополнительного кода. Обычно проще установить фильтр событий в делегате, если вам нужно настроить поведение стандартного виджета редактора Qt.

Делегаты не обязаны испускать эти подсказки, но те, которые этого не делают, будут менее интегрированы в приложения и будут менее удобны, чем те, которые испускают подсказки для поддержки общих действий редактирования.

Обработка выделений в представлениях элементов

Концепции

Модель выделения, используемая в классах представлений элементов, предоставляет общее описание выделений, основанное на возможностях архитектуры модели/представления. Хотя стандартные классы для управления выделениями достаточно для предоставленных представлений элементов, модель выделения позволяет создавать специализированные модели выделения, соответствующие требованиям собственных моделей и представлений элементов.

Информация о выделенных элементах в представлении хранится в экземпляре класса QItemSelectionModel. Этот класс сохраняет индексы модели для элементов в одной модели и независим от каких-либо представлений. Поскольку может быть много представлений для одной модели, можно совместно использовать выделения между представлениями, что позволяет приложениям показывать несколько представлений согласованным образом.

Выделения состоят из диапазонов выделения. Эти диапазоны эффективно хранят информацию о больших выделениях элементов, записывая только начальные и конечные индексы модели для каждого диапазона выделенных элементов. Несмежные выделения элементов создаются путём использования более одного диапазона выделения для описания выделения.

Выделения применяются к набору индексов модели, хранящихся в модели выделения. Последнее применённое выделение элементов известно как текущее выделение. Эффекты этого выделения могут быть изменены даже после его применения с помощью определённых типов команд выделения. Эти команды обсуждаются позже в этой секции.

Текущий элемент и выделенные элементы

В представлении всегда есть текущий элемент и выделенный элемент — два независимых состояния. Элемент может быть одновременно текущим и выделенным. Представление отвечает за то, чтобы всегда был текущий элемент, так как навигация с помощью клавиатуры, например, требует текущего элемента.

В таблице ниже показаны различия между текущим элементом и выделенными элементами.

Текущий элемент Выделенные элементы
Может быть только один текущий элемент. Может быть несколько выделенных элементов.
Текущий элемент изменяется при навигации с помощью клавиш или щелчков мыши. Состояние выделения элементов устанавливается или сбрасывается в зависимости от нескольких предопределённых режимов — например, одиночное выделение, множественное выделение и т. д. — при взаимодействии пользователя с элементами.
Текущий элемент будет отредактирован, если нажата клавиша редактирования (F2) или элемент дважды щелкнут (при условии, что редактирование включено). Текущий элемент может использоваться вместе с якорем для указания диапазона, который должен быть выделен или снят с выделения (или их комбинации).
Текущий элемент отображается прямоугольником фокуса. Выделенные элементы отображаются прямоугольником выделения.

При работе с выделениями часто полезно рассматривать QItemSelectionModel как запись состояния выделения всех элементов в модели элементов. После настройки модели выделения можно выделять, снимать выделение или переключать состояния выделения набора элементов без необходимости знать, какие элементы уже выделены. Индексы всех выделенных элементов можно получить в любое время, и другие компоненты могут быть проинформированы о изменениях в модели выделения через механизм сигналов и слотов.

Использование модели выделения

Стандартные классы представлений предоставляют стандартные модели выделения, которые могут использоваться в большинстве приложений. Модель выделения, принадлежащую одному представлению, можно получить с помощью функции selectionModel() представления, и совместно использовать между многими представлениями с помощью setSelectionModel(), поэтому создание новых моделей выделения обычно не требуется.

Выделение создаётся путём задания модели и пары индексов модели для QItemSelection. Это использует индексы для ссылки на элементы в заданной модели и интерпретирует их как верхний левый и нижний правый элементы в блоке выделенных элементов. Для применения выделения к элементам в модели требуется передать выделение в модель выделения; это можно сделать несколькими способами, каждый из которых имеет разный эффект на уже существующие выделения в модели выделения.

END_OF_DOCUMENT_MARKER

Выбор элементов

Для демонстрации основных функций выбора мы создаём экземпляр пользовательской модели таблицы с 32 элементами и открываем представление таблицы для данных:

    TableModel *model = new TableModel(8, 4, &app);

    QTableView *table = new QTableView(0);
    table->setModel(model);

    QItemSelectionModel *selectionModel = table->selectionModel();

Модель выбора по умолчанию для представления таблицы извлекается для последующего использования. Мы не изменяем элементы в модели, а вместо этого выбираем несколько элементов, которые представление отобразит в левом верхнем углу таблицы. Для этого необходимо получить индексы модели, соответствующие элементам в левом верхнем и правом нижнем углах области, которую нужно выбрать:

    QModelIndex topLeft;
    QModelIndex bottomRight;

    topLeft = model->index(0, 0, QModelIndex());
    bottomRight = model->index(5, 2, QModelIndex());

Для выбора этих элементов в модели и отображения соответствующих изменений в представлении таблицы необходимо создать объект выбора, а затем применить его к модели выбора:

    QItemSelection selection(topLeft, bottomRight);
    selectionModel->select(selection, QItemSelectionModel::Select);

Выбор применяется к модели выбора с помощью команды, определённой комбинацией флагов выбора. В данном случае используемые флаги заставляют элементы, записанные в объекте выбора, включаться в модель выбора независимо от их предыдущего состояния. Результирующий выбор отображается представлением.

Выбор элементов можно изменить, используя различные операции, которые определяются флагами выбора. Результатом этих операций может быть сложная структура выбора, но модель выбора эффективно её представляет. Использование различных флагов выбора для управления выбранными элементами описывается при рассмотрении того, как обновить выбор.

Чтение состояния выбора

Индексы модели, хранящиеся в модели выбора, можно прочитать с помощью функции selectedIndexes(). Она возвращает неупорядоченный список индексов модели, по которому можно итерироваться, зная, к какой модели они относятся:

    const QModelIndexList indexes = selectionModel->selectedIndexes();

    for (const QModelIndex &index : indexes) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

В приведенном выше коде используется цикл for с диапазоном для итерации по элементам, соответствующим индексам, возвращённым моделью выбора, и их изменения.

Модель выбора излучает сигналы, чтобы указать изменения в выборе. Эти сигналы уведомляют другие компоненты об изменениях как всего выбора, так и текущего фокусированного элемента в модели элементов. Мы можем подключить сигнал selectionChanged() к слоту и просмотреть элементы в модели, которые выбраны или сняты с выбора, когда выбор изменяется. Слот вызывается с двумя объектами QItemSelection: один содержит список индексов, соответствующих нововыбранным элементам; другой содержит индексы, соответствующие элементам, которые были сняты с выбора.

В следующем коде мы предоставляем слот, который получает сигнал selectionChanged(), заполняет выбранные элементы строкой и очищает содержимое элементов, которые были сняты с выбора.

void MainWindow::updateSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
    QModelIndexList items = selected.indexes();

    for (const QModelIndex &index : qAsConst(items)) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

    items = deselected.indexes();

    for (const QModelIndex &index : qAsConst(items)) {
        model->setData(index, QString());
}

Мы можем отслеживать текущий фокусированный элемент, подключив сигнал currentChanged() к слоту, который вызывается с двумя индексами модели. Эти индексы соответствуют предыдущему и текущему фокусированному элементу.

В следующем коде мы предоставляем слот, который получает сигнал currentChanged(), и использует полученную информацию для обновления строки состояния окна QMainWindow:

void MainWindow::changeCurrent(const QModelIndex &current,
    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. Это приводит к сбросу коллекции индексов модели выбора.

id="selecting-all-items-in-a-model">Выделение всех элементов в модели

Для выделения всех элементов в модели необходимо создать выделение для каждого уровня модели, которое охватывает все элементы на этом уровне. Для этого мы получаем индексы, соответствующие элементам в верхнем левом и нижнем правом углах с заданным родительским индексом:

    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() для определения того, является ли какой-либо элемент родителем другого уровня элементов.

id="creating-new-models">Создание новых моделей

Разделение функциональности между компонентами модели/представления позволяет создавать модели, которые могут использовать существующие представления. Этот подход позволяет нам отображать данные из различных источников с использованием стандартных графических компонентов пользовательского интерфейса, таких как QListView, QTableView и QTreeView.

Класс QAbstractItemModel предоставляет интерфейс, достаточно гибкий, чтобы поддерживать источники данных, которые организуют информацию в иерархических структурах, позволяя данные вставлять, удалять, изменять или сортировать каким-либо образом. Он также поддерживает операции перетаскивания.

Классы QAbstractListModel и QAbstractTableModel предоставляют поддержку интерфейсов для более простых неиерархических структур данных и проще использовать в качестве отправной точки для простых моделей списков и таблиц.

В этом разделе мы создадим простую модель только для чтения, чтобы изучить основные принципы архитектуры модели/представления. Позже в этом разделе мы адаптируем эту простую модель, чтобы пользователи могли изменять элементы.

Пример более сложной модели см. в примере Simple Tree Model.

Требования к подклассам QAbstractItemModel описываются более подробно в документе Model Subclassing Reference.

id="designing-a-model">Проектирование модели

При создании новой модели для существующей структуры данных важно определить, какой тип модели следует использовать для предоставления интерфейса к данным. Если структуру данных можно представить как список или таблицу элементов, можно подклассировать QAbstractListModel или QAbstractTableModel, так как эти классы предоставляют подходящие реализации по умолчанию для многих функций.

Однако, если базовую структуру данных можно представить только в виде иерархической древовидной структуры, необходимо подклассировать QAbstractItemModel. Этот подход используется в примере Simple Tree Model.

В этом разделе мы реализуем простую модель, основанную на списке строк, поэтому QAbstractListModel обеспечивает идеальный базовый класс для построения.

Какой бы форму не принимала базовая структура данных, обычно неплохо дополнять стандартный API QAbstractItemModel в специализированных моделях другим, который позволяет более естественный доступ к базовой структуре данных. Это упрощает заполнение модели данными, но при этом позволяет другим общим компонентам модели/представления взаимодействовать с ней с помощью стандартного API. Описанная ниже модель предоставляет пользовательский конструктор именно для этой цели.

id="a-read-only-example-model">Пример модели только для чтения

Реализованная здесь модель — это простая, неиерархическая модель данных только для чтения, основанная на стандартном классе QStringListModel. Она имеет QStringList в качестве своего внутреннего источника данных и реализует только необходимое для функционирующей модели. Для упрощения реализации мы подклассируем QAbstractListModel, так как он определяет разумное поведение по умолчанию для моделей списков и предоставляет более простой интерфейс, чем класс QAbstractItemModel.

При реализации модели важно помнить, что QAbstractItemModel не хранит данные самостоятельно, а лишь предоставляет интерфейс, который представления используют для доступа к данным. Для минимальной модели только для чтения необходимо реализовать несколько функций, так как для большинства интерфейсов существуют стандартные реализации. Объявление класса выглядит следующим образом:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

private:
    QStringList stringList;
};

Помимо конструктора модели, нам нужно реализовать только две функции: rowCount() возвращает количество строк в модели, а data() возвращает элемент данных, соответствующий указанному индексу модели.

Хорошо организованные модели также реализуют headerData(), чтобы деревья и табличные представления имели что отображать в своих заголовках.

Обратите внимание, что это неиерархическая модель, поэтому нам не нужно беспокоиться о взаимоотношениях родитель-потомок. Если наша модель была иерархической, нам также нужно было бы реализовать функции index() и parent().

Список строк хранится внутри в переменной stringList private member.

Размеры модели

Мы хотим, чтобы количество строк в модели было таким же, как количество строк в списке строк. Мы реализуем функцию rowCount() с учетом этого:

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

Поскольку модель неиерархическая, мы можем безопасно игнорировать индекс модели, соответствующий элементу родителя. По умолчанию модели, производные от QAbstractListModel, содержат только один столбец, поэтому нам не нужно переопределять функцию columnCount().

Заголовки и данные модели

Для элементов представления мы хотим возвращать строки из списка строк. Функция data() отвечает за возвращение элемента данных, соответствующего аргументу индекса:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Мы возвращаем допустимый QVariant только в том случае, если предоставленный индекс модели является допустимым, номер строки находится в пределах диапазона элементов в списке строк, а запрашиваемый роль поддерживается.

Некоторые представления, такие как QTreeView и QTableView, могут отображать заголовки вместе с данными элементов. Если наша модель отображается в представлении с заголовками, мы хотим, чтобы заголовки отображали номера строк и столбцов. Мы можем предоставить информацию о заголовках, переопределяя функцию headerData():

QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QStringLiteral("Column %1").arg(section);
    else
        return QStringLiteral("Row %1").arg(section);
}

Опять же, мы возвращаем допустимый QVariant только в том случае, если роль поддерживается. При определении возвращаемых данных также учитывается ориентация заголовка.

Не все представления отображают заголовки с данными элементов, и те, которые это делают, могут быть сконфигурированы на их скрытие. Тем не менее, рекомендуется реализовать функцию headerData(), чтобы предоставить соответствующую информацию о данных, предоставляемых моделью.

Элемент может иметь несколько ролей, выдавая различные данные в зависимости от заданной роли. Элементы нашей модели имеют только одну роль, DisplayRole, поэтому мы возвращаем данные для элементов независимо от заданной роли. Однако мы могли бы повторно использовать предоставленные данные для DisplayRole в других ролях, таких как ToolTipRole, которые представления могут использовать для отображения информации об элементах в подсказке.

Редактируемая модель

Модель только для чтения демонстрирует, как могут быть представлены простые варианты пользователю, но для многих приложений редактируемая модель списка намного полезнее. Мы можем изменить модель только для чтения, чтобы сделать элементы редактируемыми, изменив реализованную для неё функцию data(), и реализовав две дополнительные функции: flags() и setData(). Следующие объявления функций добавляются к определению класса:

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

Делаем модель редактируемой

Делегат проверяет, можно ли редактировать элемент, прежде чем создавать редактор. Модель должна сообщить делегату, что её элементы редактируемые. Мы делаем это, возвращая правильные флаги для каждого элемента в модели; в этом случае мы активируем все элементы и делаем их как выбираемыми, так и редактируемыми:

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

Обратите внимание, что нам не нужно знать, как делегат выполняет фактический процесс редактирования. Нам нужно только предоставить способ для делегата установить данные в модели. Это достигается с помощью функции setData():

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {

        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

В этой модели элемент в списке строк, соответствующий индексу модели, заменяется предоставленным значением. Однако прежде чем мы сможем изменить список строк, мы должны убедиться, что индекс допустим, элемент имеет правильный тип и что роль поддерживается. По соглашению, мы настаиваем на том, что роль является EditRole, так как именно эта роль используется стандартным делегатом элементов. Однако для логических значений вы можете использовать Qt::CheckStateRole и установить флаг Qt::ItemIsUserCheckable; для редактирования значения используется флажок. Базовые данные в этой модели одинаковы для всех ролей, поэтому эта деталь просто упрощает интеграцию модели со стандартными компонентами.

Когда данные установлены, модель должна сообщить представлениям о том, что некоторые данные изменились. Это делается путем испускания сигнала dataChanged(). Поскольку изменился только один элемент данных, диапазон элементов, указанный в сигнале, ограничен только одним индексом модели.

Также функция data() должна быть изменена, чтобы добавить проверку Qt::EditRole:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

Вставка и удаление строк

Можно изменить количество строк и столбцов в модели. В модели списка строк имеет смысл изменять только количество строк, поэтому мы переопределяем только функции вставки и удаления строк. Они объявлены в определении класса:

    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

Поскольку строки в этой модели соответствуют строкам в списке, функция insertRows() вставляет определенное количество пустых строк в список строк перед указанной позицией. Количество вставленных строк соответствует количеству указанных строк.

Индекс родителя обычно используется для определения того, где в модели следует добавить строки. В данном случае у нас есть только один список строк верхнего уровня, поэтому мы просто вставляем пустые строки в этот список.

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

Модель сначала вызывает функцию beginInsertRows(), чтобы проинформировать другие компоненты о том, что количество строк собирается измениться. Функция указывает номера строк первой и последней новых строк для вставки и индекс модели для их родительского элемента. После изменения списка строк она вызывает endInsertRows(), чтобы завершить операцию и проинформировать другие компоненты о том, что размеры модели изменились, возвращая true, чтобы указать на успех.

Функция удаления строк из модели также проста в реализации. Строки, которые необходимо удалить из модели, определяются позицией и количеством строк. Мы игнорируем индекс родителя, чтобы упростить нашу реализацию, и просто удаляем соответствующие элементы из списка строк.

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

Функция beginRemoveRows() всегда вызывается перед удалением каких-либо базовых данных и указывает первую и последнюю строки для удаления. Это позволяет другим компонентам получить доступ к данным перед тем, как они станут недоступны. После удаления строк модель испускает endRemoveRows(), чтобы завершить операцию и проинформировать другие компоненты о том, что размеры модели изменились.

Следующие шаги

Мы можем отобразить данные, предоставляемые этой моделью или любой другой моделью, используя класс QListView для представления элементов модели в виде вертикального списка. Для модели списка строк это представление также предоставляет стандартный редактор, позволяющий манипулировать элементами. Мы рассмотрим возможности, предоставляемые стандартными классами представлений в разделе Классы представлений.

Документ Справочник по наследованию от модели более подробно описывает требования к подклассам QAbstractItemModel и предоставляет руководство по виртуальным функциям, которые необходимо реализовать, чтобы включить различные возможности в разных типах моделей.

Классы удобства для просмотра элементов

У виджетов, основанных на элементах, есть имена, отражающие их назначение: QListWidget предоставляет список элементов, QTreeWidget отображает многоуровневую древовидную структуру, а QTableWidget предоставляет таблицу элементов ячеек. Каждый класс наследует поведение класса QAbstractItemView, который реализует общее поведение для выбора элементов и управления заголовками.

Список виджетов

Список элементов одного уровня обычно отображается с помощью QListWidget и ряда QListWidgetItem. Виджет списка создается так же, как и любой другой виджет:

    QListWidget *listWidget = new QListWidget(this);

Элементы списка можно добавить непосредственно в виджет списка при их создании:

    new QListWidgetItem(tr("Sycamore"), listWidget);
    new QListWidgetItem(tr("Chestnut"), listWidget);
    new QListWidgetItem(tr("Mahogany"), listWidget);

Они также могут быть созданы без родительского виджета списка и добавлены в список в более позднее время:

    QListWidgetItem *newItem = new QListWidgetItem;
    newItem->setText(itemText);
    listWidget->insertItem(row, newItem);

Каждый элемент в списке может отображать текстовую метку и значок. Цвета и шрифт, используемые для отображения текста, могут быть изменены для предоставления настраиваемого внешнего вида элементов. Подсказки, подсказки состояния и помощь "Что это?" легко настраиваются, чтобы обеспечить надлежащую интеграцию списка в приложение.

    newItem->setToolTip(toolTipText);
    newItem->setStatusTip(toolTipText);
    newItem->setWhatsThis(whatsThisText);

По умолчанию элементы в списке представляются в порядке их создания. Списки элементов можно сортировать по критериям, указанным в Qt::SortOrder, чтобы получить список элементов, отсортированный в алфавитном порядке или в обратном алфавитном порядке:

    listWidget->sortItems(Qt::AscendingOrder);
    listWidget->sortItems(Qt::DescendingOrder);

Деревья виджетов

Древовидные или иерархические списки элементов предоставляются классами QTreeWidget и QTreeWidgetItem. Каждый элемент в древовидном виджете может иметь свои дочерние элементы и может отображать несколько столбцов информации. Древовидные виджеты создаются так же, как и любые другие виджеты:

    QTreeWidget *treeWidget = new QTreeWidget(this);

Прежде чем элементы могут быть добавлены в древовидный виджет, необходимо задать количество столбцов. Например, мы могли бы определить два столбца и создать заголовок для предоставления меток вверху каждого столбца:

    treeWidget->setColumnCount(2);
    QStringList headers;
    headers << tr("Subject") << tr("Default");
    treeWidget->setHeaderLabels(headers);

Самый простой способ установить метки для каждого раздела — предоставить список строк. Для более сложных заголовков вы можете создать элемент дерева, украсить его по своему желанию и использовать его в качестве заголовка древовидного виджета.

Элементы верхнего уровня в виджете дерева создаются с виджетом дерева в качестве родительского виджета. Их можно вставлять в произвольном порядке, или вы можете обеспечить их упорядочивание, указав предыдущий элемент при создании каждого элемента:

    QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
    cities->setText(0, tr("Cities"));
    QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
    osloItem->setText(0, tr("Oslo"));
    osloItem->setText(1, tr("Yes"));

    QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

Виджеты дерева обрабатывают элементы верхнего уровня немного иначе, чем другие элементы, расположенные глубже в дереве. Элементы можно удалить из верхнего уровня дерева, вызвав функцию виджета дерева takeTopLevelItem(), но элементы из более низких уровней удаляются, вызвав функцию родительского элемента takeChild(). Элементы вставляются на верхний уровень дерева с помощью функции insertTopLevelItem(). На более низких уровнях дерева используется функция родительского элемента insertChild().

Легко перемещать элементы между верхним уровнем и более низкими уровнями дерева. Нам просто нужно проверить, являются ли элементы элементами верхнего уровня или нет, и эта информация предоставляется функцией каждого элемента parent(). Например, мы можем удалить текущий элемент в виджете дерева независимо от его расположения:

    QTreeWidgetItem *parent = currentItem->parent();
    int index;

    if (parent) {
        index = parent->indexOfChild(treeWidget->currentItem());
        delete parent->takeChild(index);
    } else {
        index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
        delete treeWidget->takeTopLevelItem(index);
    }

Вставка элемента в другое место в виджете дерева выполняется по той же схеме:

    QTreeWidgetItem *parent = currentItem->parent();
    QTreeWidgetItem *newItem;
    if (parent)
        newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
    else
        newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

Табличные виджеты

Таблицы элементов, похожие на таблицы в приложениях для работы с электронными таблицами, создаются с помощью QTableWidget и QTableWidgetItem. Они обеспечивают прокручиваемую таблицу с заголовками и элементами для использования в ней.

Таблицы могут быть созданы с заданным количеством строк и столбцов, или они могут быть добавлены в незаданную таблицу по мере необходимости.

    QTableWidget *tableWidget;
    tableWidget = new QTableWidget(12, 3, this);

Элементы создаются вне таблицы перед добавлением в таблицу в нужном месте:

    QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
        pow(row, column+1)));
    tableWidget->setItem(row, column, newItem);

Горизонтальные и вертикальные заголовки можно добавить в таблицу, создав элементы вне таблицы и используя их в качестве заголовков:

    QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
    tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

Обратите внимание, что строки и столбцы в таблице начинаются с нуля.

Общие функции

Существует ряд функций, основанных на элементах, общих для каждого из классов-удобств, которые доступны через те же интерфейсы в каждом классе. Мы представляем их в следующих разделах с некоторыми примерами для разных виджетов. Обратитесь к списку Классов модели/представления для каждого из виджетов для получения более подробной информации об использовании каждой используемой функции.

Скрытые элементы

Иногда бывает полезно скрывать элементы в виджете представления элементов вместо их удаления. Элементы всех вышеперечисленных виджетов могут быть скрыты и затем снова показаны. Вы можете определить, скрыт ли элемент, вызвав функцию isItemHidden(), и элементы могут быть скрыты с помощью setItemHidden().

Поскольку эта операция основана на элементах, та же функция доступна для всех трех классов-удобств.

Выбор элементов

Способ выбора элементов контролируется режимом выбора виджета (QAbstractItemView::SelectionMode). Эта свойство определяет, может ли пользователь выбирать один или несколько элементов и, при выборе множества элементов, должен ли выбор быть непрерывным диапазоном элементов. Режим выбора работает одинаково для всех вышеперечисленных виджетов.

Выбор одного элемента: Когда пользователю нужно выбрать один элемент из виджета, наиболее подходящим является режим SingleSelection по умолчанию. В этом режиме текущий элемент и выбранный элемент совпадают.

Выбор нескольких элементов: В этом режиме пользователь может переключать состояние выбора любого элемента в виджете, не изменяя существующий выбор, аналогично тому, как можно независимо переключать неисключающие флажки.

Расширенный выбор: Виджеты, которые часто требуют выбора нескольких смежных элементов, такие как таблицы в электронных таблицах, требуют режима ExtendedSelection. В этом режиме непрерывные диапазоны элементов в виджете можно выбирать как мышью, так и клавиатурой. Также можно создавать сложные выборки, включающие множество элементов, которые не примыкают к другим выбранным элементам в виджете, если используются модификаторы клавиш.

Если пользователь выбирает элемент без использования модификатора клавиш, существующий выбор очищается.

Выбранные элементы в виджете считываются с помощью функции selectedItems(), предоставляя список соответствующих элементов, по которым можно итерироваться. Например, мы можем найти сумму всех числовых значений в списке выбранных элементов с помощью следующего кода:

    const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
    int number = 0;
    double total = 0;

    for (QTableWidgetItem *item : selected) {
        bool ok;
        double value = item->text().toDouble(&ok);

        if (ok && !item->text().isEmpty()) {
            total += value;
            number++;
        }
    }

Обратите внимание, что для режима одиночного выбора текущий элемент будет в списке выбора. В режимах множественного и расширенного выбора текущий элемент может не находиться в списке выбора, в зависимости от способа выбора пользователя.

Поиск

Часто бывает полезно иметь возможность находить элементы в виджете представления элементов, как для разработчика, так и для предоставления пользователю. Все три класса удобств для представления элементов предоставляют общую функцию findItems() для максимального соответствия и простоты.

Элементы ищутся по тексту, который они содержат, в соответствии с критериями, указанными в выборе значений из Qt::MatchFlags. Мы можем получить список соответствующих элементов с помощью функции findItems().

    const QList<QTreeWidgetItem *> found = treeWidget->findItems(
        itemText, Qt::MatchWildcard);

    for (QTreeWidgetItem *item : found) {
        item->setSelected(true);
        // Show the item->text(0) for each item.
    }

Вышеприведенный код вызывает выбор элементов в виджете дерева, если они содержат текст, указанный в строке поиска. Этот шаблон также может использоваться в виджетах списка и таблицы.

Использование перетаскивания с элементами представлений

Инфраструктура перетаскивания Qt полностью поддерживается моделью/представлением. Элементы в списках, таблицах и деревьях могут быть перетаскиваемы внутри представлений, а данные могут быть импортированы и экспортированы в виде данных, закодированных в MIME.

Стандартные представления автоматически поддерживают внутреннее перетаскивание, где элементы перемещаются для изменения порядка их отображения. По умолчанию перетаскивание для этих представлений отключено, поскольку они настроены для самых простых и распространённых случаев использования. Чтобы разрешить перетаскивание элементов, необходимо включить определённые свойства представления, а сами элементы также должны допускать перетаскивание.

Требования к модели, которая позволяет только экспортировать элементы из представления и не позволяет опускать данные в него, меньше, чем для полностью включенной модели перетаскивания.

См. также Справочник по наследованию модели для получения дополнительной информации о включении поддержки перетаскивания в новые модели.

Использование удобных представлений

Каждый тип элемента, используемого с QListWidget, QTableWidget и QTreeWidget, настраивается по умолчанию с использованием другого набора флагов. Например, каждый QListWidgetItem или QTreeWidgetItem изначально включён, можно поставить флаг проверки, выбирать, и его можно использовать в качестве источника операции перетаскивания; каждый QTableWidgetItem также может быть отредактирован и использован в качестве цели операции перетаскивания.

Хотя для всех стандартных элементов установлен один или оба флага для перетаскивания, вам обычно нужно задать различные свойства в самом представлении, чтобы воспользоваться встроенной поддержкой перетаскивания:

  • Чтобы включить перетаскивание элементов, установите свойство dragEnabled представления в true.
  • Чтобы разрешить пользователю опускать внутренние или внешние элементы в представлении, установите свойство viewport()'s acceptDrops в true.
  • Чтобы показать пользователю, куда будет помещён элемент, который в данный момент перетаскивается, если его опустить, установите свойство showDropIndicator представления. Это предоставляет пользователю постоянно обновляемую информацию о размещении элементов в представлении.

Например, мы можем включить перетаскивание в виджете списка с помощью следующих строк кода:

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

В результате получается виджет списка, который позволяет копировать элементы внутри представления и даже позволяет пользователю перетаскивать элементы между представлениями, содержащими данные одного типа. В обоих случаях элементы копируются, а не перемещаются.

Чтобы разрешить пользователю перемещать элементы внутри представления, необходимо установить dragDropMode виджета списка:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Использование классов модель/представление

Настройка представления для перетаскивания следует тому же шаблону, что и при использовании удобных представлений. Например, QListView можно настроить так же, как QListWidget:

QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

Поскольку доступ к данным, отображаемым представлением, контролируется моделью, используемая модель также должна обеспечивать поддержку операций перетаскивания. Действия, поддерживаемые моделью, могут быть заданы путём переопределения функции QAbstractItemModel::supportedDropActions(). Например, операции копирования и перемещения включаются с помощью следующего кода:

Qt::DropActions DragDropListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Хотя любые комбинации значений из Qt::DropActions могут быть заданы, модель должна быть написана для их поддержки. Например, для правильного использования Qt::MoveAction с моделью списка модель должна предоставить реализацию QAbstractItemModel::removeRows(), либо напрямую, либо унаследовав реализацию от базового класса.

Включение перетаскивания для элементов

Модели указывают представлениям, какие элементы можно перетаскивать и какие будут принимать перетаскивания, переопределяя функцию QAbstractItemModel::flags(), чтобы предоставить подходящие флаги.

Например, модель, которая предоставляет простой список на основе QAbstractListModel, может включить перетаскивание для каждого элемента, убедившись, что возвращаемые флаги содержат значения Qt::ItemIsDragEnabled и Qt::ItemIsDropEnabled:

Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

Обратите внимание, что элементы могут быть опущены в верхний уровень модели, но перетаскивание разрешено только для допустимых элементов.

В приведенном выше коде, поскольку модель происходит от QStringListModel, мы получаем набор флагов по умолчанию, вызвав реализацию функции flags().

Кодирование экспортированных данных

При экспорте данных из модели в операции перетаскивания они кодируются в соответствующий формат, соответствующий одному или нескольким типам MIME. Модели объявляют типы MIME, которые они могут использовать для предоставления элементов, переопределяя функцию QAbstractItemModel::mimeTypes(), возвращая список стандартных типов MIME.

Например, модель, которая предоставляет только обычный текст, предоставит следующую реализацию:

QStringList DragDropListModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

Модель также должна предоставить код для кодирования данных в объявленном формате. Это достигается путем повторной реализации функции QAbstractItemModel::mimeData() для предоставления объекта QMimeData, как и при любой другой операции перетаскивания.

Следующий код показывает, как каждый элемент данных, соответствующий заданному списку индексов, кодируется как обычный текст и сохраняется в объекте QMimeData.

QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData;
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    for (const QModelIndex &index : indexes) {
        if (index.isValid()) {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

Поскольку функции предоставляется список индексов модели, этот подход достаточно универсален для использования как в иерархических, так и в неиерархических моделях.

Обратите внимание, что пользовательские типы данных должны быть объявлены как метаобъекты, а для них должны быть реализованы операторы потока. Подробности см. в описании класса QMetaObject.

Вставка опущенных данных в модель

Способ обработки опущенных данных конкретной моделью зависит как от ее типа (список, таблица или дерево), так и от способа представления ее содержимого пользователю. В целом, подход к обработке опущенных данных должен быть наиболее подходящим для базового хранилища данных модели.

Разные типы моделей обрабатывают опущенные данные по-разному. Модели списка и таблицы предоставляют только плоскую структуру, в которой хранятся элементы данных. В результате они могут вставлять новые строки (и столбцы) при опускании данных на существующий элемент в представлении или перезаписывать содержимое элемента в модели с использованием некоторых предоставленных данных. Модели дерева часто могут добавлять дочерние элементы, содержащие новые данные, в свои базовые хранилища данных и, следовательно, будут вести себя более предсказуемо с точки зрения пользователя.

Обработка опущенных данных осуществляется путем повторной реализации функции QAbstractItemModel::dropMimeData() модели. Например, модель, обрабатывающая простой список строк, может предоставить реализацию, которая обрабатывает данные, опущенные на существующие элементы отдельно от данных, опущенных в верхнем уровне модели (т.е. на недействительный элемент).

Модели могут запретить опускание на определенные элементы или в зависимости от опущенных данных путем повторной реализации функции QAbstractItemModel::canDropMimeData().

Модель сначала должна убедиться, что операция должна быть выполнена, предоставленные данные находятся в формате, который может быть использован, и что их местоположение в модели является допустимым:

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

    if (!data->hasFormat("application/vnd.text.list"))
        return false;

    if (column > 0)
        return false;

    return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction)
        return true;

Простая модель списка строк с одним столбцом может указать на ошибку, если предоставленные данные не являются обычным текстом или если номер столбца, указанный для опускания, является недопустимым.

Данные, которые будут вставлены в модель, обрабатываются по-разному в зависимости от того, опускаются ли они на существующий элемент или нет. В этом простом примере мы хотим разрешить опускания между существующими элементами, перед первым элементом в списке и после последнего.

При опускании индекс модели, соответствующий родительскому элементу, будет либо действительным, указывая на то, что опускание произошло на элемент, либо недействительным, указывая на то, что опускание произошло где-то в представлении, что соответствует верхнему уровню модели.

    int beginRow;

    if (row != -1)
        beginRow = row;

Изначально мы проверяем номер строки, чтобы увидеть, можем ли мы использовать его для вставки элементов в модель, независимо от того, является ли родительский индекс действительным или нет.

    else if (parent.isValid())
        beginRow = parent.row();

Если родительский индекс модели действителен, опускание произошло на элемент. В этой простой модели списка мы находим номер строки элемента и используем это значение для вставки опущенных элементов в верхний уровень модели.

    else
        beginRow = rowCount(QModelIndex());

Когда опускание происходит в другом месте представления, и номер строки неприменим, мы добавляем элементы в верхний уровень модели.

В иерархических моделях при опускании на элемент было бы лучше вставлять новые элементы в модель как дочерние элементы этого элемента. В представленном здесь простом примере модель имеет только один уровень, поэтому этот подход не подходит.

Декодирование импортированных данных

Каждая реализация функции dropMimeData() также должна декодировать данные и вставить их в базовую структуру данных модели.

Для простой модели списка строк закодированные элементы могут быть декодированы и переданы в QStringList:

    QByteArray encodedData = data->data("application/vnd.text.list");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QStringList newItems;
    int rows = 0;

    while (!stream.atEnd()) {
        QString text;
        stream >> text;
        newItems << text;
        ++rows;
    }

Затем строки могут быть вставлены в базовое хранилище данных. Для согласованности это можно сделать через собственный интерфейс модели:

    insertRows(beginRow, rows, QModelIndex());
    for (const QString &text : qAsConst(newItems)) {
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

Обратите внимание, что модель, как правило, должна предоставить реализации функций QAbstractItemModel::insertRows() и QAbstractItemModel::setData().

Прокси-модели

В рамках модели/представления данные, предоставленные одной моделью, могут быть разделены любым количеством представлений, и каждое из них может, возможно, представлять ту же информацию совершенно по-разному. Пользовательские представления и делегаты являются эффективными способами предоставления радикально различных представлений одних и тех же данных. Однако приложениям часто требуется предоставлять стандартные представления на обработанных версиях одних и тех же данных, например, отсортированные представления списка элементов.

Хотя кажется уместным выполнять операции сортировки и фильтрации как внутренние функции представлений, такой подход не позволяет нескольким представлениям совместно использовать результаты таких потенциально дорогостоящих операций. Альтернативный подход, включающий сортировку непосредственно в модели, приводит к аналогичной проблеме, где каждое представление должно отображать элементы данных, организованные в соответствии с последней операцией обработки.

Для решения этой проблемы, модель/видовая структура использует модельные прокси для управления информацией, передаваемой между отдельными моделями и представлениями. Модельные прокси — это компоненты, которые ведут себя как обычные модели с точки зрения представления и обращаются к данным исходных моделей от имени этого представления. Сигналы и слоты, используемые модель/видовой структурой, гарантируют, что каждое представление обновляется должным образом, независимо от того, сколько модельных прокси помещено между ним и исходной моделью.

Использование модельных прокси

Модельные прокси можно вставить между существующей моделью и любым количеством представлений. Qt поставляется со стандартным модельным прокси, QSortFilterProxyModel, который обычно создаётся и используется напрямую, но также может быть расширен для предоставления пользовательского поведения фильтрации и сортировки. Класс QSortFilterProxyModel можно использовать следующим образом:

    QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
    filterModel->setSourceModel(stringListModel);

    QListView *filteredView = new QListView;
    filteredView->setModel(filterModel);

Поскольку модельные прокси наследуются от QAbstractItemModel, они могут быть подключены к любому виду представления и могут быть совместно использованы между представлениями. Их также можно использовать для обработки информации, полученной от других модельных прокси в конвейерной организации.

Класс QSortFilterProxyModel предназначен для создания и непосредственного использования в приложениях. Более специализированные модельные прокси могут быть созданы путём расширения этого класса и реализации необходимых операций сравнения.

Настройка модельных прокси

Как правило, тип обработки, используемой в модельном прокси, включает сопоставление каждого элемента данных из его исходного местоположения в исходной модели либо с другим местоположением в модельном прокси. В некоторых моделях некоторые элементы могут не иметь соответствующего местоположения в модельном прокси; эти модели являются фильтрующими модельными прокси. Представления обращаются к элементам, используя индексы модели, предоставляемые модельным прокси, и они не содержат информации об исходной модели или местоположении исходных элементов в этой модели.

QSortFilterProxyModel позволяет отфильтровать данные из исходной модели перед предоставлением их представлениям, а также позволяет предоставить представлениям данные из исходной модели в виде предварительно отсортированных данных.

Пользовательские фильтрующие модели

Класс QSortFilterProxyModel предоставляет достаточно универсальную модель фильтрации, которую можно использовать в различных распространённых ситуациях. Для опытных пользователей QSortFilterProxyModel может быть расширен, обеспечивая механизм, который позволяет реализовывать пользовательские фильтры.

Подклассы QSortFilterProxyModel могут переопределять две виртуальные функции, которые вызываются всякий раз, когда запрашивается или используется индекс модели из модельного прокси:

  • filterAcceptsColumn() используется для фильтрации определённых столбцов из части исходной модели.
  • filterAcceptsRow() используется для фильтрации определённых строк из части исходной модели.

Стандартные реализации вышеуказанных функций в QSortFilterProxyModel возвращают true, чтобы гарантировать прохождение всех элементов в представления; переопределения этих функций должны возвращать false для фильтрации отдельных строк и столбцов.

Пользовательские сортирующие модели

Экземпляры QSortFilterProxyModel используют встроенную функцию 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 по миллиону раз, но это, очевидно, было бы неэффективно. Вместо этого это можно сигнализировать как одно изменение макета, которое обновляет все необходимые постоянные индексы сразу.

Ленивая загрузка данных модели

Ленивая загрузка данных модели эффективно позволяет откладывать запросы информации о модели до тех пор, пока она фактически не потребуется представлениям.

Некоторые модели должны получать данные из удалённых источников или должны выполнять длительные операции для получения информации о структурировании данных. Поскольку представления, как правило, запрашивают как можно больше информации для точного отображения данных модели, полезно ограничивать количество возвращаемой им информации, чтобы уменьшить ненужные последующие запросы данных.

В иерархических моделях, где поиск количества дочерних элементов заданного элемента является дорогостоящей операцией, полезно гарантировать, что реализация 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(), чтобы гарантировать согласованное поведение модели.

Для обеспечения корректной работы операций перетаскивания важно переопределить следующие функции, удаляющие данные из модели:

  • removeRows()
  • removeRow()
  • removeColumns()
  • removeColumn()

Для получения дополнительной информации о перетаскивании и отпускании с помощью представлений элементов, обратитесь к разделу Использование перетаскивания и отпускания с представлениями элементов.

Представления по умолчанию

Представления по умолчанию (QListWidget, QTableWidget и QTreeWidget) переопределяют стандартное поведение перетаскивания и отпускания, чтобы обеспечить менее гибкое, но более естественное поведение, подходящее для многих приложений. Например, поскольку чаще данные помещаются в ячейки QTableWidget, заменяя существующее содержимое переносимыми данными, базовая модель устанавливает данные целевых элементов вместо вставки новых строк и столбцов в модель. Более подробную информацию о перетаскивании и отпускании в представлениях по умолчанию можно найти в разделе Использование перетаскивания и отпускания с представлениями элементов.

Оптимизация производительности для больших объемов данных

Функция canFetchMore() проверяет, есть ли у родительского элемента больше доступных данных, и возвращает true или false соответственно. Функция fetchMore() извлекает данные, основанные на указанном родительском элементе. Обе эти функции можно комбинировать, например, в запросе к базе данных, включающем приращения данных для заполнения QAbstractItemModel. Мы переопределяем canFetchMore(), чтобы указать, есть ли больше данных для извлечения, и fetchMore(), чтобы заполнить модель по мере необходимости.

Еще одним примером являются динамически заполняемые древовидные модели, где мы переопределяем fetchMore() при разворачивании ветви в древовидной модели.

Если ваша реализация fetchMore() добавляет строки в модель, вам нужно вызвать beginInsertRows() и endInsertRows(). Кроме того, как canFetchMore(), так и fetchMore() должны быть переопределены, так как их стандартная реализация возвращает false и ничего не делает.

Классы модели и представления

Эти классы используют паттерн проектирования модель-представление, в котором данные (в модели) хранятся отдельно от способа их отображения и обработки пользователем (в представлении).

QAbstractItemModel

Абстрактный интерфейс для классов модели элементов

QAbstractListModel

Абстрактная модель, которая может быть расширена для создания одномерных моделей списков

QAbstractTableModel

Абстрактная модель, которая может быть расширена для создания моделей таблиц

QModelIndex

Используется для определения местоположения данных в модели данных

QPersistentModelIndex

Используется для определения местоположения данных в модели данных

QAbstractProxyModel

Базовый класс для моделей элементов прокси, которые могут выполнять сортировку, фильтрацию или другие задачи по обработке данных

QConcatenateTablesProxyModel

Проксирует несколько моделей источников, конкатенируя их строки

QIdentityProxyModel

Проксирует модель источника без изменений

QItemSelection

Управляет информацией о выделенных элементах в модели

QItemSelectionModel

Отслеживает выделенные элементы представления

QItemSelectionRange

Управляет информацией о диапазоне выделенных элементов в модели

QSortFilterProxyModel

Поддержка сортировки и фильтрации данных, передаваемых между другой моделью и представлением

QStringListModel

Модель, которая предоставляет строки для представлений

QStandardItem

Элемент для использования с классом QStandardItemModel

QStandardItemModel

Общедоступная модель для хранения пользовательских данных

QAbstractItemDelegate

Используется для отображения и редактирования элементов данных из модели

QAbstractItemView

Основные возможности для классов представления элементов

QColumnView

Реализация модели/представления представления столбцов

QDataWidgetMapper

Сопоставление между частью модели данных и виджетами

QHeaderView

Строка заголовка или столбец заголовка для представлений элементов

QItemDelegate

Средства отображения и редактирования элементов данных из модели

QItemEditorCreator

Позволяет создавать создатели редакторов элементов без наследования QItemEditorCreatorBase

QItemEditorCreatorBase

Абстрактный базовый класс, который должен быть расширен при реализации новых создателей редакторов элементов

QItemEditorFactory

Виджеты для редактирования данных элементов в представлениях и делегатах

QStandardItemEditorCreator

Возможность регистрации виджетов без необходимости наследования QItemEditorCreatorBase

QListView

Представление списка или значков на модели

QListWidget

Представление списка на основе элементов

QListWidgetItem

Элемент для использования с представлением элементов QListWidget

QStyledItemDelegate

Средства отображения и редактирования элементов данных из модели

QTableView

Стандартная реализация представления таблицы на основе модели/представления

QTableWidget

Представление таблицы на основе элементов с моделью по умолчанию

QTableWidgetItem

Элемент для использования с классом QTableWidget

QTableWidgetSelectionRange

Способ взаимодействия с выделением в модели без использования индексов модели и модели выделения

QTreeView

Стандартная реализация представления дерева на основе модели/представления

QTreeWidget

Представление дерева, использующее предопределённую модель дерева

QTreeWidgetItem

Элемент для использования с удобным классом QTreeWidget

QTreeWidgetItemIterator

Способ перебора элементов в экземпляре QTreeWidget

QFileSystemModel

Модель данных для локальной файловой системы

Примеры, относящиеся к теме

  • Представление каталога
  • Делегат поля ввода чисел
  • Пикселизатор
  • Простая модель дерева
  • График

См. также Пример головоломки для представлений элементов.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/model-view-programming.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API