В статье описывается создание приложения QML через Qt Quick с выполнением кода на C++.

В качестве среды для разработки используется Qt 5.6.0 для Windows 64 bit под компилятором Visual Studio.

В статье приведена сокращенная версия данной статьи.

В статье рассказывается о подобном приложении, но с использованием компонентов Qt Quick Controls.

Подготовка

Создадим Qt Quick приложение:

Создание нового проекта

Создание Qt Quick приложения

Ввод названия проекта

Не рекомендую выбирать With ui.qml. По крайней мере не все функции QML с таким файлом работали. Либо я что-то делал не то:

Выбор последней версии Qt Quick

Выбор компилятора

Выбор системы управления версиями

Созданный проект

И переведите разработку приложения в режим Release:

Перевод компилирования проекта в режим Release

Построение интерфейса

Откроем файл main.qml:

Файл main.qml

Удалим элемент MouseArea и элемент Text. Они тут демонстрационные:

Удаляемый код

Очищенный QML файл

Добавим в основной элемент Window следующую конструкцию:

Column {
  spacing: 5
  anchors.centerIn: parent;

  //Кнопка
  Rectangle {
    id: button //Имя кнопки

    //Размеры кнопки
    width: 100
    height: 30

    //Цвет кнопки
    color: "#0066ff"

    //Текст кнопки
    Text {
      id: buttonLabel
      text: "Сложить"
      color: "#ffffff";
      anchors.centerIn: parent;
    }

    //Действие мыши
    MouseArea {
      id: mouseArea1
      anchors.fill: parent
      hoverEnabled: true;
    }
  }

  //Строка ввода первого числа
  Rectangle {
    id: textinputRect1 //Имя строки ввода

    //Размеры строки ввода
    width: 100
    height: 18

    //цвет строки ввода
    color: "#ffffff"

    TextInput {
      id: textinput1
      objectName: "textinput1"
      color: "#0066ff";
      selectionColor: "blue"
      font.pixelSize: 12;
      width: parent.width-4
      anchors.centerIn: parent
      focus: true
      text:"1"
    }
  }

  //Строка ввода второго числа
  Rectangle {
    id: textinputRect2 //Имя строки ввода

    //Размеры строки ввода
    width: 100
    height: 18

    //цвет строки ввода
    color: "#ffffff"

    TextInput {
      id: textinput2
      objectName: "textinput2"
      color: "#0066ff";
      selectionColor: "blue"
      font.pixelSize: 12;
      width: parent.width-4
      anchors.centerIn: parent
      focus: true
      text:"1"
    }
  }

  //Поле вывода
  Rectangle {
    id: memoRect //Имя поля вывода

    //Размеры поле вывода
    width: 100
    height: 35

    //Цвет поля вывода
    color: "#ffffff"

    TextEdit{
      id: memo
      color: "#0066ff"
      objectName: "memo"
      wrapMode: TextEdit.Wrap
      width:parent.width;
      readOnly:true
    }
  }
}

Получим такой код файла main.qml:

import QtQuick 2.6
import QtQuick.Window 2.2

Window {
  visible: true
  Column {
    spacing: 5
    anchors.centerIn: parent;

    //Кнопка
    Rectangle {
      id: button //Имя кнопки

      //Размеры кнопки
      width: 100
      height: 30

      //Цвет кнопки
      color: "#0066ff"

      //Текст кнопки
      Text {
        id: buttonLabel
        text: "Сложить"
        color: "#ffffff";
        anchors.centerIn: parent;
      }

      //Действие мыши
      MouseArea {
        id: mouseArea1
        anchors.fill: parent
        hoverEnabled: true;
      }
    }

    //Строка ввода первого числа
    Rectangle {
      id: textinputRect1 //Имя строки ввода

      //Размеры строки ввода
      width: 100
      height: 18

      //цвет строки ввода
      color: "#ffffff"

      TextInput {
        id: textinput1
        objectName: "textinput1"
        color: "#0066ff";
        selectionColor: "blue"
        font.pixelSize: 12;
        width: parent.width-4
        anchors.centerIn: parent
        focus: true
        text:"1"
      }
    }

    //Строка ввода второго числа
    Rectangle {
      id: textinputRect2 //Имя строки ввода

      //Размеры строки ввода
      width: 100
      height: 18

      //цвет строки ввода
      color: "#ffffff"

      TextInput {
        id: textinput2
        objectName: "textinput2"
        color: "#0066ff";
        selectionColor: "blue"
        font.pixelSize: 12;
        width: parent.width-4
        anchors.centerIn: parent
        focus: true
        text:"1"
      }
    }

    //Поле вывода
    Rectangle {
      id: memoRect //Имя поля вывода

      //Размеры поле вывода
      width: 100
      height: 35

      //Цвет поля вывода
      color: "#ffffff"

      TextEdit{
        id: memo
        color: "#0066ff"
        objectName: "memo"
        wrapMode: TextEdit.Wrap
        width:parent.width;
        readOnly:true
      }
    }
  }
}

