Операционная система ЗОСРВ «Нейтрино» > Руководство разработчика > Программные интерфейсы общего назначения > Пользовательские графические интерфейсы > Доступные технологии > Разработка GUI для Qt приложений с использованием web-технологий (библиотека facefull)



Разработка GUI для Qt приложений с использованием web-технологий (библиотека facefull)

Данное руководство описывает основные шаги, необходимые для разработки современных графических интерфейсов с использованием библиотеки facefull (HTML/JavaScript) для нативных приложений под ЗОСРВ "Нейтрино"

В этой главе:

Введение
Шаг 1. Создание главного окна Qt с виджетом QWebView
Шаг 2. Инициализация bridge
Шаг 3. Реализация UI
Описание HTML страницы
Описание стилей
Описание основного JS скрипта
Использование QRC для сборки ресурсов
Взаимодействие с UI через bridge
Заключение

Введение

С помощью библиотеки facefull можно создавать современные графические пользовательские интерфейсы с использованием технологий HTML, CSS и JS как для веб, так и для нативных приложений. Библиотека содержит более 30 различных визуальных компонентов с огромными возможностями кастомизации. Все компоненты адаптивные и отлично подходят для использования с разными разрешениями экрана, а также с тачскринами.

Библиотека обладает исчерпывающей документацией, а ее исходный код доступен в публичном репозитории.

Для нативных приложений, в качестве рендера интерфейса выступает системный веб-движок, в случае Нейтрино - это WebKit. В Нейтрино имеется поддержка Qt5, поэтому самый простой способ отображения такого интерфейса - использование компонента QWebView.

Отличие использования facefull для разработки интерфейсов для нативных приложений от классических случаев применения web-based UI в том, что в данном случае в WebView переносится только графический интерфейс (и логика его работы), а вся основная логика приложения остаётся написанной на нативных языках С\С++. Это означает, что приложение не теряет в функциональных возможностях и скорости работы.

Для того, чтобы связать нативный код на С\С++ и код facefull требуется реализация специального механизма обмена сообщениями, который называется bridge. Для этих целей была разработана библиотека facefull-bridge, которая реализует этот механизм для различных фреймворков, в том числе и Qt5WebKit. Она включает в себя все компоненты библиотеки facefull. Библиотека facefull-bridge была портирована под ОС Нейтрино и доступна "из коробки". Её исходный код также доступен в публичном репозитории.

Таким образом, применение facefull для построения графических интерфейсов в нативных приложениях сводится к трём простым шагам.

Шаг 1. Создание главного окна Qt с виджетом QWebView

Обычно главное окно в Qt создаётся с помощью наследования от класса QMainWindow. На главном окне необходимо разместить только виджет QWebView и ряд вспомогательный компонентов. Типовой вариант заголовочного файла mainwindow.h выглядит так:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
private:
Ui::MainWindow *ui;
QVBoxLayout *MainLayout;
QWidget *MainWidget;
QWebView *WebView;
public:
MainWindow( QWidget *parent = nullptr );
~MainWindow();
};
#endif // MAINWINDOW_H

Реализация класса в файле mainwindow.cpp содержит создание объекта класса QWebView и размещение виджета на главном окне:

#include <iostream>
#include <QDir>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ) {
ui -> setupUi( this );
// Если нужно убрать системную рамку и заголовок окна
setWindowFlags( Qt::FramelessWindowHint );
std::cout << "WebKit version: " << qWebKitVersion().toStdString() << std::endl;
MainLayout = new QVBoxLayout();
MainLayout -> addSpacing( 0 );
MainLayout -> setContentsMargins( 0, 0, 0, 0 );
WebView = new QWebView( this );
MainLayout -> addWidget( WebView );
MainWidget = new QWidget();
MainWidget -> setLayout( MainLayout );
setCentralWidget( MainWidget );
}
MainWindow::~MainWindow() {
delete ui;
}

Шаг 2. Инициализация bridge

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

В описание класса в файле mainwindow.h нужно внести следующие изменения:

#include <facefull/bridge/qt5webkit.hpp>
class MainWindow : public QMainWindow {
Q_OBJECT
private:
...
FacefullBridgeQt5WebKit *Bridge;
...
protected:
bool eventFilter( QObject* object, QEvent* event ) override;
public slots:
void doBridgeEventReceive( const QString& ) const;
signals:
void BridgeEventHandler( QString, QString );
...
};

Перегруженный метод eventFilter() необходим для реализации перемещения окна с помощью кастомного заголовка, а методы doBridgeEventReceive() и BridgeEventHandler() - слот и сигнал для обработки событий от QWebView.

