среда, 6 ноября 2019 г.

03.01 Model/View в Qt. Концепции.

Программирование Model/View в Qt 

Введение в программирование Model/View

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


Архитектура model/view
Шаблон Model-View-Controller (MVC) первоначально был разработан на Smalltalk, он часто применяется при построение пользовательского интерфейса. В Design Patterns, Gamma и др. написано:
MVC содержит три типа объектов. Model - объект приложения, View - это экранное представление, а Controller определяет, как интерфейс пользователя способ реагирования на ввод пользователя. До появления MVC, интерфейс пользователя включал все эти компоненты вместе. В MVC их разделили, чем повысили гибкость и возможность повторного использования.



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

Архитектура model/view
Модель взаимодействует с источником данных, предоставляя интерфейс для других компонентов архитектуры. Характер связи зависит от типа источника данных и способа реализации модели.
Представление (view) от модели(model) получает модельные индексы; которые являются ссылками на элементы данных. Предоставляя модельные индексы модели, представление может извлекать элементы данных из источника данных. При редактировании элемента, делегат связывается с моделью напрямую, с помощью модельных индексов.
Обычно классы model/view можно разделить на три три группы, описанные выше: подели, представления, и делегаты. Каждый из этих компонентов определен абстрактными классами, которые предоставляют  общие интерфейсы и, в некоторых случаях, реализацию возможностей по умолчанию. Абстрактные классы необходимо сабклассировать (создать наследника) с целью обеспечения полного набора возможностей, ожидаемых другими компонентами; это также позволяет создавать специализированные компоненты.
Модели, представления и делегаты взаимодействуют между собой с помощью сигналов и слотов:
  • Сигналы от модели информируют представления об изменениях в данных, хранящихся в источниках данных.
  • Сигналы от представления предоставляют информацию о том, как пользователи взаимодействуют с отображаемыми элементами.
  • Сигналы от делегата используются при редактировании, чтобы сообщать модели и представлению о состоянии редактора.

Модели

Все элементы моделей основаны на абстрактном классе QAbstractItemModel. Этот класс определяет интерфейс, который используется представлениями и делегатами для доступа к данным. Сами данные в модели храниться не должны; они могут быть помещены в структуру данных или в хранилище, реализованном отдельным классом, файлом, базой данных или другим компонентом приложения.
Основные понятия, связанные с моделями, описаны в Model Classes.
Класс QAbstractItemModel предоставляет интерфейс к данным, который достаточно гибок для обработки представлениями, которые представляют данные в форме таблиц, списков и деревьев. Тем не менее, при реализации новых моделей для списков или табличных структур данных, лучше воспользоваться классами QAbstractListModel и QAbstractTableModel, потому что они предоставляют реализации по умолчанию для стандартных функций. Каждый их этих классов может быть сабклассирован для реализации моделей, которые будут функционировать в соответствии с вашими требованиями для списков и таблиц.
Процесс сабклассирования моделей обсуждается в Creating New Models.
Qt предоставляет несколько готовых к применению моделей, которые могут быть использованы  для обработки элементов данных:
  • QStringListModel - используется для хранения простого списка элементов типа QString.
  • QStandardItemModel - управляет боле сложными древовидными структурами элементов, каждый из которых может содержать произвольные данные.
  • QFileSystemModel - предоставляет информацию о файлах и директориях локальной файловой системы.
  • QSqlQueryModelQSqlTableModel, и QSqlRelationalTableModel - используются для доступа к базам данных с помощью архитектуры model/view.
Если эти стандартные модели не соответствуют вашим требованиям, вы можете  сабклассировать QAbstractItemModelQAbstractListModel, или QAbstractTableModel и создать собственные модели.

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

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

QAbstractItemDelegate - абстрактный базовый класс для делегатов в система model/view. Реализация делегатов по умолчанию представлена классом QStyledItemDelegate, который используется как делегат по умолчанию стандартными представлениями Qt's.
Тем не менее, классы QStyledItemDelegate и QItemDelegate являются независимыми альтернативными для отрисовки в редакторах элементов представлений. Разница между ними заключается в том, что класс QStyledItemDelegate использует текущий стиль отрисовки элементов. Поэтому мы рекомендуем  в качестве базового класс QStyledItemDelegate как основу при реализации кастомных делегатов или при работе с таблицами стилей Qt.
Делегаты описаны в разделе Delegate Classes.

Сортировка