Обратите внимание, что для всех элементов, к которым вы потом захотите обращаться в C++, пропишите не только свойство id, но и свойство objectName со строковым значением, совпадающим с id.

Кратко пробежимся по коду, который у нас получился. Все компоненты мы расположили в главном корневом Window. В нем находится компонент Column, который располагает находящиеся в нем компоненты в столбик (вертикальная разметка компонентов):

Column {
  spacing: 5
  anchors.centerIn: parent;
  ...
}

Внутри него находится четыре компонента Rectangle.

Первый имеет id равное button, и это обычная кнопка, а точнее закрашенный прямоугольник с текстом внутри и областью для воздействия мышью с id равным mouseArea1:

//Кнопка
Rectangle {
  id: button //Имя кнопки

  //Размеры кнопки
  width: 100
  height: 30

  //Цвет кнопки
  color: "#0066ff"

  //Текст кнопки
  Text {
    id: buttonLabel
    text: "Сложить"
    color: "#ffffff";
    anchors.centerIn: parent;
  }

  //Действие мыши
  MouseArea {
    id: mouseArea1
    anchors.fill: parent
    hoverEnabled: true;
  }
}

Второй и третий Rectangle содержит компонент TextInput, то есть строку для ввода информации. В них будем записывать два наших числа:

//Строка ввода первого числа
Rectangle {
  id: textinputRect1 //Имя строки ввода

  //Размеры строки ввода
  width: 100
  height: 18

  //цвет строки ввода
  color: "#ffffff"

  TextInput {
    id: textinput1
    objectName: "textinput1"
    color: "#0066ff";
    selectionColor: "blue"
    font.pixelSize: 12;
    width: parent.width-4
    anchors.centerIn: parent
    focus: true
    text:"1"
  }
}

//Строка ввода второго числа
Rectangle {
  id: textinputRect2 //Имя строки ввода

  //Размеры строки ввода
  width: 100
  height: 18

  //цвет строки ввода
  color: "#ffffff"

  TextInput {
    id: textinput2
    objectName: "textinput2"
    color: "#0066ff";
    selectionColor: "blue"
    font.pixelSize: 12;
    width: parent.width-4
    anchors.centerIn: parent
    focus: true
    text:"1"
  }
}

В последнем четвертом Rectangle содержится поле TextEdit для вывода текста:

//Поле вывода
Rectangle {
  id: memoRect //Имя поля вывода

  //Размеры поле вывода
  width: 100
  height: 35

  //Цвет поля вывода
  color: "#ffffff"

  TextEdit{
    id: memo
    color: "#0066ff"
    objectName: "memo"
    wrapMode: TextEdit.Wrap
    width:parent.width;
    readOnly:true
  }
}

Если мы запустим приложение, то получим следующее:

Запущенное приложение

Итак, мы описали интерфейс нашей программы.

C++ часть

При нажатии на кнопку пока ничего не происходит. Исправим это. Для начала установим взаимосвязь между QML моделью и C++ кодом. Для этого создадим класс, через которое будем осуществлять взаимодействие.

Правой кнопкой щелкнем по проекту и выбираем пункт Add New…:

Добавление нового элемента в проекте

Там выбираем C++ Class:

Создание нового класса

