Pull to refresh

Qt Components для десктопа

Reading time6 min
Views15K
Этот пост участвует в конкурсе „Умные телефоны за умные посты“.

Как известно, QML основан на графических примитивах и даже банальные кнопки приходится рисовать из них (прямоугольник, градиент, текст, область мыши, например). Для облегчения процесса создания UI были созданы Qt Components. Они позволяют создавать кнопки, текстовые поля, дропбоксы и прочие контролы для ввода или отображения данных. В официальном релизе сейчас компоненты для
мобильных платформ: Symbian и MeeGo. Но в Qt Labs также есть версия для десктопных платформ, которая (через какое-то время) будет включена в Qt5, как основное средство создания пользовательского интерфейса (как мы помним, в Qt5 на первом месте идет QML, а QtGui уже на втором). При этом компоненты сами подстраиваются под нативный вид операционной системы.

Для того, чтобы показать возможности десктопных компонентов, а заодно обозначить основные проблемы и преимущества их использования, я создал минимальное графическое приложение на классическом QtGui и на компонентах. Нет, не Hello World. Калькулятор. Без реализации расчетов, просто графический интерфейс.



Под катом собраны запуски под 3 операционными системами и некоторые рассуждения на тему.


Итак, калькулятор включает в себя дисплей и две вкладки с разными кнопками: простой набор (на скриншоте выше) и продвинутый.


Разберемся сначала с кодом.

Калькулятор на Qt Components


Первое, и самое интересное, что надо знать при работе с компонентами, это то, что они не используют QmlApplicationViewer или QDeclarativeView явно. Qml-файл запускается через QDeclarativeEngine. А вот уже компоненты сами создают QDeclarativeView, когда это нужно (то есть когда создается новое окно). Для облегчения работы, в репозитории с компонентами лежит класс QmlDesktopViewer, который легко подключить в свой проект, подключив его в общее дерево исходников и использовав в main.cpp.
#include <QtGui/QApplication>
#include "qmldesktopviewer.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QmlDesktopViewer *viewer = new QmlDesktopViewer();
    viewer->open("qrc:/main.qml");
    QObject::connect(&app, SIGNAL(lastWindowClosed()), viewer, SLOT(quit()));
    return app.exec();
}


Теперь обратимся к QML. Создадим файл main.qml, который и будет запускаться при старте приложения
import QtQuick 1.1
import QtDesktop 0.1

Window {
    function buttonPressed(name, offset) {
        var text = display.text
        var pos = display.cursorPosition
        display.text = text.substr(0,pos) + name + text.substr(pos)
        display.cursorPosition = pos+name.length-offset
    }

    title: "Calc on Components"

    width: 400
    height: 600
    maximumHeight: 900
    minimumHeight: 250
    maximumWidth: 1000
    minimumWidth: 300
    visible: true

    id: mainWindow

    TextField {
        id: display
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 4
        height: 60; font.pixelSize: 40
    }

    TabFrame {
        id: frame
        position: "North"
        anchors.top: display.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 4

        Tab {
            id: simpleTab
            title: "Simple"
            anchors.margins: 4

            CalcButtonRow {
                anchors.left: parent.left
                anchors.right: parent.right
                height: parent.height/4
                anchors.top: parent.top
                names: ["7","8","9","+"]
                onButtonPressed: mainWindow.buttonPressed(name, 0)
            }
// Еще 3 ряда
        }
//Еще одна вкладка
    }

    Component.onCompleted: display.focus = true
}

Я слегка подсократил исходники, но всего строк в этом файле 125.

Итак, главным элементом здесь является Window. Он как раз и создает QDeclarativeView, на который выводятся все его элементы. Для Window можно задать заголовок, высоту, ширину, максимальные и минимальные высоту и ширину, видимость. Вообщем, стандартный набор для окна. Первым элементом в окне является наш дисплей, реализованный на TextField. Если кто-либо разрабатывал на мобильных Qt Components, то этот элемент должен быть знакомым. У него примерно такое же API, как и в мобильных компонентах.

Следующим элементом идет TabFrame, который в итоге преобразуется в QTabWidget. В этот элемент мы добавим два элемента Tab (один я сократил, он точно такой же). Все содержимое Tab будет отображено на данной вкладке (то есть это аналог добавления виджетов на виджет вкладки или на его Layout в QtGui).

CalcButtonRow в данном случае это наш компонент, который содержит 4 кнопки (с названиями, передаваемыми через свойство names) и растягивает их по ширине и высоте, для того чтобы кнопки занимали все пространство вкладки (по отображению получается аналог QGridLayout на виджете вкладки в QtGui). Решение в лоб и подвержено многим проблемам в продакшене, но для теста сойдет.
import QtQuick 1.0
import QtDesktop 0.1

Item {
    signal buttonPressed(string name)
    property variant names: ["","","",""]

    Button {
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.bottom: parent.bottom
        width: parent.width/4
        text: names[0]
        onClicked: buttonPressed(text)
    }
//Другие 3 кнопки
}

Исходник я опять сократил (в оригинале 43 строки). Боковые кнопки позиционируются к бокам, центральные — к центру родительского элемента. Все 4 с шириной в четверть ширины родительского элемента.
Здесь мы встречаем элемент Button, который также должен быть знаком по мобильным компонентам. В обработчике нажатия на кнопку мы испускаем сигнал нашего элемента с текстом, написанным на кнопке.

