QML и C++. Как заставить работать вместе

Основы

Правила QML:

1) В каждом QML документе есть только один корневой элемент

2) В каждом QML документе есть как минимум один импорт

3) Каждый QML документ должен начинаться с заглавной буквы

4) Название каждого элемента(id) должно начинаться с маленькой буквы

Рекомендации:

1) корневой элемент каждого документа лучше называть одинаково. К примеру -

id: root

Оформление кода QML

Оформление кода является весьма важным элементов программы. С помощью него поддержка программы и ориентация в её коде становятся существенно легче.

Есть несколько базовых общепринятых правил по оформлению кода QML:

1) каждый уровень вложения отделяется четырьмя пробелами

2) открывающая блок кода скобка { отделяется пробелом на той же строке

3) в случае нескольких команд на одной строке пробел ставится после каждой точки с запятой, чтобы визуально отделить последующие команды

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

Element1.qml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

 // название для любого используемого qml элемента начинается с заглавной буквы (исключение составляет разве что main.qml)
import QtQuick 2.5 // подключение стандартных библиотек QML
...
 
import "my.components" 1.0 as Components // подключение пользовательских библиотек
...
 
import "code.js" as NeededCode // подключение кода JS
...
 
// оформление коревого элемента документа
Item { // в документе всегда только один корневой элемент
    id: root // желательно всегда его называть одинаково
 
    property ... // объявления свойств
    ...
    
    signal ... // объявления сигналов
    ...
 
    on...Changed: // обработчики сигналов объявленных выше свойств
    ...
 
    height: implicitHeight // инициализация базовых свойств элемента
    width: implicitWidth
    ... 
 
    QtObject { id: d } // внутренние(private) свойства и функции, недоступные для внешнего вызова
 
    // список элементов
    Item1 { 
        id: item1 // название элемента (если есть)
 
        property ... // объявление дополнительных свойств элемента
        ...
        
        signal ... // объявление дополнительных сигналов элемента
        ...
 
        on...Changed: // обработчики сигналов дополнительных свойств
        ...
 
        height: parent.height // инициализация базовых свойств элемента
        width: parent.width
        ... 
 
        Item11 { } // список подэлементов
 
        Item12 { }
 
        ...
 
        on...Changed: // обработка базовых свойств элемента
        ...
    } 
 
    Item2 { }
 
    ...
 
    on...Changed: // обработчики сигналов базовых(унаследованных) свойств(в данном случае - свойств Item) Можно их конечно  
    ...                  // размещать в других местах, но лично мне гораздо удобнее(и нагляднее для меня) размещать их в конце, 
                         // отделяя их от  обработчиков написанных мною свойств
}

Переменные и соединения

Типы переменных

Простые переменные: bool, int, double, url, string, list

JS переменные: var

Объектные переменные - унаследованные от QtObject.

Изменение переменных осуществляется либо путём qml-привязок (через двоеточие -

x: 5

), либо путём js-присваивания

x=5

.

--------------Дополнить--------------

Работа с ней принципиально отличается от остальных переменных.

По сути дела обычные переменные копируют переменные стандартного C++. Они также сигнализируют об изменении их значения

Один из основных инструментов QML - это соединения. Используя их рано или поздно возникает потребность в двунаправленном соединении для актуальности свойств(property) при их редактировании где-нибудь. К сожалению в QML нет по умолчанию двунаправленного соединения. Можно его организовать несколькими способами, но практически все они при некоторых условиях дают сбой. Единственное найденное мной рабочее решение заключается в использовании Binding и прямых js инициализаций. Это может не быть особенно быстрым из-за постоянного вызова js-контекста, но _оно_работает_.

Приведу простой пример двунаправленного соединения из двух элементов и свойства:

Пример двунаправленного соединения

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

// main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
 