Там вводим название нашего нового класса, например, HandlerSignals, также добавив подключение заголовочного файла QObject и базовым классом объявляем QObject:

Установка настроек класса

Настройка системы контроля версий для класса

В итоге получаем наш класс:

Созданный класс

Начнем его редактирование. Перейдем вначале в заголовочный файл handlersignals.h.

Мы будем создавать экземпляр нашего объекта и передавать в качестве родителя сцену QML объектов.

Подключите файл #include <QVariant>.

Файл handlersignals.h:

#ifndef HANDLERSIGNALS_H
#define HANDLERSIGNALS_H

#include <QObject>
#include <QVariant>

class HandlerSignals : public QObject
{
Q_OBJECT
public:
explicit HandlerSignals(QObject *parent = 0);

signals:

public slots:
};

#endif // HANDLERSIGNALS_H

Файл handlersignals.cpp (без изменений):

#include "handlersignals.h"

HandlerSignals::HandlerSignals(QObject *parent) : QObject(parent)
{

}

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

void HandlerSignals::cppSlot(const QString &msg) {
  //Найдем строки ввода
  QObject* textinput1 = this->parent()->findChild<QObject*>("textinput1");
  QObject* textinput2 = this->parent()->findChild<QObject*>("textinput2");
  //Найдем поле вывода
  QObject* memo = this->parent()->findChild<QObject*>("memo");

  //Считаем информацию со строк ввода через свойство text
  QString str1=(textinput1->property("text")).toString();
  QString str2=(textinput2->property("text")).toString();

  int a = str1.toInt();//Переведем строку в число
  int b = str2.toInt();//Переведем строку в число

  int c = a + b; //Вычисления наши

  QString strResult=QString::number(c);//Переведем результат в строку

  //Ну и наконец выведем в поле вывода нашу информацию
  memo->setProperty("text", str1+" + "+str2+" = "+strResult+" "+msg);
}

Получим следующие файлы.

Файлы handlersignals.h:

#ifndef HANDLERSIGNALS_H
#define HANDLERSIGNALS_H

#include <QObject>
#include <QVariant>

class HandlerSignals : public QObject
{
Q_OBJECT
public:
explicit HandlerSignals(QObject *parent = 0);

signals:

public slots:
void cppSlot(const QString &msg);
};

#endif // HANDLERSIGNALS_H

Файл handlersignals.cpp:

#include "handlersignals.h"

HandlerSignals::HandlerSignals(QObject *parent) : QObject(parent)
{

}

void HandlerSignals::cppSlot(const QString &msg) {
  //Найдем строки ввода
  QObject* textinput1 = this->parent()->findChild<QObject*>("textinput1");
  QObject* textinput2 = this->parent()->findChild<QObject*>("textinput2");
  //Найдем поле вывода
  QObject* memo = this->parent()->findChild<QObject*>("memo");

  //Считаем информацию со строк ввода через свойство text
  QString str1=(textinput1->property("text")).toString();
  QString str2=(textinput2->property("text")).toString();

  int a = str1.toInt();//Переведем строку в число
  int b = str2.toInt();//Переведем строку в число

  int c = a + b; //Вычисления наши

  QString strResult=QString::number(c);//Переведем результат в строку

  //Ну и наконец выведем в поле вывода нашу информацию
  memo->setProperty("text", str1+" + "+str2+" = "+strResult+" "+msg);
}

В прошлом варианте данного материала (когда писал, например, для Qt 5.5.0) я регистрировал в QML экземпляр класса и вызывал методы-слоты класса. Но при этом QML становился привязан к C++ коду, что не есть хорошо. Поэтому сейчас поступим по-другому. Мы в QML документе просто отправим сигнал, что нужно посчитать сумму двух чисел, а уже в C++ поймаем данный сигнал и отправим на обработку в класс.

Вначале отправим сигнал. В main.qml в головном элементе Window добавим сигнал:

signal qmlSignal(string msg)

Обратите внимание, что объявление сигнала надо добавлять именно в корневой элемент, иначе мы сигнал не найдем.

А в кнопке в области действия мыши пропишем уже отправку сигнала:

//Действие мыши
MouseArea {
  id: mouseArea1
  anchors.fill: parent
  hoverEnabled: true;
  onClicked: {qmlSignal("яблок")}
}

В итоге получим файл main.qml:

import QtQuick 2.6
import QtQuick.Window 2.2

Window {
  visible: true

  signal qmlSignal(string msg)

  Column {
    spacing: 5
    anchors.centerIn: parent;

    //Кнопка
    Rectangle {
      id: button //Имя кнопки

      //Размеры кнопки
      width: 100
      height: 30

      //Цвет кнопки
      color: "#0066ff"

      //Текст кнопки
      Text {
        id: buttonLabel
        text: "Сложить"
        color: "#ffffff";
        anchors.centerIn: parent;
      }

      //Действие мыши
      MouseArea {
        id: mouseArea1
        anchors.fill: parent
        hoverEnabled: true;
        onClicked: {qmlSignal("яблок")}
      }
    }

    //Строка ввода первого числа
    Rectangle {
      id: textinputRect1 //Имя строки ввода

      //Размеры строки ввода
      width: 100
      height: 18

      //цвет строки ввода
      color: "#ffffff"

      TextInput {
        id: textinput1
        objectName: "textinput1"
        color: "#0066ff";
        selectionColor: "blue"
        font.pixelSize: 12;
        width: parent.width-4
        anchors.centerIn: parent
        focus: true
        text:"1"
      }
    }

    //Строка ввода второго числа
    Rectangle {
      id: textinputRect2 //Имя строки ввода

      //Размеры строки ввода
      width: 100
      height: 18

      //цвет строки ввода
      color: "#ffffff"

      TextInput {
        id: textinput2
        objectName: "textinput2"
        color: "#0066ff";
        selectionColor: "blue"
        font.pixelSize: 12;
        width: parent.width-4
        anchors.centerIn: parent
        focus: true
        text:"1"
      }
    }

    //Поле вывода
    Rectangle {
      id: memoRect //Имя поля вывода

      //Размеры поле вывода
      width: 100
      height: 35

      //Цвет поля вывода
      color: "#ffffff"

      TextEdit{
        id: memo
        color: "#0066ff"
        objectName: "memo"
        wrapMode: TextEdit.Wrap
        width:parent.width;
        readOnly:true
      }
    }
  }

}

Запустите приложение. Конечно, у вас кнопка не сработает пока, но вы сможете проверить написали всё верно или нет.

Теперь, перейдем в файл main.cpp, чтобы прописать создание экземпляра класса и связать его с нужными объектами.

Добавим заголовочный файл:

#include "handlersignals.h"

А в функции main после загрузки QML файла пропишем строчки:

QObject* root = engine.rootObjects()[0];

HandlerSignals *handlerSignals= new HandlerSignals(root);

QObject::connect(root, SIGNAL(qmlSignal(QString)),
handlerSignals, SLOT(cppSlot(QString)));

В первой строчке мы находим корневой объект в QML модели. Во второй строчке создаем экземпляр нашего класса. И в третьей строчке связываем наш слот из класса и сигнал из QML файла.

Теперь main.cpp выглядит вот так:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "handlersignals.h"

int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);

  QQmlApplicationEngine engine;
  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

  QObject* root = engine.rootObjects()[0];

  HandlerSignals *handlerSignals= new HandlerSignals(root);

  QObject::connect(root, SIGNAL(qmlSignal(QString)),
  handlerSignals, SLOT(cppSlot(QString)));

  return app.exec();
}

Если всё сделали правильно, то при запуске приложения и нажатия на кнопку получим следующее:

Результат выполнения программы

Кстати, если у вас в QML используется компонент со своими сигналами, то связь вы устанавливаете с ним. Например:

QObject* ob = root->findChild<QObject*>("flatButton");
QObject::connect(ob, SIGNAL(qmlSignalFlatButton(QString)),
handlerSignals, SLOT(cppSlot(QString)));

Обратите внимание

Если вы запускаете приложение в стандартном эмуляторе от Android Studio, то ссылки http:\ открываться не будут в отличии от https:\. На реальном физическом устройстве будут открываться оба вида ссылок.