В файле mainwindow.cpp добавится создание объекта класса FacefullBridgeQt5WebKit и реализация некоторых из указанных методов:

MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ) {
...
Bridge = new FacefullBridgeQt5WebKit(this, WebView, QUrl("путь к html странице"));
...
}
void MainWindow::doBridgeEventReceive( const QString &data ) const {
Bridge -> doEventCatch( data.toStdString() );
}
bool MainWindow::eventFilter( QObject* object, QEvent* event ) {
Bridge -> doMoveWindow( (QMouseEvent*)event );
return false;
}

Здесь переменная respath содержит путь к странице, реализующей графический интерфейс.

Шаг 3. Реализация UI

Этот шаг сводится к созданию трёх компонентов: HTML страницы, стилей (CSS), и основного JS-скрипта интерфейса приложения. Традиционно файлы называются window.html, style.css и app.js соответственно. Библиотека facefull в свою очередь предоставляет реализацию функционала визуальных компонентов, реализацию внутренней составляющей bridge и стили.

Описание HTML страницы

Файл window.html содержит описание компонентов на языке разметки HTML5, которые должны быть отображены в приложении, а также подключает все необходимые ресурсы. Простейший пример страницы выглядит следующим образом:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Facefull test</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<!-- Подключение необходимых ресурсов -->
<script src="facefull/facefull.min.js" charset="utf-8"></script>
<script src="src/app.js" charset="utf-8"></script>
<link rel="stylesheet" href="facefull/facefull.min.css">
<link rel="stylesheet" href="src/style.css">
</head>
<body>
<!-- Контейнер окна, обязательно должен иметь id="W" -->
<div id="W" class="Window">
<!-- Определение заголовка окна, обязательно должен иметь id="WH" -->
<div id="WH" class="WindowHeader" onselectstart="return false">
<div class="WindowIcon"></div>
<div class="WindowCaption">Facefull app example</div>
<div class="WindowMover"></div>
<div class="WindowControlsBlock">
<div class="WindowControl Min"><div></div></div>
<div class="WindowControl Max" id="WCM"><div></div></div>
<div class="WindowControl Close"><div></div></div>
</div>
</div>
<!-- Контейнер рабочей зоны окна, обязательно должен иметь id="G" -->
<div id="G" class="GlobalArea">
<!-- Определение главного бокового меню -->
<div class="MainMenu">
<div id="MMI" class="MainMenuItems">
<div class="TooltipTarget" data-pagename="Page1" data-tooltip-text="Page 1" data-tooltip-width="130" data-tooltip-pos="right"></div>
<div class="TooltipTarget" data-pagename="Page2" data-tooltip-text="Page 2" data-tooltip-width="120" data-tooltip-pos="right"></div>
</div>
</div>
<!-- Рабочая область окна -->
<div class="WorkArea">
<!-- Определение первой вкладки. В id указывается желаемое id вкладки с префиксом P (т.е. P<id вкладки>). Оно автоматически связывается с элементами главного меню -->
<div id="PPage1" class="Page">
<div class="Title">
<div class="TitleText"><div>Вкладка 1</div>
<div class="Subtitle">Подзаголовок вкладки 1</div>
</div>
</div>
<div class="Box PageBody Scrolling" data-scrollboxname="P1SB">
<div class="Scrolldata">
<!---->
</div>
</div>
</div>
<!-- Определение второй вкладки -->
<div id="PPage2" class="Page">
<div class="Title">
<div class="TitleText"><div>Вкладка 2</div>
<div class="Subtitle">Подзаголовок вкладки 2</div>
</div>
</div>
<div class="Box PageBody Scrolling" data-scrollboxname="P1SB">
<div class="Scrolldata">
<!---->
</div>
</div>
</div>
</div>
</div>
<!-- Определения дополнительных элементов окна: -->
<!-- Определение всплывающей подсказки -->
<div id="TT" class="Tooltip"></div>
<!-- Определение затеняющего оверлея для всплывающих сообщений -->
<div id="OV" class="Overlay"></div>
<!-- Определение стандартного окна всплывающих сообщений -->
<div id="AE" class="Alert Hidden Rounded">
<div class="AlertCaption"></div>
<div class="AlertText"></div>
<div class="AlertButtons">
<div id="AB-OK" class="Button Rounded">OK</div>
<div id="AB-Y" class="Button Rounded">Yes</div>
<div id="AB-N" class="Button Rounded">No</div>
</div>
</div>
</div>
</body>
</html>

Описание стилей