В архитектуре model/view можно применить два способа сортировки; какой вам лучше подходит - зависит от выбранной базовой модели.
Если модель сортируемая, например, если она переопределяет метод QAbstractItemModel::sort(), то и QTableView, и QTreeView предоставляют API, который позволяет вам сортировать данные модели программно. Дополнительно, вы можете разрешить интерактивную сортировку (например, позволив пользователям сортировать данные, кликая по заголовкам представлений), путем соединения сигнала QHeaderView::sortIndicatorChanged() со слотом QTableView::sortByColumn() или со слотом QTreeView::sortByColumn(), соответственно.
Альтернативный подход, если ваша модель не имеет требуемого интерфейса или если вы хотите использовать список для представления, заключается в использовании прокси-модели для преобразования структуры вашей модели перед передачей данных в представление. Поход подробно описан в разделе Proxy Models.

Удобные готовые классы 

Есть ряд готовых классов, порожденных от стандартных классов представлений , которые предназначены для использования в качестве элементных представлений и табличных классов. Они не предназначены для саклассинга.
Примером таких классов являются QListWidgetQTreeWidget, и QTableWidget.
Эти классы мене гибкие по сравнению в классами представлений и не могут быть использованы с произвольными моделями. Рекомендуется использовать подход model/view для обработки данных в представлениях, если нет надобности в классах на основе набора элементов.

Если хотите воспользоваться возможностями, предоставляемыми методом model/view, при использовании интерфейса на основе элементов, рассмотрите возможность использования классов представлений, таких как QListView , QTableView и QTreeView с QStandardItemModel.
Использование Моделей и Представлений

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

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

В Qt представлены две модели: QStandardItemModel и QFileSystemModelQStandardItemModel - многоцелевая модель, которая может быть использована для представления различных структур данных, необходимых для списков, таблиц и древовидных представлений. Эта модель содержит элементы данных.  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 ; мы передаем ему каталог, а он возвращает модельный индекс. Модельные индексы описаны в Model Classes.
Остальная часть функции просто отображает представления в виджете "сплиттер" и запускает цикл событий приложения:
    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}
В приведенном выше примере мы не упомянули, как обрабатывать выбор (selection) элементов. Этот вопрос более подробно рассматривается в разделе Handling Selections in Item Views.

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

Прежде чем исследовать, как обрабатывается выбор (selection), полезно изучить концепции, используемые в структуре model/view.

Базовые концепции

В архитектуре model/view, модель предоставляет стандартный интерфейс для представления и делегатов для доступа к данным. В 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);
Роли элементов
Роль указывают модели, на какой тип данных ссылаться. Представления могут отображать роли различными способами, поэтому важно предоставить соответствующую информацию для каждой роли.
Раздел Creating New Models  более подробно описывает некоторые конкретные варианты применения ролей,

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

Резюме

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


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

