Создание гибридного Qt Quick и C++ приложения

Добрый день, %username%!



Небольшая предыстория:


Некоторое время назад делал знакомому лабораторную, тематика которой – код Хэмминга. Программа представляла собой обыкновенное Qt приложение с минимальным набором контролов. Сдача прошла успешно, прошло некоторое время, и его теперь другу необходимо тоже сдать лабораторную на эту же тематику. Ту же программу, очевидно, сдавать нельзя. Тут возникает вопрос – как сделать программу с тремя кнопками и двумя текстбоксами непохожей на предыдущую? Мне в голову пришла мысль переписать интерфейс с помощью Qt Quick, а логику и расчеты программы оставить в С++, а заодно и рассказать интересующимся людям, как я обычно делаю подобные вещи. По Qt Quick не так много литературы, тем более на русском, так что очень надеюсь, что данная статья будет полезна и интересна. Уточню – основное внимание уделено способам взаимодействия Qt Quick и C++, в меньшей мере основам Qt и QML, но никак не подсчету кодов Хэмминга.

Создание каркаса


Итак, идут последние секунды установки Qt Sdk у меня на работе, мы начинаем.
Открываем QtCreator, создаем новый проект типа «GUI приложение Qt». Выбираем директорию, название, затем вот в это окошке мастера:
image
убираем галку «Создать форму». Она нам не понадобится, весь интерфейс будем писать ручками.
В сгенерированном файле mainwindow.h включите следующие заголовочные файлы:

#include #include <QtDeclarative/QDeclarativeContext>
#include <QtDeclarative/QDeclarativeView>

В конструкторе класса MainWindow внесем следующие изменения:

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QDeclarativeView* dv = new QDeclarativeView();
dv->setSource(QUrl("qrc:/main.qml"));
QDeclarativeContext* cntx = dv->rootContext();
cntx->setContextProperty("window",this);

dv->setResizeMode(QDeclarativeView::SizeRootObjectToView);
setCentralWidget(dv);
}


Немного поясню – мы создаем виджет типа QDeclarativeView – это элемент, способный отображать QML. Затем задаем имя корневого QML элемента — main.qml, который берется из ресурсов, которые добавим позже. Затем получаем контекст виджета, чтобы добавить в нем новое свойство «window”. Теперь из QML можно будет обращаться к объекту под названием window — это есть наша форма. Зачем это нужно станет ясно чуть позже. Ниже задается режим изменения размера – теперь корневой QML элемент будет изменять размер в соответствии с окном, а не наоборот. Метод setCentralWidget устанавливает для QMainWindow центральный виджет. Все, кто сталкивался с Qt, несомненно знакомы с ним.

Наш проект нуждается в QML файле, котором говорилось выше. Добавим его:
image
Теперь похожим образом добавляем файл ресурсов:
image

Он автоматически открывается, внизу жнем «Добавить» -> «Добавить префикс», появляется "/new/prefix1". Стираем и оставляем только «/» для простоты. Теперь добавляем туда файл main.qml ( “Добавить” -> «Добавить файл» ). Еще одна маленькая вещь – в файл *.pro нужно внести одно изменение
QT += core declarative
Таким нехитрым образом мы выставляем проекту зависимость от библиотеки QtDeclarative4

Теперь можно смело запускать проект и видеть … Белый квадрат 100 на 62 :) Но внешность обманчива – на самом деле это куда больше, чем белый квадрат, это приложение, интерфейс которого написан на QML, а логика на C++.
Примерно так начинается создание любого приложения, которое будет гибридом Qt Quick и C++. Основной момент — получение корневого контекста виджета QDeclarativeView, добавление нового свойства для доступа к объекту C++. Это свойство станет своего рода мостом между QML и С++.

Создание интерфейса и логики

Итак, этап создания базиса пройден, теперь логика работы.
В файле mainwindow.h в описание класса добавляем
public slots:
QString slotEncode(QString sIn);
QString slotDecode(QString sIn);
bool slotCheck(int val);

По названию ясно, что первые два слота будут заниматься кодированием и декодированием. Принимать на вход оба будут строку, которую пользователь введет в QML, а возвращать, соответственно, кодированный или декодированный вариант. Почему слоты вместо обыкновенных функций? Потому что именно слоты, а так же функции, объявленные с помощью макроса Q_INVOKABLE (подробнее см. документацию ) могут быть вызваны из QML посредством обращения к объекту, сделанному видимым с помощью setContextProperty(). Проще говоря, можно будет написать window.slotEncode() в QML :) Так же из QML кода доступны все свойства, объявленные с помощью макроса Q_PROPERTY — к ним можно обращаться прям по имени.

Реализацию данных слотов я возьму из старого проекта, и не приведу их тут в полном виде, можете скачать архив по ссылке ниже. Есть так же небольшая оговорка – в процессе кодирования и декодирования строится таблица, которую мне совсем не хотелось переписывать, поэтому она останется в первоначальном виде и просто будет показана в отдельном окне. Так же был добавлен слот slotCheck(int v) для служебных целей, использующийся только в процессе подсчета кода, и конечно, его нужно добавить, однако совсем не стоит задумываться, для чего он – ибо не относится к уроку!

