Яндекс.Метрика

Песочница

QML и C++. Простой пример связки

image
QML технология красивая и радует глаз. Меня она очень заинтересовала, и я решил ее освоить. Но не тут то было, ибо я оказался тупым и беспомощным. Нигде в сети не нашел примера «для чайников» (наверно плохо искал), чтобы с нуля построить простейшее приложение QML и C++ в связке. Везде чего-то не хватало: или не учитывался Qt Creator, или код выдавал ошибки, или отсутствовали целые моменты, которые пользователи должны были сами знать. Официальная документация и примеры здесь на хабре также были с этими недостатками. Вот и решил после долгих попыток и ошибок написать такую статью для начинающих с подробнейшим описанием.

Задача. Нужно написать программу QML в связке с С++, где
1. На форме располагается кнопка, строка ввода, и поле вывода.
2. Требуется считать из строки ввода число, прибавляется 1, и ответ выводится в поле вывода.
3. Интерфейс написан на QML.
4. Функционал на С++, то есть нам нужно обеспечить взаимосвязь между QML и C++: кнопка QML вызывает С++ функцию, а функция меняет свойства QML объектов.

Создание базового приложение QML


Используем Qt Creator. Я использовал версию 2.3 с Qt 4.7.4
image
Создание QML приложения

1. Создаем GUI Приложение: Файл -> Новый файл или проект.... Там слева выбираем Проект Qt Widget, с справа GUI приложение Qt. Потом жмем внизу кнопку "Выбрать...".


2. В следующем окне выбираем название нашего проекта (без пробелов и русских букв). Например, в нашем случае это "Example".


3. В следующем окне у Вас должна стоять галочка у "Desktop". Если ее у Вас ее нет, то Вы неверно установили Qt Creator (или Вы намерено не хотите создавать десктопные приложения). Та сборка Qt Creator официальная, которую я ставил (2.3), по умолчанию почему-то десктопные части не устанавливала.


4. В следующем окне снимите галочку с пункта "Создать форму".


5. В следующем окне можно ничего не менять. И жмем кнопку "Завершить".


Редактирование файла проекта


6. Отредактируем файл проекта (у нас это Example.pro):


И добавим к строчке "QT += core gui" слово "declarative". В итоге получим строчку:

QT       += core gui declarative


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

7. По папке с проектом в Qt Creator щелкаем правой кнопкой и идем к пункту "Добавить новый..."


8. Выбираем слева "QML", а справа "Файл QML".


9. Назовем его "main".


10. Следующее окно без изменений.


11. В результате получим файл "main.qml" с текстом:

import QtQuick 1.0

Rectangle {
    width: 100
    height: 62
}


Создаем файл ресурсов

12. По папке с проектом в Qt Creator щелкаем правой кнопкой и идем к пункту "Добавить новый..."



13. Выбираем слева "Qt", а справа "Файл ресурсов Qt".


14. Назовем его "res".


15. Следующее окно без изменений.


В результате получим файд "res.qrc"

16. Добавим префикс. Для этого щелкнем по кнопке "Добавить", а там щелкнуть "Добавить префикс".


17. Измените текст префикса на "/".


18. Добавим наш QML файл. Для этого щелкнем по кнопке "Добавить", а там щелкнуть "Добавить файлы".


19. И выберем наш файл "main.qml". И файл добавится к ресурсам нашего приложения:


Если сейчас запустим наше приложение, то qml пока не увидим:


Редактирование исходников

Теперь займемся подключением qml, чтобы он заработал.

20. Перейдем к редактированию файла "mainwindow.h" (находится в заголовочных). Он имеет пока вид:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
};

#endif // MAINWINDOW_H


Поменяем его на такой вид:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtDeclarative/QDeclarativeView>
#include <QGraphicsObject>
#include <QtGui>
#include <QDeclarativeContext>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    QDeclarativeView *ui;
};

#endif // MAINWINDOW_H


Мы добавили #include <QtDeclarative/QDeclarativeView>, #include <QGraphicsObject> и др, добавили namespace, добавили ключевое слово explicit, и главное добавили QDeclarativeView *ui.

21. Теперь займемся редактированием файла mainwindow.cpp. Он имеет пока вид:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
}

MainWindow::~MainWindow()
{

}


