Spec-Zone.ru › Qt 6.0

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

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

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

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

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

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

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

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

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

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

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

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

Модели, представления и делегаты взаимодействуют друг с другом с помощью сигналов и слотов:

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

Модели

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

Основные концепции, связанные с моделями, представлены в разделе Классы моделей.

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

Процесс наследования моделей обсуждается в разделе Создание новых моделей.

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

  • QStringListModel используется для хранения простого списка элементов QString.
  • QStandardItemModel управляет более сложными древовидными структурами элементов, каждый из которых может содержать произвольные данные.
  • QFileSystemModel предоставляет информацию о файлах и каталогах в локальной файловой системе.
  • QSqlQueryModel, QSqlTableModel и QSqlRelationalTableModel используются для доступа к базам данных с использованием соглашений модели/представления.

Если стандартные модели не удовлетворяют вашим требованиям, вы можете создать собственную модель, унаследовав от QAbstractItemModel, QAbstractListModel или QAbstractTableModel.

Представления

Представлены полные реализации для разных типов представлений: QListView отображает список элементов, QTableView отображает данные из модели в таблице, а QTreeView отображает элементы модели данных в иерархическом списке. Каждый из этих классов основан на абстрактном базовом классе QAbstractItemView. Хотя эти классы являются готовыми реализациями, их также можно наследоваться для предоставления настраиваемых представлений.

Доступные представления рассматриваются в разделе Классы представлений.

Делегирование

QAbstractItemDelegate — это абстрактный базовый класс для делегатов в рамках модели/представления. Реализация делегата по умолчанию предоставляется классом QStyledItemDelegate, и он используется в качестве делегата по умолчанию стандартными представлениями Qt. Однако QStyledItemDelegate и QItemDelegate — это независимые альтернативы для рисования и предоставления редакторов для элементов в представлениях. Разница между ними заключается в том, что QStyledItemDelegate использует текущий стиль для рисования своих элементов. Поэтому мы рекомендуем использовать QStyledItemDelegate в качестве базового класса при реализации пользовательских делегатов или при работе со стилями Qt.

Делегаты описаны в разделе Классы делегатов.

Сортировка

Существует два способа реализации сортировки в архитектуре модель/представление; выбор способа зависит от вашей базовой модели.

Если ваша модель поддерживает сортировку, то есть, если она переопределяет функцию QAbstractItemModel::sort(), как QTableView, так и QTreeView предоставляют API, позволяющий программно сортировать данные модели. Кроме того, вы можете включить интерактивную сортировку (то есть, позволяющую пользователям сортировать данные по щелчку заголовков представления), подключив сигнал QHeaderView::sortIndicatorChanged() к слоту QTableView::sortByColumn() или QTreeView::sortByColumn() соответственно.

Альтернативный подход, если ваша модель не имеет требуемого интерфейса или если вы хотите использовать представление списка для отображения данных, заключается в использовании модели-прокси для преобразования структуры вашей модели перед отображением данных в представлении. Это подробно описано в разделе Модели-прокси.

Классы-утилиты

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

Примеры таких классов включают QListWidget, QTreeWidget и QTableWidget.

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

Если вы хотите воспользоваться преимуществами подхода модель/представление, сохраняя при этом интерфейс на основе элементов, используйте классы представлений, такие как QListView, QTableView и QTreeView с QStandardItemModel.

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

Следующие разделы объясняют, как использовать шаблон модель/представление в Qt. Каждый раздел включает пример и за ним следует раздел, показывающий, как создать новые компоненты.

Две модели, включённые в Qt

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

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

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

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

Мы создаём QFileSystemModel, чтобы он был готов к использованию, и создаём несколько представлений для отображения содержимого каталога. Это демонстрирует самый простой способ использования модели. Создание и использование модели выполняется внутри одной main() функции:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QSplitter *splitter = new QSplitter;

    QFileSystemModel *model = new QFileSystemModel;
    model->setRootPath(QDir::currentPath());

Модель настроена для использования данных из определённой файловой системы. Вызов setRootPath() сообщает модели, какой диск файловой системы необходимо отобразить для представлений.

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

    QTreeView *tree = new QTreeView(splitter);
    tree->setModel(model);
    tree->setRootIndex(model->index(QDir::currentPath()));

    QListView *list = new QListView(splitter);
    list->setModel(model);
    list->setRootIndex(model->index(QDir::currentPath()));

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

Функция index() , используемая в данном случае, уникальна для QFileSystemModel; мы передаём ей каталог, и она возвращает индекс модели. Индексы моделей обсуждаются в разделе Классы моделей.

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

    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}

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

Классы моделей

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

Основные концепции

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

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

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

Индексы моделей

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

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

