Архитектура "Модель-представление"
Модель-представление-контроллер (Model-View-Controller, MVC) — паттерн
проектирования, ведущий свою историю от языка Smalltalk, который часто
используется для построения пользовательских интерфей сов. В книге "Паттерны
проектирования" (Design Patterns, Gamma et. al.) написано следующее:
MVC состоит из трёх видов объектов:
• Модель — объект приложения.
• Представление — отображение приложения на экране.
• Контроллер — реакция на действия пользователя.
До появления MVC архитектура пользовательского интерфейса часто «склеивала» эти объекты вместе, MVC
же разделила их для гибкости и повторного использования кода.
Архитектура "Модель-представление"
Схема архитектуры "модель-представление"
Схема архитектуры "модель-представление"
• Модель обменивается информацией с источником данных и предоставляет
интерфейс остальным компонентам архитектуры. Сама схема обмена
информацией будет зависеть от типа источника данных и конкретной
реализации модели.
• Представление получает от модели индексы, ссылающиеся на элементы данных.
Передавая индексы модели, представление может получать данные из их
источника.
• В стандартных представлениях делегаты ответственны за отображение
конкретных элементов данных. Когда элемент редактируется (например,
пользователем), делегат сообщает об изменениях напрямую модели с помощью
индекса.
В общем, классы архитектуры "Модель-представление" могут быть разделены на
три группы, описанные выше: модели, представления и делегаты. Каждый из этих
компонентов определяется абстрактным классом, предоставляющим
общепринятый интерфей с и, в некоторых случаях, стандартную реализацию
особенностей . Предполагается, что от абстрактных классов будут унаследованы
другие, предоставляющие полную функциональность, требуемую другими
компонентами. Такая схема позволяет создавать специализированные компоненты.
Модели, представления и делегаты общаются между собой с помощью сигналов и
слотов:
• Сигналы модели сообщают представлению об изменениях в данных, хранимых в
источнике данных.
• Сигналы представления содержат информацию о пользовательском
взаимодей ствии с отображаемыми элементами.
• Сигналы делегата используются при редактировании для уведомления модели
и представления о состоянии редактора.
Модели
Все модели основываются на абстрактном классе QAbstractItemModel,
определяющем интерфей с, используемый представлениями и делегатами для
доступа к данным. Модель сама по себе не хранит данные; они могут находиться как
в некоторой структуре данных или хранилище, предоставляемом другим классом,
так и в базе данных, фай ле или в другом компоненте приложения.
Базовые концепции, связанные с моделями, представлены в секции Классы
моделей.
QAbstractItemModel предоставляет для доступа к данным интерфей с, достаточно
гибкий для того, чтобы обслуживать представления, отображающие данные в виде
таблиц, списков и деревьев. Однако, при реализации новых моделей для списков и
древоподобных структур данных, классы QAbstractListModel и QAbstractTableModel
будут лучшими начальными точками, поскольку они уже предоставляют
подходящие реализации основных требуемых функций . Оба этих класса могут быть
расширены с помощью наследования для создания моделей , поддерживающих
специализированные варианты списков и таблиц.
Процесс расширения моделей обсуждается в секции Создание новых моделей.
PyQt предоставляет некоторые готовые модели, подходящие для работы с
элементами данных:
• StringListModel хранит простой список строк (str).
• QStandardItemModel управляет более сложными древоподобными структурами
элементов, каждый из которых может содержать произвольные данные.
• QFileSystemModel предоставляет информацию о фай лах и каталогах локальной
фай ловой системы.
• QSqlQueryModel, QSqlTableModel и QSqlRelationalTableModel используются для
доступа к базам данных с соблюдением соглашения "модель-представление".
Если данные стандартные классы не соответствуют Вашим требованиям, Вы можете
расширить QAbstractItemModel, QAbstractListModel или QAbstractTableModel для
создания собственных произвольных моделей .
Представления
Полноценные реализации предоставлены для различных видов представлений :
• QListView отображает список элементов.
• QTableView отображает данные модели в виде таблицы.
• QTreeView показывает элементы данных, полученных от модели, в виде
иерархического списка.
Каждый из этих классов основан на абстрактном классе QAbstractItemView.
Несмотря на то, что данные классы полностью готовы к использованию, они всё
равно могут быть расширены для создания собственных представлений .
Доступные представления рассматриваются в секции Классы представлений
Сортировка
Существуют два подхода к сортировке в архитектуре "модель-представление",
выбор между которыми основан на модели, лежащей в основе.
В случае, если модель сортируемая (т.е. переопределяет функцию sort()), и
QTableView, и QTreeView предоставляют API, позволяющий программно
отсортировать данные модели. Кроме того, можно включить интерактивную
сортировку, т.е. позволить пользователям сортировать данные, нажимая на
заголовки представления), соединив сигнал sortIndicatorChanged() класса
QHeaderView со слотом sortByColumn() класса QTableView или QTreeView.
Альтернативный подход может применяться в случае, если модель не поддерживает
требуемый интерфей с, или в случае, если Вы хотите отображать данные с
использованием представления списка (например, класса QListView). В таком случае
требуется использовать прокси-модель для преобразования структуры исходной
модели перед отображением данных с помощью представления. Детали такого
подхода подробно описаны в секции Прокси-модели.
Вспомогательные классы
На основе стандартных классов представлений создано несколько вспомогательных
классов для использования в приложениях, опирающихся на элементо-
ориентированные классы представлений PyQt. Данные классы не предназначены
для расширения.
Вспомогательными классами являются, в частности, QListWidget, QTreeWidget и
QTableWidget.
list = QListView(splitter)
list.setModel(model)
list.setRootIndex(model.index(os.getcwd()))
def main():
app = QApplication(sys.argv)
splitter = QSplitter()
splitter.setWindowTitle("Two views onto the same directory")
model = QFileSystemModel()
model.setRootPath(os.getcwd())
tree_view = QTreeView(splitter)
tree_view.setModel(model)
tree_view.setRootIndex(model.index(os.getcwd()))
list_view = QListView(splitter)
list_view.setModel(model)
list_view.setRootIndex(model.index(os.getcwd()))
splitter.show()
return app.exec_()
if __name__ == '__main__':
main()
Классы моделей
Прежде чем рассматривать вопрос обработки выделения элементов, Вам может
оказаться полезным обратиться к концепциям, используемым в фрей мворке
«модель-представление».
Базовые концепции
В архитектуре «модель-представление» модель предоставлеяет стандартный
интерфей с, используемый представлениями и делегатами для доступа к данным. В
PyQt стандарт для такого интерфей са определяется классом QAbstractItemModel. Вне
зависимости от способа хранения данных в структуре данных, на которой основана
модель, все классы-наследники QAbstractItemModel представляют данные как
иерархическую структуру, содержащую таблицы элементов. Представления
используют эту конвенцию для доступа к данным модели, но они не ограничены в
способах представления полученных данных пользователю.
Три вида моделей
Три вида моделей
Модели также уведомляют все прикреплённые к ним представления об изменениях
данных с помощью механизма сигналов и слотов.
Данная секция описывает некоторые основные концепции, лежащие в основе того,
как компоненты программы получают доступ к данным с помощью классов моделей .
Расширения этой схемы обсуждаются в следующих секциях.
Индексы моделей
Чтобы проиллюстрировать, что представление данных отделено от способа доступа
к ним, введём концепцию индексов модели. Каждый фрагмент информации, который
может быть получен с помощью модели, отображается с помощью индекса модели.
Представления и делегаты используют эти индексы для запросов на отображение
элементов данных.
В результате, только модели требуется знать о способе получения данных, и тип
данных, управляемых моделью, может задаваться достаточно широко. Индекс
модели ссылается (строго говоря, содержит указатель) на модель, которой он был
создан, чтобы предотвращать беспорядок при одновременной работе с несколькими
моделями.
model = index.model()
Строки и столбцы
В простей шем случае модель может быть представлена как простая таблица, в
которой элементы расположены в соответствии с номерами их строк и столбцов. Из
этого не следует, что соответствующие фрагменты данных хранятся в массиве (в
смысле структуры данных); использование строк и столбцов является просто
договорённостью для обеспечения взаимодей ствия компонентов программы друг с
другом. Мы можем получить информацию о любом данном элементе, просто указав
модели на его номера строки и столбца, также мы можем получить индекс,
представляющий данный элемент:
index = model.index(row, column, ...)
Строки и столбцы
Диаграмма выше показывает представление базовой модели для таблицы, в
которой каждый элемент расположен в соответствии с парой чисел — номером
строки и номером столбца. Можно получить индекс модели, ссылающий ся на
элемент данных, передав соответствующие номера в модель.
indexA = model.index(0, 0, QModelIndex())
indexB = model.index(1, 1, QModelIndex())
indexC = model.index(2, 1, QModelIndex())
Родительские элементы
При представлении данных в виде таблицы или списка идеальным является
табличный или схожий с табличным интерфей с элементов данных — система
пронумерованных строк и столбцов точно отображает способ, которым
представление отображает данные и их элементы для пользователя. В то же время,
представления, схожие по структуре с деревьями, требуют от модели более гибкого
интерфей са для доступа к хранящимся внутри неё элементам. Следовательно,
каждый элемент может быть родителем другой таблицы элементов, примерно так
же, как элемент самого верхнего уровня дерева может содержать список элементов.
При запросе индекса элемента модели, нам необходимо предоставить некоторую
информацию о родителе элемента. Единственный способ обратиться к элементу,
находясь при этом за пределами модели — с помощью индекса модели,
следовательно, требуется передать индекс родительского элемента:
index = model.index(row, column, parent)
Роли элементов
Элементы модели могут играть различные роли для других компонентов
программы, позволяя в различных ситуациях предоставлять данные различных
типов. Например, DisplayRole используется при необходимости получить доступ к
строке, которая может отображаться представлением как текст. Обычно элементы
содержат данные для нескольких ролей , стандартные роли же определяется
ItemDataRole.
Роли элементов
Роль показывает модели, к какому типу данных обращаются. Представления могут
отображать роли различными способами, поэтому важно предоставлять
достаточную информацию о каждой роли. В секции Создание новых моделей
особенности использования ролей описаны более детально.
Роли элементов
Роли элементов
Наиболее частые способы использования элементов данных покрываются
стандартными ролями, определёнными в ItemDataRole. Передавая подходящие
элементы данных для каждой роли, модели могут предоставлять представлениями
и делегатам подсказки о том, как элементы требуется отображать пользователю.
Различные типы представлений могут как использовать эту информацию (так, как
это требуется представлению), так и игнорировать её. Также, если это требуется в
конкретном приложении, можно определить собственные дополнительные роли.
Итоги раздела
• Индексы моделей дают представлениям и делегатам информацию о
местонахождении элементов модели, причём эта информация независима от
конкретной организации данных внутри модели.
• К элементам модели можно обратиться по сочетанию их номера столбца,
номера строки и индексу их родительского элемента.
• Индексы создаются моделями по запросу других компонентов, таких как
представления и делегаты.
• Если при запросе индекса в index() передан валидный индекс элемента-
родителя, то полученный индекс указывает на элемент, находящий ся в модели
под данным родительским элементом, т.е. полученный индекс указывает на
потомка элемента, индекс которого передаётся.
• Если же переданный индекс не валиден, то полученный индекс указывает на
элемент верхнего уровня модели.
• Роль позволяет различать различные типы данных, связанные с конкретным
элементом модели.
Классы представлений
Создание новых моделей
Прокси-модели