Pull to refresh

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

Reading time 6 min
Views 31K
Добрый день, %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» встречаются все чаще. Спасибо за внимание!
Tags:
Hubs:
+20
Comments 21
Comments Comments 21

Articles