QAbstractItemModel *model = index.model();

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

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

Строки и столбцы

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

QModelIndex index = model->index(row, column, ...);

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

Строки и столбцы

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

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

Элементы верхнего уровня в модели всегда ссылаются на указание QModelIndex() в качестве родительского элемента. Это рассматривается в следующем разделе.

Родители элементов

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

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

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

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

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

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

Мы создаём модель файловой системы следующим образом:

    QFileSystemModel *model = new QFileSystemModel;
    connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
        QModelIndex parentIndex = model->index(directory);
        int numRows = model->rowCount(parentIndex);
    });
    model->setRootPath(QDir::currentPath);

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

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

    for (int row = 0; row < numRows; ++row) {
        QModelIndex index = model->index(row, 0, parentIndex);

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

        QString text = model->data(index, Qt::DisplayRole).toString();
        // Display the text in a widget.

    }

Приведённый выше пример демонстрирует основные принципы извлечения данных из модели:

  • Размеры модели можно найти, используя rowCount() и columnCount(). Эти функции обычно требуют указания индекса родительской модели.
  • Индексы модели используются для доступа к элементам в модели. Для указания элемента необходимы строка, столбец и индекс родительской модели.
  • Для доступа к элементам верхнего уровня в модели укажите нулевой индекс модели в качестве родительского индекса с помощью QModelIndex().
  • Элементы содержат данные для разных ролей. Для получения данных для определенной роли модели необходимо предоставить как индекс модели, так и роль.

Дополнительная информация

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

Классы представлений

Концепции

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

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

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

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

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

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

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

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

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

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

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new StringListModel(numbers);

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

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

QListView *view = new QListView;
view->setModel(model);

Представление отображается обычным способом:

    view->show();
    return app.exec();
}

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

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

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

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

    QTableView *firstTableView = new QTableView;
    QTableView *secondTableView = new QTableView;

    firstTableView->setModel(model);
    secondTableView->setModel(model);

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

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

Обработка выборов элементов

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

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

Общий выбор между представлениями

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

    secondTableView->setSelectionModel(firstTableView->selectionModel());

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

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

Классы делегатов

Концепции

В отличие от шаблона Model-View-Controller, дизайн модели/представления не включает полностью отдельный компонент для управления взаимодействием с пользователем. Как правило, представление отвечает за представление данных модели пользователю и за обработку пользовательского ввода. Чтобы обеспечить некоторую гибкость в способе получения этого ввода, взаимодействие выполняется с помощью делегатов. Эти компоненты обеспечивают возможности ввода и также отвечают за отображение отдельных элементов в некоторых представлениях. Стандартный интерфейс для управления делегатами определен в классе QAbstractItemDelegate.

Ожидается, что делегаты смогут сами отображать свое содержимое, реализуя функции paint() и sizeHint(). Однако простые делегаты на основе виджетов могут вместо QAbstractItemDelegate унаследовать QStyledItemDelegate и воспользоваться реализациями этих функций по умолчанию.

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

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

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

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

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

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

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

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

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

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = nullptr);

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

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

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

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

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

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

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

    return editor;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Совета по редактированию

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

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

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

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

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

Выделение элементов

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

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

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

    QItemSelectionModel *selectionModel = table->selectionModel();

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

    QModelIndex topLeft;
    QModelIndex bottomRight;

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

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

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

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

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

Чтение состояния выделения

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

    const QModelIndexList indexes = selectionModel->selectedIndexes();

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

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

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

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

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

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

    items = deselected.indexes();

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

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

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

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

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

Creating New Models

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

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

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

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

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

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

Designing a model

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

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

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

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

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    endInsertRows();
    return true;
}

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

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

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

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

    endRemoveRows();
    return true;
}

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

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

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

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

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

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

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

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

    QListWidget *listWidget = new QListWidget(this);

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

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

Их также можно создать без родительского виджета списка и добавить в список в какой-то момент позже:

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

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

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

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

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

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

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

    QTreeWidget *treeWidget = new QTreeWidget(this);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Общие возможности

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

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

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

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

Выбор

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

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

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

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

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

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

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

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

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

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

Поиск

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (column > 0)
        return false;

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

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

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

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

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

    int beginRow;

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

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

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

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

    else
        beginRow = rowCount(QModelIndex());

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

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

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

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

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

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

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

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

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

    return true;
}

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

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

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

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

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

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

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

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

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

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

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

Настройка прокси-моделей

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

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

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

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

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

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

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

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

Экземпляры QSortFilterProxyModel используют функцию std::stable_sort() для создания отображений между элементами в исходной модели и элементами в прокси-модели, что позволяет отобразить отсортированную иерархию элементов представлениям без изменения структуры исходной модели. Для обеспечения пользовательского поведения сортировки переопределите функцию lessThan() для выполнения пользовательских сравнений.

