Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке для мобильной платформы Sailfish OS. В этой статье пойдет речь о создании собственных компонентов на QML на С++, а конкретно о создании свойств и методов, доступных в QML, о сигналах и связывании. Так же покажем, как подключать новый компонент к приложению.
Для начала стоит определить случаи, в которых использование компонентов на С++ имеет смысл. Встроенные типы Qt Quick безусловно предлагают некоторые инструменты для работы с сенсорами, с базами данных и уведомлениями, о которых мы уже рассказывали ранее здесь и здесь, с различными системными возможностями, такими как сеть или Bluetooth, D-Bus. Однако возможности этих инструментов достаточно ограничены и не подходят для создания более сложных приложений. В этом случае С++ как раз позволяет реализовывать функциональность сверх того, что предоставляет Qt Quick.
Еще одним доводом в пользу С++ является возможность использования разнообразных низкоуровневых библиотек. Помимо этого, сложную логику попросту правильнее выделять в классы С++. Именно этим мы руководствовались при создании компонента для отображения данных на временной оси в виде графика.
Внешний вид компонента представляет собой координатную плоскость, на которой изображаются различные графики. Перемещение по графику вправо и влево осуществляется с помощью кнопок под компонентом. Данные на плоскости могут отображаться за различные временные периоды: за неделю, месяц, квартал или год. При этом при выборе одного из последних трех периодов данные уплотняются. Так, например, для месяца на графике отображаются средние значения для каждого дня диапазона, а для квартального и годового диапазона — средние значения за каждые 3 и 12 дней соответственно. Выбрать желаемый период можно с помощью кнопок, которые располагаются над координатной плоскостью:
Стоит отметить, что сам процесс создания компонентов не отличается от такового на Qt, однако все же имеется несколько специфических для Sailfish OS особенностей. О них будет сказано ниже.
Для начала необходимо создать С++ класс, унаследованный от QObject или его потомков и содержащий макрос Q_OBJECT:
Наш класс унаследован от QQuickPaintedItem для возможности переопределения метода QQuickPaintedItem::paint(), в котором будет реализован процесс отрисовки графика с помощью интерфейса QPainter. Для реализации визуальных компонентов без использования рисования можно использовать класс QQuickItem.
Покажем создание свойств, которые будут видны в QML, и работу с ними на примере свойства periodIndex, которое отвечает за индекс текущего выбранного периода. Чтобы объявить свойство, необходимо использовать макрос Q_PROPERTY:
Как можно видеть из примера, для свойства необходимо указывать его имя, тип и функцию READ для получения значения свойства. Так же можно указать WRITE функцию, предназначенную для присваивания свойству нового значения. Полный список доступных параметров можно посмотреть здесь. Помимо этого внутри класса лучше всего объявить переменную для хранения значения свойства (что мы и сделали в примере).
Также, чтобы использовать механизм связывания внутри QML, с помощью параметра NOTIFY у свойства periodIndex определяется сигнал об изменении его значения. Вызов соответствующего сигнала необходимо поместить внутри метода PlotView::setPeriodIndex():
Для всех сигналов, объявленных внутри нашего компонента, можно создавать соответствующие обработчики при объявлении компонента в QML. Так, обработчик сигнала об изменении индекса текущего периода будет выглядеть следующим образом:
Чтобы создать метод, который будет доступен для вызова внутри QML, при его объявлении необходимо добавить макрос Q_INVOKABLE . Объявим метод, который обновляет данные для графиков и перерисовывает компонент:
Теперь мы можем добавить вызов этого метода в обработчик onPeriodIndexChanged()
Таким образом, при изменении свойства periodIndex координатная плоскость и графики автоматически обновятся.
Стоит отметить, что вместо использования Q_INVOKABLE метод можно объявить в качестве слота, что также сделает его видимым внутри QML.
Сама отрисовка компонента происходит, как уже было сказано выше, с помощью стандартных классов, предоставляемых библиотекой Qt. Пример отрисовки горизонтальных линий координатной плоскости выглядит следующим образом:
Итак, чтобы сделать наш компонент доступным внутри QML, необходимо его зарегистрировать внутри приложения. Здесь стоит отметить несколько особенностей, характерных для Sailfish приложений, а именно с их публикацией в магазине Harbour:
Полный список требований к именам для публикации приложения в магазине можно посмотреть здесь.
Регистрация выполняется с помощью метода qmlRegisterType() и выглядит следующим образом:
Таким образом, использование этого компонента в QML будет выглядеть примерно так:
Важно отметить, что внутри наших компонентов можно использовать классы и свойства из библиотеки Sailfish Silica, тем самым создавая компоненты удовлетворяющие UI Sailfish OS. Хорошим примером для этого является класс Theme предоставляющий доступ к стандартным стилевым свойствам Sailfish OS, таким как цвета шрифты и отступы:
В результате были показаны основные шаги для создания собственных компонентов. Аналогичные шаги можно применять для создания невизуальных компонентов для вашего приложения. Более подробную информацию по этой тематике можно найти здесь и здесь. Исходники небольшого приложения для демонстрации работы нашего компонента доступны на Bitbucket.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Дарья Ройчикова
Мотивация
Для начала стоит определить случаи, в которых использование компонентов на С++ имеет смысл. Встроенные типы Qt Quick безусловно предлагают некоторые инструменты для работы с сенсорами, с базами данных и уведомлениями, о которых мы уже рассказывали ранее здесь и здесь, с различными системными возможностями, такими как сеть или Bluetooth, D-Bus. Однако возможности этих инструментов достаточно ограничены и не подходят для создания более сложных приложений. В этом случае С++ как раз позволяет реализовывать функциональность сверх того, что предоставляет Qt Quick.
Еще одним доводом в пользу С++ является возможность использования разнообразных низкоуровневых библиотек. Помимо этого, сложную логику попросту правильнее выделять в классы С++. Именно этим мы руководствовались при создании компонента для отображения данных на временной оси в виде графика.
Описание компонента
Внешний вид компонента представляет собой координатную плоскость, на которой изображаются различные графики. Перемещение по графику вправо и влево осуществляется с помощью кнопок под компонентом. Данные на плоскости могут отображаться за различные временные периоды: за неделю, месяц, квартал или год. При этом при выборе одного из последних трех периодов данные уплотняются. Так, например, для месяца на графике отображаются средние значения для каждого дня диапазона, а для квартального и годового диапазона — средние значения за каждые 3 и 12 дней соответственно. Выбрать желаемый период можно с помощью кнопок, которые располагаются над координатной плоскостью:
Реализация компонента
Стоит отметить, что сам процесс создания компонентов не отличается от такового на Qt, однако все же имеется несколько специфических для Sailfish OS особенностей. О них будет сказано ниже.
Для начала необходимо создать С++ класс, унаследованный от QObject или его потомков и содержащий макрос Q_OBJECT:
#include <QQuickPaintedItem>
#include <QObject>
class PlotView : public QQuickPaintedItem {
Q_OBJECT
public:
explicit PlotView(QQuickItem* parent = NULL);
void paint(QPainter* painter);
};
Наш класс унаследован от QQuickPaintedItem для возможности переопределения метода QQuickPaintedItem::paint(), в котором будет реализован процесс отрисовки графика с помощью интерфейса QPainter. Для реализации визуальных компонентов без использования рисования можно использовать класс QQuickItem.
Покажем создание свойств, которые будут видны в QML, и работу с ними на примере свойства periodIndex, которое отвечает за индекс текущего выбранного периода. Чтобы объявить свойство, необходимо использовать макрос Q_PROPERTY:
class PlotView : public QQuickPaintedItem {
//...
Q_PROPERTY(int periodIndex READ periodIndex WRITE setPeriodIndex NOTIFY periodIndexChanged)
private:
int periodIndexValue;
//...
public:
void setPeriodIndex(int periodIndex);
int periodIndex() const;
//...
signals:
void periodIndexChanged();
};
Как можно видеть из примера, для свойства необходимо указывать его имя, тип и функцию READ для получения значения свойства. Так же можно указать WRITE функцию, предназначенную для присваивания свойству нового значения. Полный список доступных параметров можно посмотреть здесь. Помимо этого внутри класса лучше всего объявить переменную для хранения значения свойства (что мы и сделали в примере).
Также, чтобы использовать механизм связывания внутри QML, с помощью параметра NOTIFY у свойства periodIndex определяется сигнал об изменении его значения. Вызов соответствующего сигнала необходимо поместить внутри метода PlotView::setPeriodIndex():
void PlotView::setPeriodIndex(int periodIndex) {
periodIndexValue = periodIndex;
//...
emit periodIndexChanged();
//...
}
Для всех сигналов, объявленных внутри нашего компонента, можно создавать соответствующие обработчики при объявлении компонента в QML. Так, обработчик сигнала об изменении индекса текущего периода будет выглядеть следующим образом:
PlotView {
onPeriodIndexChanged: {}
//...
}
Чтобы создать метод, который будет доступен для вызова внутри QML, при его объявлении необходимо добавить макрос Q_INVOKABLE . Объявим метод, который обновляет данные для графиков и перерисовывает компонент:
class PlotView : public QQuickPaintedItem {
//...
public:
Q_INVOKABLE void drawPlot();
//...
};
Теперь мы можем добавить вызов этого метода в обработчик onPeriodIndexChanged()
PlotView {
onPeriodIndexChanged: drawPlot()
}
Таким образом, при изменении свойства periodIndex координатная плоскость и графики автоматически обновятся.
Стоит отметить, что вместо использования Q_INVOKABLE метод можно объявить в качестве слота, что также сделает его видимым внутри QML.
Сама отрисовка компонента происходит, как уже было сказано выше, с помощью стандартных классов, предоставляемых библиотекой Qt. Пример отрисовки горизонтальных линий координатной плоскости выглядит следующим образом:
void PlotView::drawVerticalScaleLines(QPainter* painter) {
painter->setPen(QPen(QBrush(Qt::white), 1));
for (float i = minValue; i < maxValue; i += step) {
int y = calculatePlotYCoordinate(i);
painter->drawLine(0, y, width(), y);
}
}
Итак, чтобы сделать наш компонент доступным внутри QML, необходимо его зарегистрировать внутри приложения. Здесь стоит отметить несколько особенностей, характерных для Sailfish приложений, а именно с их публикацией в магазине Harbour:
- Название вашего приложение должно начинаться с harbour и записываться через "-". К примеру, harbour-plot-app
- Необходимо, чтобы URI, на котором вы регистрируете компоненты, начинался с названия приложения, в котором "-" заменены на ".". В нашем случае это будет выглядеть как harbour.plot.app.plotview
Полный список требований к именам для публикации приложения в магазине можно посмотреть здесь.
Регистрация выполняется с помощью метода qmlRegisterType() и выглядит следующим образом:
#include <QtQuick/QQuickView>
#include <QGuiApplication>
#include "plotview.h"
//...
int main(int argc, char *argv[]) {
QGuiApplication* app = SailfishApp::application(argc, argv);
//...
qmlRegisterType<PlotView>("harbour.plotapp.plotview", 1, 0, "PlotView");
//...
return app->exec();
}
Таким образом, использование этого компонента в QML будет выглядеть примерно так:
import QtQuick 2.0
import Sailfish.Silica 1.0
import harbour.plotapp.plotview 1.0
//...
PlotView {
id: plotView
periodIndex: 0
onPeriodIndexChanged: drawPlot();
//...
}
//...
Важно отметить, что внутри наших компонентов можно использовать классы и свойства из библиотеки Sailfish Silica, тем самым создавая компоненты удовлетворяющие UI Sailfish OS. Хорошим примером для этого является класс Theme предоставляющий доступ к стандартным стилевым свойствам Sailfish OS, таким как цвета шрифты и отступы:
PlotView {
//...
width: parent.width - Theme.horizontalPageMargin * 2
anchors.horizontalCenter: parent.horizontalCenter
//...
}
Заключение
В результате были показаны основные шаги для создания собственных компонентов. Аналогичные шаги можно применять для создания невизуальных компонентов для вашего приложения. Более подробную информацию по этой тематике можно найти здесь и здесь. Исходники небольшого приложения для демонстрации работы нашего компонента доступны на Bitbucket.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Дарья Ройчикова