Теперь осталось только сделать интерфейс программы :)
Пожалуй, я выдам ту версию интерфейса, которая делается минут за 15, обыкновенным программистом без творческого взгляда на мир (хотя обычно мне помогает моя девушка-художник делать делать подобные вещи):
import QtQuick 1.0

Rectangle {
width: 365
height: 200
gradient: Gradient {
GradientStop {
position: 0
color: "#696363"
}

GradientStop {
position: 1
color: "#000000"
}
}

Text {
id: text1
x: 37
y: 27
color: "#fbfbfb"
text: "Исходное слово"
style: Text.Raised
font.pixelSize: 12
}

Text {
id: text2
x: 198
y: 27
color: "#ffffff"
text: "Код Хэмминга"
style: Text.Raised
font.pixelSize: 12
}

// Поле ввода прямого кода.
Rectangle {
color: "#4de013"
radius: 6
border.width: 5
border.color: "#f5f9f4"
x: 36
y: 66
width: 133
height: 20
TextInput {
id: text_input1
width: parent.width - 20
height: parent.height - 5
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: ""

font.pixelSize: 12
}
}

// Поле для кода Хэмминга.
Rectangle {
color: "#48c819"
radius: 6
border.width: 5
border.color: "#f5f9f4"
width: 133
height: 20
x: 201
y: 66
TextInput {
id: text_input2
width: parent.width - 20
height: parent.height - 5
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: ""
font.pixelSize: 12
}
}

// Кнопки кодирования.
Rectangle {
id: bEncode
property alias text: label.text

width: 135
height: 60
radius: 20
border.width: 2
border.color: "#090606"

gradient: Gradient {
GradientStop {
id: gradientstop1
position: 0.01
color: "#ffffff"
}

GradientStop {
id: gradientstop2
position: 0.28
color: "#867d7d"
}

GradientStop {
id: gradientstop3
position: 1
color: "#000000"
}
}

signal clicked()
x: 36
y: 110

Text {
id: label
color: "#ffffff"
text: "Кодировать"
font.strikeout: false
font.pointSize: 10
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}

MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
text_input2.text = window.slotEncode(text_input1.text)
}
onEntered: {
bEncode.state = "green"
}
onExited: {
bEncode.state = "gray"
}
}
states: [
State {
name: "gray"
},
State {
name: "green"

PropertyChanges {
target: gradientstop1
color: "#ffffff"
}

PropertyChanges {
target: gradientstop2
color: "#34c22a"
}

PropertyChanges {
target: gradientstop3
color: "#000000"
}
}
]

}

// Кнопка декодирования.
Rectangle {
id: bDecode
property alias text: label.text

width: 136
height: 60
radius: 20
border.width: 2
border.color: "#090606"

gradient: Gradient {
GradientStop {
id: gradientstop21
position: 0.01
color: "#ffffff"
}

GradientStop {
id: gradientstop22
position: 0.28
color: "#867d7d"
}

GradientStop {
id: gradientstop23
position: 1
color: "#000000"
}
}

signal clicked()
x: 200
y: 110

Text {
id: label2
text: "Декодировать"
color: "#ffffff"
font.strikeout: false
font.pointSize: 10
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}

MouseArea {
id: mouseArea2
anchors.fill: parent
hoverEnabled: true
onClicked: {
text_input1.text = window.slotDecode(text_input2.text)
}
onEntered: {
bDecode.state = "green"
}
onExited: {
bDecode.state = "gray"
}
}
states: [
State {
name: "gray"
},
State {
name: "green"

PropertyChanges {
target: gradientstop21
color: "#ffffff"
}

PropertyChanges {
target: gradientstop22
color: "#34c22a"
}

PropertyChanges {
target: gradientstop23
color: "#000000"
}
}
]

}
}



Совсем не уверен, подсветится ли синтаксис верно — будем надеяться, что схожесть с JavaScript сыграет свою положительную роль!

Ссылка на исходники — Тык!

Результат работы:
image

Небольшой итог