Чтобы продемонстрировать, как данные могут быть извлечены из модели с использованием модельных индексов, настроим 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();
        // Отображение текста в виджете.

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

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


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

    Концепции

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


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

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

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

    Некоторые представления, такие как 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);
    Использование сигналов и слотов в архитектуре model/view означает, что изменение в модели могут распространяться на все присоединенные представления, гарантируя, что мы мы получаем доступ к одним и тем же данным независимо от используемого представления.
    На картинке выше показаны два разных представления одной и той же модели, в каждом выбрано несколько элементов. Несмотря на то, что данные из модели в каждом представлении отображаются согласованно, каждое представление имеет собственную модель выборки. Это может быть полезно в некоторых случаях, однако во многих случаях более желательна разделяемая модель выборки.

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

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

    Как правило, если вы не создаете подкласс для модели или представления, вам не нужно напрямую манипулировать содержимым выборок. Тем не менее, интерфейс к модели выборки может быть доступен, если требуется, и это исследуется в разделе Обработка выбора в представлениях элементов .
    Обычно, если вы не создаете подкласса модели или представления, вам не нужно напрямую манипулировать содержимым выборок. Тем не менее, интерфейс к модели выборки может при необходимости получен, об этом - в Handling Selections in Item Views.

    Общая выборка для видов

    Несмотря на то, то классы представлений по умолчанию предоставляют модель выборок по умолчанию, часто желательно, чтобы данные модели и выборки были одинаковыми во всех представлениях. Поскольку классы представлений позволяют заменять их внутренние модели выборки, мы можем добиться одинаковой выборки между представлениями следующей строкой кода:
        secondTableView->setSelectionModel(firstTableView->selectionModel());
    Второму представлению передается модель выборки от первого представления. Теперь оба представления работают с одной и той же моделью выборки, синхронно отображая как данные, так и выбранные элементы.
    В приведенном выше примере два представления одного типа использовались для отображения данных одной и той же модели. Однако, если использовались два разных типа представления, выбранные элементы могут быть представлены очень по-разному в каждом представлении; например, непрерывный выбор в табличном представлении может быть представлен как фрагментированный набор выделенных элементов в древовидном представлении.

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

    Концепции

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

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

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

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

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

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

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

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

    Реализованный здесь делегат для предоставления средств редактирования использует QSpinBox и в основном предназначен для использования с моделями, отображающими целые числаХотя для этой цели мы создали собственную модель таблиц целых чисел, мы могли бы вместо нее использовать стандартную QStandardItemModel, т.к. наш делегат контролирует ввод данных. Мы создаем табличное представление для отображения содержимого модели, которое  для редактирования будет использовать пользовательский делегат. 
    Наследуем наш класс делегата от QStyledItemDelegate, так как мы не хотим описывать функции отображения. Однако, нам все еще нужно предоставить функции для управления виджетом редактирования:
    class SpinBoxDelegate : public QStyledItemDelegate
    {
        Q_OBJECT
    
    public:
        SpinBoxDelegate(QObject *parent = nullptr);
    
        QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
    
        void setEditorData(QWidget *editor, const QModelIndex &index) const override;
        void setModelData(QWidget *editor, QAbstractItemModel *model,
                          const QModelIndex &index) const override;
    
        void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                                  const QModelIndex &index) const override;
    };
    Заметим, что в момент конструирования делегата виджет редактирования не настраивается. Виджет редактирования мы создаем лишь тогда, когда он нужен.
    Предоставление редактора


    В данном примере, когда табличному представлению нужен редактор, оно просит делегат предоставить виджет редактирования, подходящий для элемента, который будет изменяться. Функция createEditor() поставляется со всем, что нужно, чтобы делегат мог настроить подходящий виджет:
    QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                           const QStyleOptionViewItem &/* option */,
                                           const QModelIndex &/* index */) const
    {
        QSpinBox *editor = new QSpinBox(parent);
        editor->setFrame(false);
        editor->setMinimum(0);
        editor->setMaximum(100);
    
        return editor;
    }
    Отметим, что нет надобности хранить указатель на виджет редактора, так как за уничтожение виджета отвечает представление, которое уничтожит его, когда в нем отпадет необходимость.
    Для делегата в редакторе мы устанавливаем фильтр событий по умолчанию, чтобы быть уверенными в том, что для редактирования можно будет использовать стандартные комбинаций клавиш, которые ожидают пользователи. Дополнительные комбинации клавиш могут быть добавлены к редактору, чтобы реализовать более сложное поведение, это описано в Editing Hints.
    Представление гарантирует, что редактируемые данные и геометрия редактора установлены правильно, вызывая функции, которые мы определим позже для этих целей. Мы можем создавать разные редакторы в зависимости от модельного индекса, представленного представлением. Например, если у нас есть столбец целых чисел и столбец строк, мы можем вернуть либо QSpinBoxлибо, либо QLineEdit, в зависимости от того, какой столбец редактируется.


    Делегат должен предоставить функцию для копирования данных модели в редактор. В этом примере мы читаем данные, хранящиеся в display role, и, соответственно устанавливаем значение в поле спинбокса.
    void SpinBoxDelegate::setEditorData(QWidget *editor,
                                        const QModelIndex &index) const
    {
        int value = index.model()->data(index, Qt::EditRole).toInt();
    
        QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
        spinBox->setValue(value);
    }
    В этом примере мы знаем, что виджет редактора является спинбоксом, но мы могли бы предоставить разные редакторы для разных типов данных в модели, в этом случае прежде чем получить доступ к его функциям-членам, нам нужно было бы привести виджет к соответствующему типу.

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

    Когда пользователь завершил редактирование значения в поле прокрутки , представление просит делегата сохранить отредактированное значение в модели, вызвав функцию setModelData() .
    void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                       const QModelIndex &index) const
    {
        QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
        spinBox->interpretText();
        int value = spinBox->value();
    
        model->setData(index, value, Qt::EditRole);
    }
    Так как представление управляет виджетами редактирования для делегата, нам нужно лишь  обновить модель с помощью содержимого применяемого редактора. В это м случае мы можем быть уверены. что и спинбокс и модель обновлены значением, которое мы получаем с помощью модельного индекса.
    Стандартный класс QStyledItemDelegate информирует представление о завершении редактирования, генерируя сигнал closeEditor(). Представление гарантирует закрытие и уничтожение виджета редактирования. Так как в этом примере мы предоставляем лишь простые средства редактирования, поэтому нам никогда не нужно генерировать этот сигнал.

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

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

    Ответственность за управление геометрией редактора лежит на делегате. Геометрия должна быть установлена ​​при создании редактора и при изменении размера или положения элемента в представлении. К счастью, представление предоставляет всю необходимую информацию о геометрии внутри объекта view option.
    void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                               const QStyleOptionViewItem &option,
                                               const QModelIndex &/* index */) const
    {
        editor->setGeometry(option.rect);
    }

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

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

    Комментариев нет:

    Отправить комментарий

    QSettings: работа с ini файлом, русские символы.

      QSettings работает в т.ч. и с ini файлами. Локальные (русские, например) символы обрабатываются с помощью кодеков. Наверное, ini файл сле...