Поменяем на такой:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    //Включаем наш QML
    ui = new QDeclarativeView;
    ui->setSource(QUrl("qrc:/main.qml"));
    setCentralWidget(ui);
    ui->setResizeMode(QDeclarativeView::SizeRootObjectToView);
}

MainWindow::~MainWindow()
{
    //Удаляем QML
    delete ui;
}


22. Запускаем наше приложение и получаем белое окно. Наш QML заработал.


Написание приложения


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

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

23. На данный момент main.qml выглядит так:

import QtQuick 1.0

Rectangle {
    width: 100
    height: 62
}




Отредактируем его, изменив главный прямоугольник-окно:

import QtQuick 1.0

//Главное окно
Rectangle {
    width: 300
    height: 300
    anchors.fill: parent

}


24. Добавим простейшую кнопку на нашу форму.

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

        //Размещаем в центре
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2;

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

        //Цвет кнопки
        color: "gray"

        //Текст кнопки
        Text {
            id: buttonLabel
            text: "Пуск"
            anchors.centerIn: parent;
        }

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


В результате main.qml примет вид:

import QtQuick 1.0

Rectangle {
    width: 300
    height: 300
    anchors.fill: parent
    
    //Кнопка
    Rectangle {
        id: button //Имя кнопки

        //Размещаем в центре
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2;

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

        //Цвет кнопки
        color: "gray"

        //Текст кнопки
        Text {
            id: buttonLabel
            text: "Пуск"
            anchors.centerIn: parent;
        }

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


Если мы запустим приложение,

то получим следующее:


25. Добавим строку ввода, куда пользователь будет вводить информацию с именем textinput.

//Строка ввода
    Rectangle {
        id: textinputRect //Имя строки ввода

        //Размещаем ниже
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2+40;

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

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

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


В результате main.qml примет вид:

import QtQuick 1.0

Rectangle {
    width: 300
    height: 300
    anchors.fill: parent

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

        //Размещаем в центре
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2;

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

        //Цвет кнопки
        color: "gray"

        //Текст кнопки
        Text {
            id: buttonLabel
            text: "Пуск"
            anchors.centerIn: parent;
        }

        //Действие мыши
        MouseArea {
            anchors.fill: parent
            id: mouseArea
        }
    }
    
    //Строка ввода
        Rectangle {
            id: textinputRect //Имя строки ввода
    
            //Размещаем ниже
            x: parent.width / 2 - button.width / 2;
            y: parent.height / 2 - button.height / 2+40;
    
            //Размеры строки ввода
            width: 100
            height: 18
    
            //цвет строки ввода
            color: "gray"
    
            TextInput {
                id: textinput
                objectName: "textinput"
                color: "#151515";
                selectionColor: "blue"
                font.pixelSize: 12;
                width: parent.width-4
                anchors.centerIn: parent
                focus: true
                text:"1"
                }
        }
}

Обратите внимание на следующее:
  • Для того, чтобы мы смогли обращаться к строке ввода из С++ у нас кроме параметра id есть еще параметр objectName, который заключается в двойные кавычки.
  • Информация, которая нам потом будет нужна, содержится в параметре text.


При запуске получим следующее:


26. Добавим поле вывода, куда программа будет выводить ответ с именем memo.

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

        //Размещаем ниже
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2+70;

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

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

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


В результате main.qml примет вид:

import QtQuick 1.0

Rectangle {
    width: 300
    height: 300
    anchors.fill: parent

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

        //Размещаем в центре
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2;

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

        //Цвет кнопки
        color: "gray"

        //Текст кнопки
        Text {
            id: buttonLabel
            text: "Пуск"
            anchors.centerIn: parent;
        }

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

    //Строка ввода
        Rectangle {
            id: textinputRect //Имя строки ввода

            //Размещаем ниже
            x: parent.width / 2 - button.width / 2;
            y: parent.height / 2 - button.height / 2+40;

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

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

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

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

            //Размещаем ниже
            x: parent.width / 2 - button.width / 2;
            y: parent.height / 2 - button.height / 2+70;

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

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

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


При запуске получим следующее:


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

C++ часть

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

 //Находим корневой элемент
    Root = ui->rootObject();
    //Соединяем C++ и QML, делая видимым функции С++ через элемент window
    ui->rootContext()->setContextProperty("window", this);


Получим следующее:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    //Включаем наш QML
    ui = new QDeclarativeView;
    ui->setSource(QUrl("qrc:/main.qml"));
    setCentralWidget(ui);
    ui->setResizeMode(QDeclarativeView::SizeRootObjectToView);
    
    //Находим корневой элемент
    Root = ui->rootObject();
    //Соединяем C++ и QML, делая видимым функции С++ через элемент window
    ui->rootContext()->setContextProperty("window", this);
}

MainWindow::~MainWindow()
{
    //Удаляем QML
    delete ui;
}


28. В добавленном коде у нас присутствует необъявленная переменная Root . Через нее мы потом будем производить поиск всех других дочерних элементов. Объявим ее в mainwindow.h в классе в разделе private:

QObject *Root;//корневой элемент QML модели


В результате получим:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtDeclarative/QDeclarativeView>
#include <QGraphicsObject>
#include <QtGui>
#include <QDeclarativeContext>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    QDeclarativeView *ui;
    QObject *Root;//корневой элемент QML модели
};

#endif // MAINWINDOW_H


29. Пусть у нас кнопка из QML будет вызывать С++ функцию под произвольным именем FunctionC. Объявим ее в mainwindow.h в классе в разделе public:

Q_INVOKABLE void FunctionC();//Функция C++ вызываемая из QML


В результате получим:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtDeclarative/QDeclarativeView>
#include <QGraphicsObject>
#include <QtGui>
#include <QDeclarativeContext>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    
    Q_INVOKABLE void FunctionC();//Функция C++ вызываемая из QML

private:
    QDeclarativeView *ui;
    QObject *Root;//корневой элемент QML модели
};

#endif // MAINWINDOW_H


Обратите внимание на ключевое слово Q_INVOKABLE. Именно оно делает видимой функцию для QML.

30. Теперь опишем нашу функцию в mainwindow.cpp:

void MainWindow::FunctionC()
{
    //Найдем строку ввода
    QObject* textinput = Root->findChild<QObject*>("textinput");
    
    //Найдем поле вывода 
    QObject* memo = Root->findChild<QObject*>("memo");
    
    QString str;//Создадим новую строковую переменную
    
    //Считаем информацию со строки ввода через свойство text
    str=(textinput->property("text")).toString();
    
    int a;
    a=str.toInt();//Переведем строку в число
    a++;//Добавим к числу 1
    
    QString str2;//Создадим еще одну строковую переменную
    str2=QString::number(a);//Переведем число в строку
    
    //Ну и наконец выведем в поле вывода нашу информацию
    memo->setProperty("text", str+"+1="+str2);
}


31. Ну и наконец добавим нашу функцию в обработчике нашей кнопки QML в файле main.qml. Обработчик действий мыши сейчас выглядит так:

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


Теперь же он станет таким:

        //Действие мыши
        MouseArea {
            anchors.fill: parent
            id: mouseArea
            //При нажатии вызвать фугкцию window.FunctionC()
            onClicked: window.FunctionC()
        }


В итоге получим:

import QtQuick 1.0

Rectangle {
    width: 300
    height: 300
    anchors.fill: parent

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

        //Размещаем в центре
        x: parent.width / 2 - button.width / 2;
        y: parent.height / 2 - button.height / 2;

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

        //Цвет кнопки
        color: "gray"

        //Текст кнопки
        Text {
            id: buttonLabel
            text: "Пуск"
            anchors.centerIn: parent;
        }

        //Действие мыши
        MouseArea {
            anchors.fill: parent
            id: mouseArea
            //При нажатии вызвать фугкцию window.FunctionC()
            onClicked: window.FunctionC()
        }
    }

    //Строка ввода
        Rectangle {
            id: textinputRect //Имя строки ввода

            //Размещаем ниже
            x: parent.width / 2 - button.width / 2;
            y: parent.height / 2 - button.height / 2+40;

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

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

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

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

            //Размещаем ниже
            x: parent.width / 2 - button.width / 2;
            y: parent.height / 2 - button.height / 2+70;

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

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

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


Обратите внимание на то, что функцию вызываем через window.

Вот и всё! Теперь запускаем программу и нажимаем на кнопку. Если Вы всё сделали правильно и я не допустил ошибок, то Вы увидете:

null

Ссылка на исходники: Скачать.
Ссылка на исполняемый файл: Скачать.