Вернемся обратно в main.qml
// Вывод на дисплей
    function buttonPressed(name, offset) {
        var text = display.text
        var pos = display.cursorPosition
        display.text = text.substr(0,pos) + name + text.substr(pos)
        display.cursorPosition = pos+name.length-offset
    }

// Обработчик для простых кнопок 
onButtonPressed: mainWindow.buttonPressed(name, 0)

// Обработчик для кнопок с функциями (sin(), cos() и т.д.)
onButtonPressed: mainWindow.buttonPressed(name, 1)

При нажатии на кнопку вызывается функция buttonPressed, которая добавляет текст кнопки в строку на текущей позиции курсора и сдвигает ее на нужное количество символов (для функций это количество меньше на один, так как курсор должен быть внутри скобок, а не после них).

Это ВЕСЬ код. Итого получилось около 160 строк qml.
Аналогичный код, написанный на QtGui (без дизайнера, с генерацией всех объектов в конструкторе в циклах, с QSignalMapper и прочими способами уменьшить код для подобных форм) занял около 130 строк c++ кода (без учета main.cpp).

То есть количество кода примерно равное. Посмотрим на результаты в различных ОС

Linux KDE4


Все примерно одинаково, но. В QML версии немного по другому выглядят кнопки (разные градиенты), небольшой косяк с отрисовкой вкладок, всегда горит синяя рамка фокуса (на QtGui версии она пропадает, если окно неактивное), нет анимаций при добавлении текста на текстовое поле и при переключении табов. Неплохо.

Windows 7


В QML версии опять небольшой косяк с отображением вкладок, опять рамка фокуса видна всегда и плюс остаются следы от hover'а мышки на кнопках (которые в QtGui версии, как им и положено, исчезают). Опять же неплохо.

Mac OS X 10.6



Традиционно горящая рамка фокуса в qml версии, к ней еще добавилась вечно горящая вкладка на неактивном окне. НО. Для Mac OS X пришлось слегка модифицировать приложение на QtGui, потому что когда я запустил его в первый раз, окно выглядело вот так.

Пришлось для QLineEdit явно указать minimumHeight. QML версия не переделывалась.

Плюшки от использования QML вместо QtGui


Вы спросите, зачем нам эти компоненты, если есть QtGui. Ответ прост. Для создания более приятного и богатого интерфейса. Я не буду говорить про всем известные возможности типа кинетической прокрутки, красивых анимаций при смене объектов и все такое.

Что можно добавить в UI калькулятора? Первое, что приходит на ум, это зависимость размера шрифта на дисплее от введенного текста. Чем текста больше — тем меньше шрифт. Поменяем для этого наш TextField с дисплеем на
    TextField {
        function adaptFontSize() {
            var width = display.width
            var length = display.text.length
            var newFontSize = 40
            if (width/26 < length) {
                newFontSize = 1.5*(display.width/display.text.length)
                if (newFontSize < 20)
                    newFontSize = 20
            }
            display.font.pixelSize = newFontSize
        }

        id: display
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 4
        height: 60; font.pixelSize: 40
        onWidthChanged: adaptFontSize()
        onTextChanged: adaptFontSize()

        Behavior on font.pixelSize { NumberAnimation{easing: Easing.OutElastic; duration: 200}}
    }

Как обычно, магия QML в действии. Мы добавили одну функцию, два обработчика изменения свойств и элемент Behavior. И все, теперь у нас изменяется динамически шрифт, да еще и с анимацией. Сколько это займет кода на QtGui (именно вместе с анимацией)? Чуть поболее 15 строк. Придется подключать Animation Framework.




Можно придумать еще много других примеров, когда QML облегчает создание UI приложений для десктопа.

Текущее состояния Qt Components для десктопа


Сейчас этот проект находится где-то в районе альфы и говорить о его повсеместном коммерческом применении еще рано, но уже сейчас оно работает вполне сносно и позволяет интегрировать виджеты QtGui в QML-интерфейс. Да, конечно всегда можно их вставить через проксирование в QGraphicsObject, но это гораздо сложнее, это урезает часть возможностей по взаимодействию с виджетами и это занимает больше кода. На скриншоте отображены 4 запуска экзампла из компонентов. На них отображено текущее состояние. То есть, уже можно сделать многое. На скриншоте не видно, но правая кнопка на тулбаре открывает новое окно.


Плюсы и минусы использования компонентов вместо QtGui


Плюсы:
  • Богатые возможности создания интерфейса
  • Упрощение реализации сложных интерфейсов
  • Возможность использовать стандартные QML элементы без необходимости создавать лишние QDeclarativeView
  • Упрощенный перенос приложения на мобильные устройства (общее API у всех компонентов очень сходное)
  • Нет зависимости от платформы (QtGui иногда зависим. Пример виден на запуске в Mac OS X)

Минусы:
  • Это все же еще глубокая альфа, множество минорных и не очень багов отображения
  • Усложнение взаимодействия со слоем данных (свойства декларативного контекста, плагины и прочие способы)
  • Непривычно для тех, кто не имел дела с QML
  • Возможно не все (и я сомневаюсь что даже в релизе они будут поддерживать весь функционал QtGui)

Ссылки


Tags:
Hubs:
+54
Comments56

Articles