Результатом нашей работы стало гибридное приложение. В перспективе создание подобных приложений позволяет совместить нестандартный интерфейс QtQuick с высокопроизводительным С++ кодом. Надеюсь, статья будет полезной интересующимся данными технологиями, ибо вопросы «Дайте литературу по Qt Quick» встречаются все чаще. Спасибо за внимание!
Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 21

    +21
    Постараюсь учесть в одном комментарии все замечания, которые хотелось бы оставить

    0. Неформатированный кодл читать не возможно. Хабр поддерживает тег source.

    1. Вся «литература» aka Документация по Qt Quick есть здесь или здесь, а еще в assistant

    2. Русский текст в исходниках зло (конечно это приемлимо для «курсача», но никогда в продакшене), если хотите кнопочки на русском, пишите qsTr(«English text») потом переводите и подключайте транслятор.

    3. Стилистика: имена переменных выбраны не удачно, вот вы, не зная, что делает программа, смогли бы сказать, чем отличается text_input1 от text_input2? Я нет. Но, если бы они назывались sourceWordInput и HammingCodeInput, было бы куда нагляднее.

    4. Кнопка используется у вас два раза, если потребуется третья вам придется городить новый Rectangle,
    гораздо лучше вынести кнопку в отдельный компонент (т.е. qml-файл), ято позволит ее использовать многократно.

    5. Градиенты — штука безусловно клевая, но, к сожалению, сейчас жутко медленая, я (да и многие другие) рекомендую отказываться от градиента там, где это возможно, и заменять его изображением.

    6. MainWindow не нужен, QDeclarativeView сам по себе является виджетом. Достаточно было в main.cpp написать:
        QDeclarativeView view;
        view.setResizeMode(QDeclarativeView::SizeRootObjectToView);
        view.setSource("qrc:/main.qml");
        view.show();
    

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

    В целом приятно что люди интересуются данной технологией, потому как она имеет огромный потенциал, но давайте все же самосовершествоваться и стараться писать хороший код с помощью хорошей технологии.
    Спасибо.
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        Ну как я понял, этого делать было нельзя ибо такое приложение было бы похожим на то что сдавал предыдущий человек.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Ну например можно из программы убрать кнопки и приклеиться к textChanged и пытаться кодировать/декодировать на лету :)

            У нас к счастью интересовались.
            Про Qt не понял, почему сразу перестает интересовать?
              0
              Я так понял, что они не знают библиотеку, следовательно не пытаются разбираться…
              А может, если в группе Qt знает парочка человек, то и подозрений нет?
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  А можно поинтересоваться названием ВУЗа?
                  ЗЫ. Сам я ХНУРЭ заканчивал.
                  • НЛО прилетело и опубликовало эту надпись здесь
                0
                > А вообще преподаватели, интересующиеся кодом, пересчитываются на пальцах

                *Вздыхая* даа…

                Кстати в некоторых ВУЗах Qt преподают, очень сожалею, что мой к таким не относится…
              0
              Человеку и правда не нравилось, его слова «Все равно похоже»…
              0
              Так же попробую ответить одним постом:
              0. Первый раз пишу статью, честно — не знал, что тег нормально не подсветит!
              1. Просят именно литературу, а не документацию. Урок, я думаю, лучше английской спецификации для новичков.
              2. Ааааа тут просто nc, вы думаете я не работаю? Не оформляю официальный код как надо? Зачем загромождать пример, именно пример, сторонней информацией?
              3. Эти вещи набросал в редакторе, очевидно. Я вроде бы сказал насчет 15-ти минут...
              4. Так и было, кнопочку уже использовал и не раз, файл носил гордое название Button.qml, для этой цели там и сигнал кстати есть... Но опять же не хотелось ничего лишнего.
              5. Учту, но медлительности, собственно, не замечал - даже на симбианах :)
              6. Ответ все остальное сразу - скажите, что проще понять новичку - подключение плагина или обычный вызов слотов через свойство?... Ответ очевиден!

              В целом, очень рад, что первый же коммент оказался таким полным, однако я лично считаю, что не стоит относиться к уроку, как к проекту на сдачу :) И холивар тоже кстати разводить не хочется насчет индивидуальных предпочтений. Спасибо.
                +1
                  0
                  ну он же уроК :) Это же хорошо, когда есть урокИ?
                    +1
                    Ну вот могу предложить вот это lorcode.org/wiki/Qt_in_Education_Course_Material
                    Но перевод пока что в процессе.
                      0
                      Прошу прощения, по QtQuick уже перевели статью. Вот, тут чисто архив с уроками Qt Quick ge.tt/8ilffC5
                        0
                        Приятно видеть, что я не зря старался. А вообще перевод заглох по причине нехватки людей. Если у кого то есть желание помочь — было бы замечательно, с полным переводом этих уроков можно было бы надеятся на внедрение Qt в образовательный процесс. Вся инфраструктура уже готова (), все статусы «в процессе» на самом деле равнозначны «нет перевода», т.к. взявшиеся люди видимо «забили». Если кто-нибудь желает взяться за перевод — пишите в личку, может и я снова займусь.
                          0
                          Я бы очень хотел помочь развитию проекта. Но у меня была сессия, сейчас начинается практика и к тому же нужно готовиться к сертификационному экзамену.

                          Вот после 30 июня я смогу помочь с переводами, самому хочется хорошо разбираться. Занимался 2 переводами в Full Circle.
                            0
                            Приятно, что моя статья дала хоть и небольшой, но толчок развитию русскоязычного сообщества Qt :)
                  0
                  Я связь QML с С++ изучал по gutenberg.troll.no/4.7-snapshot/qtbinding.html, когда еще доки не было. Примеры отличные, как впрочем и вся дока Qt.
                    +1
                    Я как-то писал немного более сложный пример для подобных целей.
                    Вот тут можно почитать. В основном про встраивание C++-класса в QML.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое