Связка Qt 5.3 + QML предоставляет отличную возможность разделить в приложении логику модели данных и логику представления. Но чтобы обеспечить свзять между моделью данных и представлением, необходимо предоставить представлению доступ к данным. Я это делаю через отдельную прослойку - Presenter (из Model-Vew-Presenter). Для этого я завожу отдельный класс, объекты которого, с одной стороны, имеют ссылку на объекты модели, а с другой, к ним может быть осуществлен доступ из QML - представления. Итак, необходимо предоставить из QML доступ к объектам в С++ коде.
У меня есть класс BTree (двоичное дерево), который хранит данные в дереве, написанный на С++. Я хочу его отобразить средствами QML, при этом не хочу его изменять - для этого я пишу класс BTreePresenter, который будет взаимодействовать с QML и иметь ссылку на реализцию двоичного дерева (объект класса BTree).
Класс должен наследовать QObject, чтобы его можно было использовать в QML. Те его методы, которые буду вызываться из QML, должны предваряться макросом Q_INVOKABLE (а члены-данные, к которым необходимо обеспечить доступ из QML - макросом Q_PROPERTY).
В моем примере есть один метод, возвращающий int, один - QList с указателями на QObject и один - указатель на пользовательский класс BTreeNodePresenter. Возвращаемые значения первых двух методов автоматически конвертируются в соответствующие типы Javascript и могут быть использованы в QML. Если возвращаемый объект является указателем на пользовательский класс, то этот класс должен наследовать QObject и быть зарегистрированным для QML (смотри ниже, в файле main.cpp). При этом невозможно передать объект, наследующй QObject по значению, так как в классе QObject запрещено копирование, а объект, не наследующий QObject, передавать нет смысла, так как его будет не зарегистрировать и QML его не распознает.
Теперь объект класса BTreePresenter надо зарегистрировать в контексте QML:
Теперь к созданному объекту можно обращаться из QML по имени binaryTree:
После того, как заранее зарегистрированный класс BTreeNodePresenter импортирован (посредством директивы import dgrafov.binarytree 1.0), можно вызывать методы объектов этого класса, в частности, getData() того объекта, который будет возвращен методом binaryTree.getRoot().
Следует отметить, что после передачи указателя на объект в QML через возвращаемое значение метода, объект считается находящимся во владении QML и может быть уничтожен сборщиком мусора. В моем примере объекты, указатели на которые были переданы, будут уничтожены собрщиком мусора QML при закрытии окна.
Подробнее о передаче владения между С++ и QML и о том, как это поведение можно изменить, можно прочитать здесь.
Класс 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 и о том, как это поведение можно изменить, можно прочитать здесь.
А можно увидеть реализацию getRoot()?
ОтветитьУдалитьБоюсь, что нет - того кода уже давно не существует нигде. Но реализация getRoot как раз простая обычно для двоичного дерева, потому что указатель на корень обычно хранится в качестве отдельного члена класса (скорее всего и я так делал), поэтому getRoot() выглядит обычно вот так:
Удалить{
return m_root;
}
Я не стал приводить реализацию этого класса, потому что она не важна в контексте регистрации класса в QML. Это мог бы быть любой другой класс, просто пример с двоичным деревом под рукой оказался.