ApplicationWindow {
    id: root
 
    property int prop
 
    onPropChanged: console.log("Main prop changed to " + prop)
 
    visible: true
    width: 640
    height: 480
 
    SpinBox {
        id: spinBox
 
        Binding on value { value: root.prop }
 
        onValueChanged: if (root.prop !== value) root.prop = value
    }
 
    StackView {
        id: stack
 
        anchors.top: spinBox.bottom
        anchors.bottom: parent.bottom
        width: parent.width
        initialItem: initItem
    }
 
    Item {
        visible: false
 
        Test2 {
            id: initItem
 
            Binding on prop { value: root.prop }
 
            onPropChanged: if (root.prop !== prop) root.prop = prop
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// Test2.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
 
Item {
    id: root
 
    property int prop
 
    onPropChanged: console.log("Test 2 prop changed to " + prop)
 
    SpinBox {
        id: spinbox
 
        Binding on value { value: root.prop }
 
        onValueChanged: if (root.prop !== value) root.prop = value
    }
}

Динамическое создание объектов

Динамическое создание объектов в QML обычно востребовано по двум разным причинам:

1) на платформах, где программе выделяется мало памяти

2) для большей плавности и отзывчивости интерфейса

Пример создания динамического самоудаляемого элемента на примере Dialog:

самоудаляемый элемент Dialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

import QtQuick.Dialogs 1.2
...
id: root
...
Button {
    id: saveDialogButton
 
    text: "Save Dialog"
 
    onClicked: {
        var svDialog = saveDialogComponent.createObject(root)
        svDialog.text = text
        svDialog.save.connect(function(){ fileio.saveCurrent(); }) // учтите, что функция будет вызываться в контексте где она объявлена,         
                                                                                        // а не в контексте svDialog.
// К сожалению, почему-то нельзя данным образом присоединить сигнал изменения свойств(<свойство>Changed).
// Поэтому saveDialog приходится создавать в качестве компонента(чтобы прописать там самоудаление)
 
        svDialog.open()
    }
}
 
Component {
    id: saveDialogComponent
 
    Dialog {
        id: saveDialog
 
        signal save
 
        standardButtons: StandardButton.Save | StandardButton.Cancel
 
        onClickedButtonChanged: if (StandardButton === StandardButton.Save) save()
        onVisibleChanged: if (visible === false) destroy() // автоматическое уничтожение компонента при его сокрытии
    }
}

Для планомерной же загрузки используется элемент Loader.

JSON

Внутренние JSON файлы:

Пока ещё не нашёл внятного и нормального механизма загрузки JSON в QML. =(

Поэтому пока что решаю данную проблему путём преобразования JSON в JS файлы.

JS

Есть собственно два типа js файлов в qml - обычные и библиотеки:

Обычные файлы JS при подключении в qml создаются и выполняются в пространстве имён подключённого qml файла.

Библиотеки содержат в себе объявление "

Кликните здесь для просмотра всего текста

.pragma library

". Они имеют своё собственное пространство имён и создаются только один раз.

Подключать другие файлы js из js файлов можно с помощью

.import "файл.js"

.

Взаимодействие между QML и C++

http://doc.qt.io/qt-5/qtqml-cp... topic.html

С++: QML разделяет классы объектов на виды:

1) Которые не предназначены для QML. Регистрируются помощью

qmlRegisterType

без параметров. Данный тип нельзя создавать в QML и к нему нельзя обращаться. Соответственно нельзя использовать в QML и все подклассы данного класса.

2) Интерфейсы. Регистрируются с помощью

qmlRegisterInterface

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

3) Не создаваемые QML. Регистрируются с помощью

qmlRegisterUncreatableType

. Данный тип нельзя создавать в QML, но к нему можно обращаться. Это полезно, когда класс не должен создаваться в QML, но в тоже время его перечисляемые типы(enum) или свойства должны быть доступны из QML.

4) Синглтоны. Регистрируются с помощью

qmlRegisterSingletonType

. Данный тип в единственном экземпляре автоматически создаётся QML(и уничтожается по окончанию работы) и к нему спокойно можно обращаться по его имени. Может быть не только QObject, но и QJSValue.

5) Обычные объекты QML. Регистрируются с помощью

qmlRegisterType

с параметрами. Объекты данного типа можно спокойно создавать в QML и к ним можно обращаться.

6) Расширение объектов. Регистрируются с помощью

qmlRegisterExtendedType

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

Пример обычного объекта QML

C++ часть:

C++ (Qt)Выделить код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
    Q_PROPERTY(QDateTime creationDate READ creationDate WRITE setCreationDate NOTIFY creationDateChanged)
public:
    Q_INVOKABLE void do_some_operation();
    //...
public slots:
    void do_some_other_operation();
    //...
signals:
    void someSignal();
    // ...
};
 
#inclide <QtQml>
qmlRegisterType<Message>("com.mycompany.messaging", 1, 0, "Message");

QML часть:

1
2
3
4
5
6
7
8
9
10

import com.mycompany.messaging 1.0
 
Message {
    author: "Amelie"
    creationDate: new Date()
 
    onSomeSignal: do_some_operation()
    
    Component.onCompleted: do_some_other_operation()
}

Внести в контекст QML любой объект довольно просто:

Создание прикреплённых типов и свойств.

Также можно добавлять объекты в контекст QML. Но используйте данную возможность с умом.

Пример

C++ (Qt)Выделить код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class ApplicationData : public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE QDateTime getCurrentDateTime() const {
        return QDateTime::currentDateTime();
    }
};
 
int main(int argc, char argv[]) {
    QGuiApplication app(argc, argv);
 
    QQuickView view;
 
    ApplicationData data;
    view.rootContext()->setContextProperty("applicationData", &data);
 
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();
 
    return app.exec();
}

1
2
3
4

// MyItem.qml
import QtQuick 2.0
 
Text { text: applicationData.getCurrentDateTime() }

В таком случае возникает проблема, если надо каким-то образом в QML привязать слот в подсоединённом объекте с какими-либо свойствами других объектов. В этом случае нужно применять QML класс

Binding

.

Пример

1
2
3
4
5
6
7
8
9

Text {
    text: applicationData.getCurrentDateTime() // applicationData - подсоединённые объект
 
    Binding {
        target: applicationData
        property: "someProperty"
        value: "someValue" // значение, которое вешается на свойство someValue объекта applicationData
    }
}

Дополнительные материалы по QML:

1)

Qt Cadaques

2)

Developing with Qt_Quick

3) ICS Best Practices in Qt Quick/QML (

1

,

2

,

3

,

4

)