Ссылка на наследование моделей

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

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

  • Обработка данных элементов: Все модели должны реализовывать функции, позволяющие представлениям и делегатам запрашивать размеры модели, проверять элементы и извлекать данные.
  • Навигация и создание индексов: Иерархические модели должны предоставлять функции, которые представления могут вызывать для навигации по древовидным структурам, которые они предоставляют, и получения индексов модели для элементов.
  • Поддержка перетаскивания и обработка типов MIME: Модели наследуют функции, которые управляют способом выполнения внутренних и внешних операций перетаскивания. Эти функции позволяют описывать элементы данных с точки зрения типов MIME, которые могут понимать другие компоненты и приложения.

Обработка данных элементов

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

Доступ только для чтения

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

flags() Используется другими компонентами для получения информации о каждом элементе, предоставляемом моделью. Во многих моделях комбинация флагов должна включать Qt::ItemIsEnabled и Qt::ItemIsSelectable.
data() Используется для предоставления данных элементов представлениям и делегатам. Как правило, модели должны предоставлять данные только для Qt::DisplayRole и любых прикладных пользовательских ролей, но также рекомендуется предоставлять данные для Qt::ToolTipRole, Qt::AccessibleTextRole и Qt::AccessibleDescriptionRole. См. документацию по перечислению Qt::ItemDataRole для получения информации о типах, связанных с каждой ролью.
headerData() Предоставляет представлениям информацию для отображения в своих заголовках. Информация извлекается только представлениями, которые могут отображать информацию о заголовке.
rowCount() Предоставляет количество строк данных, предоставляемых моделью.

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

Кроме того, следующие функции обязательно должны быть реализованы в прямых подклассах QAbstractTableModel и QAbstractItemModel:

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

Редактируемые элементы

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

flags() Должен возвращать соответствующую комбинацию флагов для каждого элемента. В частности, возвращаемое значение этой функции должно включать Qt::ItemIsEditable в дополнение к значениям, применённым к элементам в модели только для чтения.
setData() Используется для изменения элемента данных, связанного с указанным индексом модели. Для возможности принять пользовательский ввод, предоставленный элементами пользовательского интерфейса, эта функция должна обрабатывать данные, связанные с Qt::EditRole. Реализация также может принимать данные, связанные со многими различными типами ролей, указанными в Qt::ItemDataRole. После изменения элемента данных модели должны испускать сигнал dataChanged(), чтобы проинформировать другие компоненты об изменении.
setHeaderData() Используется для изменения информации о горизонтальных и вертикальных заголовках. После изменения элемента данных модели должны испускать сигнал headerDataChanged(), чтобы проинформировать другие компоненты об изменении.

Изменяемые размеры моделей

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

insertRows() Используется для добавления новых строк и элементов данных во все типы моделей. Реализации должны вызывать beginInsertRows() перед вставкой новых строк в любые базовые структуры данных и вызывать endInsertRows() непосредственно после.
removeRows() Используется для удаления строк и содержащихся в них элементов данных из всех типов моделей. Реализации должны вызывать beginRemoveRows() перед удалением строк из любых базовых структур данных и вызывать endRemoveRows() непосредственно после.
insertColumns() Используется для добавления новых столбцов и элементов данных в табличные модели и иерархические модели. Реализации должны вызывать beginInsertColumns() перед вставкой новых столбцов в любые базовые структуры данных и вызывать endInsertColumns() непосредственно после.
removeColumns() Используется для удаления столбцов и содержащихся в них элементов данных из табличных моделей и иерархических моделей. Реализации должны вызывать beginRemoveColumns() перед удалением столбцов из любых базовых структур данных и вызывать endRemoveColumns() непосредственно после.

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

Сигналы, испускаемые функциями, используемыми в реализациях API изменения размера, дают присоединённым компонентам возможность предпринять действия до того, как какие-либо данные станут недоступными. Использование функций begin и end для операций вставки и удаления также позволяет модели правильно управлять постоянными индексами модели.

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

  • Издать сигнал layoutAboutToBeChanged()
  • Обновить внутренние данные, представляющие структуру модели.
  • Обновить постоянные индексы с помощью changePersistentIndexList()
  • Издать сигнал layoutChanged().

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

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

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

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

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

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

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

Создание навигации и индекса модели

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

Родители и дети

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

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

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

Поддержка перетаскивания и обработка типов MIME

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

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

Данные MIME

По умолчанию встроенные модели и представления используют внутренний тип MIME (application/x-qabstractitemmodeldatalist) для передачи информации об индексах моделей. Это указывает данные для списка элементов, содержащих номера строки и столбца каждого элемента и информацию о ролях, которые поддерживает каждый элемент.

