Spec-Zone.ru › Qt 5.6

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

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

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's item-based item view и table classes. Они не предназначены для наследования.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Основные понятия

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

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

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

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

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

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

QAbstractItemModel *model = index.model();

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

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

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

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

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

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

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

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

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

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

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

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

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

QModelIndex index = model->index(row, column, parent);
Родители, строки и столбцы

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

Элементы «A» и «C» представлены как элементы-родственники верхнего уровня в модели:

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

Элемент «A» имеет несколько дочерних элементов. Индекс модели для элемента «B» получается с помощью следующего кода:

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

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

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

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

QVariant value = model->data(index, role);
Роли элементов

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

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

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

Резюме

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

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

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

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

    QFileSystemModel *model = new QFileSystemModel;
    QModelIndex parentIndex = model->index(QDir::currentPath());
    int numRows = model->rowCount(parentIndex);

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

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

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

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

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

    }

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

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

Дополнительная литература

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

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

QAbstractItemModel *model = new StringListModel(numbers);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = 0);

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

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

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

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

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

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

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

    return editor;
}

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

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

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

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

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

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

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

Передача данных модели

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    QItemSelectionModel *selectionModel = table->selectionModel();

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

    QModelIndex topLeft;
    QModelIndex bottomRight;

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

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

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

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

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

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

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

    QModelIndexList indexes = selectionModel->selectedIndexes();
    QModelIndex index;

    foreach(index, indexes) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

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

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

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

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

    foreach (index, items) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

    items = deselected.indexes();

    foreach (index, items)
        model->setData(index, "");
}

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

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

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

Выбор всех элементов в модели

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

    QModelIndex topLeft = model->index(0, 0, parent);
    QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
        model->columnCount(parent)-1, parent);

Выбор создается с этими индексами и моделью. Соответствующие элементы затем выбираются в модели выбора:

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

Это необходимо выполнить для всех уровней в модели. Для элементов верхнего уровня мы определим индекс родителя обычным способом:

    QModelIndex parent = QModelIndex();

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

Создание новых моделей

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

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

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

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

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

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

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

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

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

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

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

Пример модели только для чтения

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

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

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

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

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

private:
    QStringList stringList;
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    endInsertRows();
    return true;
}

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

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

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

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

    endRemoveRows();
    return true;
}

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

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

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

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

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

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

Виджеты списков

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

    QListWidget *listWidget = new QListWidget(this);

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

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

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

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

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

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

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

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

Виджеты деревьев

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

    QTreeWidget *treeWidget = new QTreeWidget(this);

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

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

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

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

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

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

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

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

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

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

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

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

Виджеты таблиц

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

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

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

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

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

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

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

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

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

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

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

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

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

Выбор

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

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

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

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

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

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

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

    foreach (item, selected) {
        bool ok;
        double value = item->text().toDouble(&ok);

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

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

Поиск

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

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

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

    foreach (item, found) {
        treeWidget->setItemSelected(item, true);
        // Show the item->text(0) for each item.
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (column > 0)
        return false;

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

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

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

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

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

    int beginRow;

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

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

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

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

    else
        beginRow = rowCount(QModelIndex());

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

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

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

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

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

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

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

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

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

    return true;
}

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

Модели-прокси

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Справочник по наследованию от моделей

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

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

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

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

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

Только чтение

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

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

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

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

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

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

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

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

Изменяемые модели

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

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

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

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

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

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

class="generic" width="70%">
insertRows() Эти функции позволяют модели автоматически вставлять новые данные, используя существующую реализацию, предоставляемую QAbstractItemModel::dropMimeData().
insertColumns()
setData() Позволяет заполнять новые строки и столбцы элементами.
setItemData() Эта функция обеспечивает более эффективную поддержку заполнения новых элементов.

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

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

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

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

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

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

Удобные представления

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

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

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

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

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

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

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

QAbstractItemModel

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

QAbstractListModel

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

QAbstractTableModel

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

QModelIndex

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

QPersistentModelIndex

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

QAbstractProxyModel

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

QIdentityProxyModel

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

QItemSelection

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

QItemSelectionModel

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

QItemSelectionRange

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

QSortFilterProxyModel

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

QStringListModel

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

QStandardItem

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

QStandardItemModel

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

QFileSystemModel

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

QAbstractItemDelegate

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

QAbstractItemView

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

QColumnView

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

QDataWidgetMapper

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

QHeaderView

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

QItemDelegate

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

QItemEditorCreator

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

QItemEditorCreatorBase

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

QItemEditorFactory

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

QStandardItemEditorCreator

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

QListView

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

QListWidget

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

QListWidgetItem

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

QStyledItemDelegate

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

QTableView

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

QTableWidget

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

QTableWidgetItem

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

QTableWidgetSelectionRange

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

QTreeView

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

QTreeWidget

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

QTreeWidgetItem

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

QTreeWidgetItemIterator

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

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

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

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

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

Spec-Zone.ru

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