Стандартный файл стилей facefull.min.css содержит все необходимые стили для стандартных визуальных компонентов библиотеки facefull, но их можно переопределить в собственном CSS файле. Например, можно задать значки пунктам главного меню и значок в заголовке окна:

.MainMenu *[data-pagename="Page1"]::before {
content: '\F056E';
}
.MainMenu *[data-pagename="Page2"]::before {
content: '\F0D7C';
}
.WindowIcon {
font-family: "Material Design Icons";
font-size: 28px;
}
.WindowIcon::before {
content: '\F126F';
}

В состав библиотеки facefull входит шрифт Material Design Icons, содержащий сотни значков в минималистичном стиле.

Описание основного JS скрипта

Теперь необходимо описать скрипт инициализации графического интерфейса. JS файл (app.js) должен содержать следующие обязательные определения:

// Инициализация объекта facefull. Все взаимодействия с библиотекой осуществляются через этот объект
facefullCreate( true );
// Запуск инициализации интерфейса после загрузки страницы
window.addEventListener( 'load', function ) {
App();
});
function App() {
// Инициализация компонентов facefull
facefull.doInit();
// ...
// Иницализация графического интерфейса всегда должна заканчиваться отправкой в bridge сообщения doWindowReady. Это сообщение генерирует событие, которое означает, что интерфейс проинициализирован и готов к работе. После этого можно отправлять и получать сообщения через bridge.
facefull.doEventSend( "doWindowReady" );
}

Использование QRC для сборки ресурсов

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

<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>ui/window.html</file>
<file>ui/app.js</file>
<file>ui/style.css</file>
<file>ui/facefull/facefull.min.js</file>
<file>ui/facefull/facefull.min.css</file>
<file>ui/facefull/theme-light.min.css</file>
<file>ui/facefull/fonts/md-embedded.woff</file>
</qresource>
</RCC>

Таким образом, все ресурсы будут доступны из основного кода приложения через префикс "qrc:". Теперь нужно указать в конструкторе при создании bridge правильный путь к html странице графического интерфейса:

Bridge = new FacefullBridgeQt5WebKit( this, WebView, QUrl( "qrc:/ui/window.html" ) );

Теперь после запуска приложения можно увидеть получившийся результат:

interface_main.png
Рисунок 1. Внешний вид интерфейса

Взаимодействие с UI через bridge

Со стороны нативного кода отправка сообщений через bridge осуществляется с помощью метода doEventSend(), для приёма сообщений необходимо создать обработчик события с помощью метода doEventAttach(). Аналогичным образом взаимодействие осуществляется и со стороны UI - отправка и приём выполняется с помощью методов facefull.doEventSend() и facefull.doEventHandlerAttach() соответственно.

Рассмотрим пример. Чтобы отправить тестовое сообщение в UI в нативном коде (например в конструкторе класса MainWindow) выполняем соответствующий вызов:

// Навешиваем обработчик на событие готовности окна и отправляем сообщение
Bridge -> doEventAttach( "doWindowReady", [this]( const std::string& data ) ) {
Bridge -> doEventSend( "doTestMessage", "Тестовое сообщение" );
});

В свою очередь в app.js добавляем обработчик события:

facefull.doEventHandlerAttach( "doTestMessage", function( data ) ) {
AlertShow("Сообщение", data, "info", "OK");
});

Теперь при запуске приложения будет появляться всплывающее сообщение с текстом "Тестовое сообщение":

interface_msg.png
Рисунок 2. Интерфейс со всплывающим сообщением

Заключение

Библиотека facefull-bridge (библиотека визуальных компонентов facefull входит в её состав) является open-source проектом, который был портирован и адаптирован под использование в ОС Нейтрино. Описанный в статье подход позволяет с минимальными усилиями начать создавать современные графические интерфейсы, например, для вывода таблиц, графиков или других важных показателей ФПО. При этом сохраняется производительность и функциональность этого ФПО, так как нативный код приложения остаётся нативным.

Полный исходный код описанного в статье примера использования библиотеки facefull-bridge доступен в публичном репозитории СВД ВС.

Список поддерживаемых визуальных компонентов, доступных "из коробки", постоянно расширяется. Сейчас доступны различные кнопки, переключатели, списки, графики, поля ввода, меню и много другое. Использование библиотеки не требует специальных знаний (только чтение документации на API), так как применяются стандартные HTML5, CSS и JS. Полученный графический интерфейс приложения легко переносится из нативного режима в браузер, если потребуется создание web-приложения на его основе.




Предыдущий раздел: перейти