- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF142:QML
Материал из Linuxformat.
- Технология QML Только начинает свое развитие, но ее преимущества видны уже сегодня
QML |
---|
|
Qt и QML: Язык интерфейса
- Признав принцип разделения движка и логики программы, Андрей Боровский находит общий язык с интерфейсом пользователя.
В программировании издавна живут, взаимодействуют и борются друг с другом два подхода: декларативный (что надо сделать) и императивный (как сделать). А самая заветная мечта всех разработчиков – супер-программа, которая превращала бы декларативное описание задачи в императивное (правда, если такая программа когда-нибудь появится, разработчики, в современном смысле этого слова, исчезнут: их окончательно заменят дизайнеры).
Проблема заключается еще и в том, что те, кто решает декларативную и императивную части задачи, зачастую говорят на разных языках и мыслят разными категориями. Декларативная часть может меняться как перчатки (почти в буквальном смысле), а императивная гораздо более консервативна. Наглядный пример тому – многочисленные приложения баз данных. На императивном уровне все они делают практически одно и то же – обмениваются данными с СУБД, используя команды и объекты языка SQL (я сам использовал один и тот же класс взаимодействия с СУБД в совершенно разных приложениях). А вот внешне, с точки зрения пользователей, приложения БД могут существенно отличаться (благодаря чему так много кодеров и обеспечены работой). Под внешней частью приложения я подразумеваю не только то, как оно выглядит, но и то, как оно себя ведет – то, что обычно называют логикой работы приложения.
Все эти простые наблюдения, сделанные не мной и не сейчас, привели разработчиков к формулировке очень важного принципа: разделения движка и логики. Под логикой в данном случае понимается все, что программа делает. Движок же решает, как именно это будет сделано. К данному принципу близко примыкает другой – разделение движка и интерфейса. Интерфейс, как уже было отмечено, гораздо чаще подвергается изменениям, чем движок. Кроме того, пользователи и администраторы хотят иметь возможность настраивать его самостоятельно. Хорошие примеры разделения движка и интерфейса – графическая обертка вокруг консольной программы Unix или Web-интерфейс взаимодействия с неким сервером. При этом интерфейс удобно описывать в терминах декларативного языка (Web-интерфейсы управления – наглядный тому пример), а движок гораздо проще написать на императивном языке.
Разработчики Qt решили, по-видимому, закрепить принципы разделения движка и интерфейса «на законодательном уровне», заставив их буквально говорить на разных языках. Если основным инструментом реализации движка по-прежнему остается C++ и все библиотеки и вспомогательные средства Qt library, то для описания интерфейса приложения предложен самостоятельный язык QML.
Как вы уже поняли, QML – декларативный язык. Он позволяет описывать, что именно должен видеть пользователь на экране. С помощью QML можно описать внешний вид и расположение элементов управления приложением. Как вам, например, идея заменить стандартный суховатый QDial на вот такой гламурный «спидометр» (рис. 1)?
А еще с помощью QML можно поиграть в так любимую офисным планктоном косынку (рис. 2). Или насладиться индикаторами выполнения задачи, выполненными в пастельных тонах (рис. 3).
Нет нужды говорить о том, что QML кросс-платформен так же, как и сама Qt. Но на самом деле, примеры из скомпилированного сборника Qt создают обманчивое впечатление о текущих возможностях QML. Технология все еще далека от совершенства, хотя кое-что, разумеется, можно сделать уже сейчас.
Вернемся к описанию языка QML. Помимо декларативных элементов, язык включает свой собственный вариант ECMAScript (близкий к JavaScript) с полным набором императивных элементов. Так что на QML можно не только интерфейсы, но и писать программы, а Qt использовать лишь в качестве интерпретатора. Собственно говоря, в состав новейших дистрибутивов Qt уже входит такой интерпретатор – утилита qmlviewer, которая позволяет насладиться всеми доступными на данный момент возможностями технологии QML, не написав ни строчки кода на C++. Если уж говорить начистоту, QML весьма похож на современный HTML, с той разницей, что выполняется он не в браузере, а в приложении Qt, и может взаимодействовать с объектами этого приложения. Когда я говорю, что QML похож на HTML 5, я имею в виду сходство функциональных возможностей, но не синтаксиса. Вот простейшая программа – «Hello World» мира QML (этот пример взят из официальной документации Qt):
import QtQuick 1.0 Rectangle { id: page width: 500; height: 200 color: “lightgray” Text { id: helloText text: “Hello world!” y: 30 anchors.horizontalCenter: page.horizontalCenter font.pointSize: 24; font.bold: true }
Прежде чем разбирать пример, надо сказать пару слов об изменчивом синтаксисе. При переходе от Qt 4.7 к Qt 4.7.1 синтаксис QML был несколько преобразован. Например, в версии 4.7 в начале программы вместо «import QtQuick 1.0» мы должны писать «import Qt 4.7». Поскольку я верю, что изменения, произошедшие при переходе от Qt 4.7 к Qt 4.7.1, отражают дальнейшие устремления разработчиков, я буду пользоваться синтаксисом Qt 4.7.1. А вот в самой документации по Qt все еще можно встретить примеры, использующие старый синтаксис. Будьте внимательны!
Итак, первая строка программы QML
import QtQuick 1.0
указывает, где хранятся определения используемых в программе структур. Если вы пользуетесь Qt 4.7, эта строка должна выглядеть иначе, о чем было сказано выше. Далее следует корневой объект нашей программы. Как и в HTML, и в XML элементы QML образуют иерархические структуры. В общем виде они имеют такой вид:
ОБЪЕКТ { ОБЪЕКТ { … } … }
В рассмотренном выше примере объект Rectangle (корень иерархии) включает дочерний объект Text, предназначенный для вывода текста. Но это не все. Помимо дочерних объектов, у элементов языка QML (так же как у тэгов HTML) имеются параметры (их еще называют свойствами). Как вы можете видеть, для элемента Rectangle задаются такие параметры, как ширина, высота и цвет. Кроме того, у объекта Rectangle есть параметр id, который позволяет программе QML отличать один объект Rectangle от другого. Этот параметр не является обязательным, и его можно опустить, если другие части программы не ссылаются на данный конкретный объект, однако в нашем случае он необходим. Рассмотрим строку
anchors.horizontalCenter: page.horizontalCenter
которая устанавливает горизонтальное расположение элемента Text внутри прямоугольника. В общем, нетрудно догадаться, что мы хотим, чтобы центр (по длине) строки текста совпадал с центром прямоугольника. Вот тут-то нам и требуется его индивидуальный идентификатор.
Далее официальный туториал QML рекомендует сохранить текст в файле с расширением .qml и выполнить его в программе qmlviewer. Я не буду приводить экранный снимок того, что вы там увидите (скорее всего, вы и сами уже догадались).
На QML можно взглянуть и иначе – а именно, как на язык сценариев для QGraphicsView. Действительно, на стороне программы Qt важную роль в работе QML играет класс QDeclarativeView, который является потомком класса QGraphicsView. Таким образом, мы имеем развитие технологии Graphics View Framework, про которую я писал когда-то, что одно из ее предназначений – построение сложных графических интерфейсов. QML можно использовать и для написания сценариев для объектов QGraphicsScene. QtScript для этого тоже подойдет, с той разницей, что QtScript нацелен на работу с объектами приложения Qt, которые могут создаваться и уничтожаться приложением, тогда как QML в большей степени ориентирован на работу с графическими элементами, созданными в описательной части самого языка.
А можно ли выполнить простейшую программу на QML из нашей собственной программы, написанной в Qt? Разумеется! Запомните правило: все, что может показать qmlviewer, можем показать и мы, хотя иногда для этого придется изрядно потрудиться. Вообще надо отметить, что на данном этапе разработчики языка QML сосредоточились в основном на описании самого языка, и ресурсов на эту тему в Сети уже предостаточно. Вопросы взаимодействия QML и Qt освещаются не так подробно (скорее всего, у разработчиков просто не дошли руки до этого). Но мы попробуем немного исправить положение дел, ведь главный документ – исходные тексты инструментов Qt, предназначенных для работы с QML – у нас есть!
Допустим, у нас есть окно Dialog, которое является потомком класса QDialog. Добавить в него виджет, способный отобразить вывод описанной выше программы QML, не просто, а очень просто. Вот конструктор нашего окна, в котором добавляется виджет:
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); QDeclarativeView *qmlView = new QDeclarativeView; qmlView->setSource(QUrl::fromLocalFile(«/home/andrei/qml/helloworld.qml»)); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(qmlView); qmlView->show(); }
Как уже было сказано, простейшее средство отображения QML в Qt – класс QDeclarativeView, экземпляр которого мы и создаем первым делом (объект qmlView). Далее нам следует загрузить файл программы QML в созданный объект; для этого служит метод setSource, которому ссылка на файл программы QML передается в виде объекта QUrl. Данный факт намекает нам на то, что в перспективе мы будем загружать QML-обертки наших программ Qt из Сети. Далее виджет qmlView добавляется в окно обычным для программно созданных виджетов способом. В результате в окне нашей программы появится то же, что вы уже видели в окне, созданном утилитой qmlviewer (разумеется, путь к файлу .qml, прописанный в аргументе метода setSource(), вам надо заменить на свой собственный).
Для сборки программы, в файл проекта (тот, что с расширением .pro) необходимо добавить строку
QT += declarative
Эта строка укажет, что в проект следует включить ссылки на библиотеки Qt, ответственные за работу с QML. Описание класса QDeclarativeView (единственного класса, предназначенного для QML, который нам пока что понадобился) хранится в заголовочном файле <QtDeclarative/QDeclarativeView>.
Наша программа демонстрирует картинку, созданную с помощью простейшей программы QML, и вы можете поэкспериментировать с кодом файла helloworld.qml и убедиться в том, что даже существенно измененные файлы будут отображаться в нашей программе точно так же (иначе говоря, мы действительно имеем разделение движка и интерфейса). Однако пить шампанское пока рано, тем более что этот напиток плохо способствует эффективному программированию. Хотя наша программа умеет показывать картинки, созданные с помощью QML (честно говоря, не все), эти картинки и сама программа никак не взаимодействуют между собой, а ведь в таком взаимодействии и заключается смысл использования QML.
Итак, пойдем дальше. Наша следующая задача заключается в том, чтобы наладить взаимодействие между кодом Qt и QML, а это, в свою очередь, удобнее сделать, если код QML интерактивен, то есть может реагировать на действия пользователя.
Начнем с программы на QML, благо она не намного сложнее предыдущей. Этот файл называется interactive.qml. Как и все остальные файлы исходников данного примера, вы найдете его на диске.
import QtQuick 1.0 Rectangle { width: 120 height: 40 color: palette.background TextInput { id: ti anchors.centerIn: parent color: palette.foreground text: “blue” } MouseArea { anchors.fill: parent onClicked: { palette.background = ti.text; ti.focus = true; } } }
В этой программе мы встречаем два новых объекта QML: MouseArea и TextInput. MouseArea, как и подсказывает название – это область, которая реагирует на события мыши. Строка
anchors.fill: parent
в теле элемента MouseArea означает, что данный элемент должен заполнить все пространство своего родительского элемента, в данном случае – элемента Rectangle. Еще у элемента MouseArea есть ряд событий, похожих на события объекта Qt. Обработчик события onClicked вызывается всякий раз, когда пользователь щелкает мышью в области элемента MouseArea, то есть, в нашем случае, в области прямоугольника Rectangle. Прежде чем разбираться, что делает наш обработчик события, рассмотрим другой новый элемент программы – объект TextInput. Этот элемент предназначен для ввода строки текста (таким образом, в нашей программе целых два объекта принимают ввод пользователя). У объекта TextInput есть идентификатор ti, который используется в обработчике события onClicked. Благодаря строке
ti.focus = true;
элемент TextInput получает фокус ввода всякий раз, когда мы щелкаем мышью в прямоугольнике. Параметр text элемента TextInput содержит введенный пользователем текст. Кроме того, этот параметр можно использовать для начального задания текста, который будет отображаться по умолчанию. Осталось изучить волшебный объект palette, который, судя по всему, управляет цветом прямоугольника (свойство palette.background) и текста строки ввода (свойство palette.foreground).
Изначально элемент ввода содержит текст «blue», и именно это значение будет присвоено свойству palette.background при щелчке мышью. В результате цвет прямоугольника станет синим. А цвет текста станет желтым (дополнительным к синему до белого). Так работает объект palette. Если в строке текста вы введете yellow, то после щелчка мышью увидите, как текст и прямоугольник «поменяются» цветами. Можете поэкспериментировать и с другими сочетаниями, учтя при этом, что нужно вводить названия цветов, принятые в QML. А откуда взялся этот объект palette? Он не является частью QML, а создан qmltest, программой Qt, исходные тексты которой вы найдете на диске. Главное дополнение нашей программы Qt по сравнению с предыдущей – класс CustomPalette. Вот его объявление (файл palette.h):
class CustomPalette : public QObject { Q_OBJECT Q_PROPERTY(QColor background READ background WRITE setBackground NOTIFY backgroundChanged) Q_PROPERTY(QColor foreground READ foreground WRITE setForeground NOTIFY foregroundChanged) public: CustomPalette(); QColor background() const; void setBackground(const QColor &c); QColor foreground() const; void setForeground(const QColor &c); signals: void foregroundChanged(); void backgroundChanged(); private: QColor m_background; QColor m_foreground; };
Как мы видим, этот класс объявляет два свойства: background и foreground. Свойства, объявленные с помощью макроса Q_PROPERTY(), не имеют особого смысла для программы, написанной на C++, зато они очень полезны, когда объекты Qt взаимодействуют с кодом, написанном на других языках, в том числе и на QML.
Что происходит в программе Qt, когда мы присваиваем значение свойству background? Вызывается метод setBackground(), который, как вы можете убедиться, ознакомившись с реализацией класса (файл palette.cpp), присваивает новое значение переменной m_background; так что при чтении свойства background, за которое отвечает метод background(), оно будет возвращено. Далее метод setBackground() инвертирует полученный цвет и присваивает инвертированное значение переменной m_foreground, так что теперь свойство foreground вернет значение, дополняющее background до белого.
В конце метод setBackground() эмитирует два сигнала: backgroundChanged() и foregroundChanged(). Обратите внимание, что в самой программе Qt обработчиков для этих сигналов не предусмотрено. Дело в том, что эти сигналы предназначены для программы QML. Они должны оповестить соответствующие элементы программы о том, что цвета изменились. Именно поэтому имена этих сигналов стоят после ключевого слова NOTIFY макроса Q_PROPERTY(). Чтобы понять, как это работает, вернемся к описанию объекта Rectangle. Строка
color: palette.background
указывает на то, что значение цвета этот объект должен получать из свойства palette.background, и, благодаря наличию сигнала backgroundChanged(), это будет происходить автоматически всякий раз, когда свойству palette.background будет присвоено новое значение. Точно так же будет вести себя и свойство palette.foreground.
Обратите внимание также на то, что хотя в программе QML мы используем текстовые обозначения цветов, свойства класса CustomPalette оперируют значениями QColor. Впрочем, класс QColor обладает средствами для конвертации текстовых обозначений цветов в другие их представления, так что ничего особенного тут нет. Вот, собственно говоря, и вся магия нашей программы. Осталось связать объект класса CustomPalette с объектом palette программы QML. Для начала загрузим программу в объект qmlView. Тут для нас нет ничего нового. Но нам необходимо предоставить программе QML доступ к объекту класса CustomPalette.
Для понимания того, как это происходит, необходимо немного углубиться в иерархию классов Qt, отвечающих за взаимодействие с QML. Как было сказано выше, программа на QML представляет собой иерархию объектов, таких как Rectangle или TextInput. При загрузке программы QML в объект QDeclarativeView для каждого объекта QML создается его «представитель» – объект QDeclarativeContext. Эти объекты образуют иерархию, соответствующую иерархии объектов QML, и предназначаются, вы правильно поняли, для обмена данными между программами (точнее, между соответствующими объектами) QML и Qt. Корневому объекту программы QML (в нашем случае – объекту Rectangle) соответствует объект QDeclarativeContext, указатель на который возвращает метод rootContext() объекта qmlView класса QDeclarativeView. Данные, которые мы передаем этому объекту, становятся видимыми для корневого компонента программы QML и по умолчанию для всех его дочерних компонентов. Таким образом, строчка
qmlView->rootContext()->setContextProperty(“palette”, new CustomPalette);
из файла qmltest.cpp задает значение свойства (параметра) palette объекта Rectangle. Иначе этот параметр просто не имел бы смысла. Обратите внимание, что дочерние элементы наследуют свойство palette. Чтобы связать все выше сказанное в единое целое, рассмотрим текст конструктора класса qmltest (файл qmltest.cpp, который вы найдете на диске):
qmltest::qmltest(QWidget *parent, Qt::WFlags flags) : QDialog(parent, flags) { ui.setupUi(this); QDeclarativeView *qmlView = new QDeclarativeView; qmlView->setSource(QUrl::fromLocalFile(“/home/andrei/qml/interactive.qml”)); qmlView->rootContext()->setContextProperty(“palette”,new CustomPalette); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(qmlView); }
Не забудьте, опять же, сменить путь к файлу interactive.qml на свой собственный. Результат проиллюстрирован рис. 4.
Поскольку наша программа косвенно взаимодействует с объектом класса QDeclarativeContext, в нее необходимо добавить заголовочный файл <QtDeclarative / QDeclarativeContext>.
Технология QML только начинает свое развитие, и многие базовые вещи все еще приходится выполнять вручную. Но я еще помню времена, когда об интегрированной среде визуальной разработки для Qt наподобие Delphi можно было только мечтать, а теперь мы имеем Qt Creator, который не только ни в чем не уступает Delphi IDE, но, по-моему, превосходит ее. Так что появление визуального инструмента для работы с QML, я думаю, не за горами.