Qt 5.3: предоставление доступа к С++ объектам из QML

Связка Qt 5.3 + QML предоставляет отличную возможность разделить в приложении логику модели данных и логику представления. Но чтобы обеспечить свзять между моделью данных и представлением, необходимо предоставить представлению доступ к данным. Я это делаю через отдельную прослойку - Presenter (из Model-Vew-Presenter). Для этого я завожу отдельный класс, объекты которого, с одной стороны, имеют ссылку на объекты модели, а с другой, к ним может быть осуществлен доступ из QML - представления. Итак, необходимо предоставить из QML доступ к объектам в С++ коде.

У меня есть класс BTree (двоичное дерево), который хранит данные в дереве, написанный на С++. Я хочу его отобразить средствами QML, при этом не хочу его изменять - для этого я пишу класс BTreePresenter, который будет взаимодействовать с QML и иметь ссылку на реализцию двоичного дерева (объект класса BTree).

Класс BTreePresenter

class BTreePresenter : public QObject
{
    Q_OBJECT
public:
    explicit BTreePresenter(const std::string & filename, QObject *parent = 0);

    Q_INVOKABLE int getHeight() const;
    Q_INVOKABLE QList<QObject*> getNextRow() const;
    Q_INVOKABLE BTreeNodePresenter * getRoot() const;
private:
    std::unique_ptr<BTree> pModel;
};

Класс должен наследовать QObject, чтобы его можно было использовать в QML. Те его методы, которые буду вызываться из QML, должны предваряться макросом Q_INVOKABLE (а члены-данные, к которым необходимо обеспечить доступ из QML - макросом Q_PROPERTY).
В моем примере есть один метод, возвращающий int, один - QList с указателями на QObject и один - указатель на пользовательский класс BTreeNodePresenter. Возвращаемые значения первых двух методов автоматически конвертируются в соответствующие типы Javascript и могут быть использованы в QML. Если возвращаемый объект является указателем на пользовательский класс, то этот класс должен наследовать QObject и быть зарегистрированным для QML (смотри ниже, в файле main.cpp). При этом невозможно передать объект, наследующй QObject по значению, так как в классе QObject запрещено копирование, а объект, не наследующий QObject, передавать нет смысла, так как его будет не зарегистрировать и QML его не распознает.
Теперь объект класса BTreePresenter надо зарегистрировать в контексте QML:

Файл main.cpp

BTreePresenter btree("test_input.txt");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("binaryTree", &btree);
qmlRegisterType<BTreeNodePresenter>("dgrafov.binarytree", 1, 0, "BinaryTreeNode"); //регистрация пользовательского класса 

Теперь к созданному объекту можно обращаться из QML по имени binaryTree:

QML

import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.2

import dgrafov.binarytree 1.0

ApplicationWindow {
    visible: true
    id: rootWindow
    width: 640
    height: 480
    title: qsTr("Demo")

    MouseArea {
        anchors.fill: parent
        onClicked: {
            console.log(binaryTree.getHeight());
            console.log(binaryTree.getNextRow().length);
            console.log(binaryTree.getRoot().getData());
        }
}

После того, как заранее зарегистрированный класс BTreeNodePresenter импортирован (посредством директивы import dgrafov.binarytree 1.0), можно вызывать методы объектов этого класса, в частности, getData() того объекта, который будет возвращен методом binaryTree.getRoot().
Следует отметить, что после передачи указателя на объект в QML через возвращаемое значение метода, объект считается находящимся во владении QML и может быть уничтожен сборщиком мусора. В моем примере объекты, указатели на которые были переданы, будут уничтожены собрщиком мусора QML при закрытии окна.
Подробнее о передаче владения между С++ и QML и о том, как это поведение можно изменить, можно прочитать здесь.

2 комментария :

  1. А можно увидеть реализацию getRoot()?

    ОтветитьУдалить
    Ответы
    1. Боюсь, что нет - того кода уже давно не существует нигде. Но реализация getRoot как раз простая обычно для двоичного дерева, потому что указатель на корень обычно хранится в качестве отдельного члена класса (скорее всего и я так делал), поэтому getRoot() выглядит обычно вот так:
      {
      return m_root;
      }
      Я не стал приводить реализацию этого класса, потому что она не важна в контексте регистрации класса в QML. Это мог бы быть любой другой класс, просто пример с двоичным деревом под рукой оказался.

      Удалить