Данные, закодированные с помощью этого типа MIME, могут быть получены путем вызова QAbstractItemModel::mimeData() с QModelIndexList, содержащей элементы, которые необходимо сериализовать.

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

mimeData() Эту функцию можно переопределить, чтобы возвращать данные в форматах, отличных от внутреннего типа MIME по умолчанию application/x-qabstractitemmodeldatalist.

Подклассы могут получить объект QMimeData по умолчанию от базового класса и добавить данные в дополнительных форматах.

Для многих моделей полезно предоставить содержимое элементов в общем формате, представленном типами MIME, такими как text/plain и image/png. Обратите внимание, что изображения, цвета и HTML-документы можно легко добавить в объект QMimeData с помощью функций QMimeData::setImageData(), QMimeData::setColorData() и QMimeData::setHtml().

Прием опущенных данных

При выполнении операции перетаскивания над представлением модель запрашивает, какие типы операций она поддерживает и какие типы MIME она может принимать. Эта информация предоставляется функциями QAbstractItemModel::supportedDropActions() и QAbstractItemModel::mimeTypes(). Модели, которые не переопределяют реализации, предоставляемые QAbstractItemModel, поддерживают операции копирования и внутренний тип MIME по умолчанию для элементов.

При опускании сериализованных данных элемента на представление данные вставляются в текущую модель с помощью её реализации QAbstractItemModel::dropMimeData(). Реализация по умолчанию этой функции никогда не перезаписывает данные в модели; вместо этого она пытается вставить элементы данных либо как элементы-сёстры элемента, либо как потомки этого элемента.

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

insertRows() Эти функции позволяют модели автоматически вставлять новые данные, используя существующую реализацию, предоставляемую QAbstractItemModel::dropMimeData().
insertColumns()
setData() Разрешает заполнение новых строк и столбцов элементами.
setItemData() Эта функция обеспечивает более эффективную поддержку заполнения новых элементов.

Для поддержки других типов данных, эти функции должны быть переопределены:

supportedDropActions() Используется для возвращения комбинации действий перетаскивания, указывающих типы операций перетаскивания и опускания, которые поддерживает модель.
mimeTypes() Используется для возвращения списка типов MIME, которые могут быть декодированы и обработаны моделью. Обычно типы MIME, которые поддерживаются для ввода в модель, совпадают с теми, которые модель может использовать при кодировании данных для использования внешними компонентами.
dropMimeData() Выполняет фактическое декодирование данных, переданных операциями перетаскивания и опускания, определяет, куда в модели они будут помещены, и вставляет новые строки и столбцы при необходимости. Как эта функция реализуется в подклассах, зависит от требований данных, предоставляемых каждой моделью.

Если реализация функции dropMimeData() изменяет размеры модели, вставляя или удаляя строки или столбцы, или если изменяются данные элементов, необходимо позаботиться о том, чтобы все соответствующие сигналы были отправлены. Может быть полезно просто вызвать переопределения других функций в подклассе, таких как setData(), insertRows() и insertColumns(), чтобы обеспечить согласованное поведение модели.

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

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

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

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

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

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

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

Другим примером будут динамически заполняемые модели дерева, где мы переопределяем fetchMore() при разворачивании ветки в модели дерева.

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

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

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

QAbstractItemDelegate

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

QAbstractItemModel

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

QAbstractItemView

Основные функции для классов представлений элементов

QAbstractListModel

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

QAbstractProxyModel

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

QAbstractTableModel

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

QColumnView

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

QConcatenateTablesProxyModel

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

QDataWidgetMapper

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

QFileSystemModel

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

QHeaderView

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

QIdentityProxyModel

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

QItemDelegate

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

QItemEditorCreator

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

QItemEditorCreatorBase

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

QItemEditorFactory

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

QItemSelection

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

QItemSelectionModel

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

QItemSelectionRange

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

QListView

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

QListWidget

Виджет списка на основе элементов

QListWidgetItem

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

QModelIndex

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

QModelRoleData

Содержит роль и данные, связанные с этой ролью

QModelRoleDataSpan

Диапазон объектов QModelRoleData

QPersistentModelIndex

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

QSortFilterProxyModel

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

QStandardItem

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

QStandardItemEditorCreator

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

QStandardItemModel

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

QStringListModel

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

QStyledItemDelegate

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

QTableView

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

QTableWidget

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

QTableWidgetItem

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

QTableWidgetSelectionRange

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

QTreeView

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

QTreeWidget

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

QTreeWidgetItem

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

QTreeWidgetItemIterator

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

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

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

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

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

Spec-Zone.ru

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