Одним из наиболее распространенных и эффективных приемов проектирования программ является использование шаблона программирования MVC (Model-View-Controller) — Модель-Представление-Контроль. MVC позволяет разделить части программы, отвечающие за хранение и доступ к данным, отображение данных и за взаимодействие с пользователем на отдельные слабо связанные модули. Подобное разделение ответственности упрощает структуру программы и позволяет вносить изменения в одну из этих частей не затрагивая остальные.
Такой подход активно применяется в Qt, а в QML вообще является краеугольным камнем. Так что тем, кто изучает QML понимание принципов MVC будет совсем не лишним.
Model-View в QML:
Разделение ответственности является одним из основополагающих принципов программирования. Суть его состоит в том, чтобы разделять программу на функциональные блоки, каждый из которых отвечает за свою часть работы. Этими блоками могут быть и функции, и объекты, и модули и т.д. Блоки между собой связываются через определенные интерфейсы. Это дает некоторую независимость блоков относительно друг друга и снижает сложность системы.
Основные концепции графический интерфейс пользователя (GUI) были разработаны в лаборатории Xerox в 70-х годах двадцатого века. В их число входит и шаблон проектирования MVC, призванный снизить сложность и упростить архитектуру программ с графическим интерфейсом при помощи того самого разделения ответственности.
Итак, суть этого шаблона состоит в разделении программы на три компонента.
Схематически модель можно изобразить так:
Между этими компонентами есть определенные интерфейсы, позволяющие развязать их между собой. В идеале эти модули вообще должны независимы друг от друга и позволять вносить изменения или даже полностью заменить какой-либо модуль без переделки остальных.
С развитием графических интерфейсов и появлением новых видов программ, таких, например, как веб-приложения, такой вариант разделения стал подходить не везде. Поэтому появились его разновидности, такие как MVVC, MVP и т.д. Я буду дальше все разновидности этой модели обобщенно называть MVC.
Немало современных фреймворков используют MVC и содержат в себе средства для разделения программы в соответствии с этим шаблоном. Поскольку одним из основных предназначений Qt является разработка графических интерфейсов пользователя, то в нем без MVC тоже не обошлось и Qt включает в себя свою вариацию — Model-View фреймворк.
Основная идея применения данного шаблона в Qt — разделение данных и их отображения.
Здесь модель отвечает за данные и доступ к ним. Поскольку в графическом интерфейсе за отображение элементов и получение ввода от пользователя нередко отвечают одни и те же элементы, то вполне логична идея объединить представление и контроль. Именно так и сделано в Qt: представление не только отображает данные, но и выполняет функции контроля и отвечает за взаимодействие с пользователем. Но для того, чтобы из-за такого объединения не терять гибкость, было введено понятие делегата. Делегат позволяет определять, как эти данные будут отображаться и как пользователь может их изменять. Представление же, по сути, теперь является контейнером для экземпляров делегата.
В QML этот принцип тоже применяется, даже в гораздо большей степени. Как я уже говорил, Model-View является одной из основополагающих концепций QML. Одно из основных предназначений QML — это выделение интерфейса программы в отдельную часть, которая может быть очень гибкой и легко адаптироваться для разных нужд. Например, для десктопного приложение может быть один вариант интерфейса, а для мобильного другой. При этом ядро программы (модель) может быть написано на C++ и оставаться неизменным.
Также стоит отметить, что для некоторых моделей в QML, делегаты могут только отображать данные, но не редактировать их.
Несмотря на то, что интерфейсная часть является основным местом для применения QML, модель на нем тоже можно можно реализовать, есть для этого соответствующие инструменты. При желании, программу можно сделать практически полностью на QML/JavaScript и свести количество кода на C++ к минимуму. Именно с такой программы мы и начнем.
Рассмотрим программу, которая выводит пару цветных прямоугольников с текстом посередине.
Моделью здесь является объект типа ListModel. В качестве элементов модели используются два дочерних объекта специального типа ListElement, в которых мы определили два свойства: color и text.
Для отображения мы используем объект типа ListView, которому в качестве модели мы указываем id нашего объекта типа ListModel. Само по себе представление не знает, какие отображать наши данные, поэтому нужно задать ему еще и делегат. Делегатом в QML является тип объекта, экземпляр которого создается на каждый элемент модели. А ListView, в свою очередь, создает эти объекты, отображает их в виде списка и обеспечивает навигацию по ним.
В качестве делегата выступает прямоугольник с текстом посредине. Цвет прямоугольника и текст берутся из модели. Внутри делегата видны все свойства элементов модели — т.н. «роли». Они доступны через специальный объект model, а также и напрямую, т.е. можно писать просто color. Я предпочитаю писать полностью, так нагляднее, откуда берутся данные и не будет никаких конфликтов, если у делегата есть свойства с такими же именами.
Результат выполнения выглядит примерно так:
В MVC модель может быть как пассивной, так и активной.
Пассивной является модель, которая просто хранит данные и не воздействует никак контроллер и представление. Соответственно, при изменении данных в модели, она не может никак сообщить представлению об этом. В таком случае, все изменения модели отслеживает контроллер, он же говорит представлению о том, что надо обновить информацию.
Активная же модель может оповещать представления о том, что данные изменились. В свою очередь, представление может отобразить эти изменения. Именно такой вариант считается классической реализацией MVC.
В Qt и QML применяется активная модель. Это дает нам возможность изменять данные в модели и не беспокоится об отображении обновленных данных — представление все сделает за нас само.
Намного доработаем предыдущий пример: добавим кнопку, которая будет добавлять элементы в модель при нажатии.
Изначально модель не содержит никаких элементов. При нажатии на кнопку в модель добавляется пустой элемент без свойств. Модель информирует представление о том, что добавился новый элемент. На каждый новый элемент представление создает объект, тип которого мы указали в качестве делегата. Помимо тех свойств, которые мы описываем в элементах модели, в делегате будет доступно еще одно — index (или model.index), содержащее индекс элемента в модели и, соответственно, в отображении. Так что, несмотря на то, что мы добавляем пустой элемент в модель, одно свойство там все-таки есть и именно индекс мы используем в качестве текста.
Запустив программу и понажимав на кнопку получим примерно такой вид:
Я показал два простых примера, которые иллюстрируют разделение дынных и их отображения и использование делегатов для управления тем, как данные будут отображаться. В дальнейшем тексте, я буду ссылаться на эти два примера, чтобы не приводить их код целиком.
Разработанная более 30 лет назад, технология MVC ничуть не утратила своей актуальности. И по сей день она является основой для проектирования программ с графическим интерфейсом; сейчас сложно представить себе такую программу, не использующую MVC.
Как технология, разработанная для создания графических интерфейсов, Qt и, особенно, QML имеют полную поддержку MVC. Необходимые инструменты входят в состав библиотек, более того, в самом Qt, достаточно четко прослеживается идеология этого шаблона.
Соответственно применение MVC здесь является естественным и именно так задумано разработчиками и именно с таким видением был спроектирован фреймворк.
На этом вводную часть можно считать законченной, а в следующей статье мы рассмотрим подробнее представления.
Такой подход активно применяется в Qt, а в QML вообще является краеугольным камнем. Так что тем, кто изучает QML понимание принципов MVC будет совсем не лишним.
Model-View в QML:
- Model-View в QML. Часть нулевая, вводная
- Model-View в QML. Часть первая: Представления на основе готовых компонентов
- Model-View в QML. Часть вторая: Кастомные представления
- Model-View в QML. Часть третья: Модели в QML и JavaScript
- Model-View в QML. Часть четвертая: C++-модели
Что такое MVC
Разделение ответственности является одним из основополагающих принципов программирования. Суть его состоит в том, чтобы разделять программу на функциональные блоки, каждый из которых отвечает за свою часть работы. Этими блоками могут быть и функции, и объекты, и модули и т.д. Блоки между собой связываются через определенные интерфейсы. Это дает некоторую независимость блоков относительно друг друга и снижает сложность системы.
Основные концепции графический интерфейс пользователя (GUI) были разработаны в лаборатории Xerox в 70-х годах двадцатого века. В их число входит и шаблон проектирования MVC, призванный снизить сложность и упростить архитектуру программ с графическим интерфейсом при помощи того самого разделения ответственности.
Итак, суть этого шаблона состоит в разделении программы на три компонента.
- Модель. Отвечает за данные и обеспечивает доступ к ним.
- Представление. Отвечает за отображение данных, полученных из модели.
- Контроль. Отвечает за взаимодействие с пользователем. Может изменять данные в модели.
Схематически модель можно изобразить так:
Между этими компонентами есть определенные интерфейсы, позволяющие развязать их между собой. В идеале эти модули вообще должны независимы друг от друга и позволять вносить изменения или даже полностью заменить какой-либо модуль без переделки остальных.
С развитием графических интерфейсов и появлением новых видов программ, таких, например, как веб-приложения, такой вариант разделения стал подходить не везде. Поэтому появились его разновидности, такие как MVVC, MVP и т.д. Я буду дальше все разновидности этой модели обобщенно называть MVC.
Немало современных фреймворков используют MVC и содержат в себе средства для разделения программы в соответствии с этим шаблоном. Поскольку одним из основных предназначений Qt является разработка графических интерфейсов пользователя, то в нем без MVC тоже не обошлось и Qt включает в себя свою вариацию — Model-View фреймворк.
MVC в Qt и QML
Основная идея применения данного шаблона в Qt — разделение данных и их отображения.
Здесь модель отвечает за данные и доступ к ним. Поскольку в графическом интерфейсе за отображение элементов и получение ввода от пользователя нередко отвечают одни и те же элементы, то вполне логична идея объединить представление и контроль. Именно так и сделано в Qt: представление не только отображает данные, но и выполняет функции контроля и отвечает за взаимодействие с пользователем. Но для того, чтобы из-за такого объединения не терять гибкость, было введено понятие делегата. Делегат позволяет определять, как эти данные будут отображаться и как пользователь может их изменять. Представление же, по сути, теперь является контейнером для экземпляров делегата.
В QML этот принцип тоже применяется, даже в гораздо большей степени. Как я уже говорил, Model-View является одной из основополагающих концепций QML. Одно из основных предназначений QML — это выделение интерфейса программы в отдельную часть, которая может быть очень гибкой и легко адаптироваться для разных нужд. Например, для десктопного приложение может быть один вариант интерфейса, а для мобильного другой. При этом ядро программы (модель) может быть написано на C++ и оставаться неизменным.
Также стоит отметить, что для некоторых моделей в QML, делегаты могут только отображать данные, но не редактировать их.
Несмотря на то, что интерфейсная часть является основным местом для применения QML, модель на нем тоже можно можно реализовать, есть для этого соответствующие инструменты. При желании, программу можно сделать практически полностью на QML/JavaScript и свести количество кода на C++ к минимуму. Именно с такой программы мы и начнем.
Простой пример MVC в QML
Рассмотрим программу, которая выводит пару цветных прямоугольников с текстом посередине.
import QtQuick 2.0
Rectangle {
width: 360
height: 360
ListModel {
id: dataModel
ListElement {
color: "orange"
text: "first"
}
ListElement {
color: "skyblue"
text: "second"
}
}
ListView {
id: view
anchors.margins: 10
anchors.fill: parent
spacing: 10
model: dataModel
delegate: Rectangle {
width: view.width
height: 100
color: model.color
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: model.text
}
}
}
}
Моделью здесь является объект типа ListModel. В качестве элементов модели используются два дочерних объекта специального типа ListElement, в которых мы определили два свойства: color и text.
Для отображения мы используем объект типа ListView, которому в качестве модели мы указываем id нашего объекта типа ListModel. Само по себе представление не знает, какие отображать наши данные, поэтому нужно задать ему еще и делегат. Делегатом в QML является тип объекта, экземпляр которого создается на каждый элемент модели. А ListView, в свою очередь, создает эти объекты, отображает их в виде списка и обеспечивает навигацию по ним.
В качестве делегата выступает прямоугольник с текстом посредине. Цвет прямоугольника и текст берутся из модели. Внутри делегата видны все свойства элементов модели — т.н. «роли». Они доступны через специальный объект model, а также и напрямую, т.е. можно писать просто color. Я предпочитаю писать полностью, так нагляднее, откуда берутся данные и не будет никаких конфликтов, если у делегата есть свойства с такими же именами.
Результат выполнения выглядит примерно так:
Динамика в Model-View
В MVC модель может быть как пассивной, так и активной.
Пассивной является модель, которая просто хранит данные и не воздействует никак контроллер и представление. Соответственно, при изменении данных в модели, она не может никак сообщить представлению об этом. В таком случае, все изменения модели отслеживает контроллер, он же говорит представлению о том, что надо обновить информацию.
Активная же модель может оповещать представления о том, что данные изменились. В свою очередь, представление может отобразить эти изменения. Именно такой вариант считается классической реализацией MVC.
В Qt и QML применяется активная модель. Это дает нам возможность изменять данные в модели и не беспокоится об отображении обновленных данных — представление все сделает за нас само.
Намного доработаем предыдущий пример: добавим кнопку, которая будет добавлять элементы в модель при нажатии.
import QtQuick 2.0
Rectangle {
width: 360
height: 360
ListModel {
id: dataModel
}
Column {
anchors.margins: 10
anchors.fill: parent
spacing: 10
ListView {
id: view
width: parent.width
height: parent.height - button.height - parent.spacing
spacing: 10
model: dataModel
clip: true
delegate: Rectangle {
width: view.width
height: 40
color: "skyblue"
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: model.index
}
}
}
Rectangle {
id: button
width: 100
height: 40
anchors.horizontalCenter: parent.horizontalCenter
border {
color: "black"
width: 1
}
Text {
anchors.centerIn: parent
renderType: Text.NativeRendering
text: "Add"
}
MouseArea {
anchors.fill: parent
onClicked: dataModel.append({})
}
}
}
}
Изначально модель не содержит никаких элементов. При нажатии на кнопку в модель добавляется пустой элемент без свойств. Модель информирует представление о том, что добавился новый элемент. На каждый новый элемент представление создает объект, тип которого мы указали в качестве делегата. Помимо тех свойств, которые мы описываем в элементах модели, в делегате будет доступно еще одно — index (или model.index), содержащее индекс элемента в модели и, соответственно, в отображении. Так что, несмотря на то, что мы добавляем пустой элемент в модель, одно свойство там все-таки есть и именно индекс мы используем в качестве текста.
Запустив программу и понажимав на кнопку получим примерно такой вид:
Я показал два простых примера, которые иллюстрируют разделение дынных и их отображения и использование делегатов для управления тем, как данные будут отображаться. В дальнейшем тексте, я буду ссылаться на эти два примера, чтобы не приводить их код целиком.
Небольшой вывод
Разработанная более 30 лет назад, технология MVC ничуть не утратила своей актуальности. И по сей день она является основой для проектирования программ с графическим интерфейсом; сейчас сложно представить себе такую программу, не использующую MVC.
Как технология, разработанная для создания графических интерфейсов, Qt и, особенно, QML имеют полную поддержку MVC. Необходимые инструменты входят в состав библиотек, более того, в самом Qt, достаточно четко прослеживается идеология этого шаблона.
Соответственно применение MVC здесь является естественным и именно так задумано разработчиками и именно с таким видением был спроектирован фреймворк.
На этом вводную часть можно считать законченной, а в следующей статье мы рассмотрим подробнее представления.