Архитектура С++/QML - способ воспроизвести структуру С++ в QML?

В настоящее время я пытаюсь разработать довольно важное приложение (подобное ОС) с Qt 5.2 и Qt Quick 2; то, что я хотел бы сделать, это реализовать всю логику на C++, а пользовательский интерфейс объявляется благодаря QML. На данный момент это кажется логичным и способ обойти. Тем не менее, я не могу понять, как сделать это чистым способом. Я прочитал много документации, руководств и примеров, но ничего такого большого…

Давайте немного объясним, что я хотел бы назвать архитектурой; допустим, у нас есть объект Scene, который может содержать неопределенное количество объектов Application. Что я хотел бы, так это определить логику в CPP (как я загружаю приложения из XML, что сцена должна иметь в качестве свойств, …), а затем показать сцену с помощью QML. Кроме того, мы должны заметить, что элементы Scene и Application должны повторно использоваться как компоненты; Итак, вот основная идея: я хотел бы определить графические стили, общие для каждого объекта, с помощью файла в QML (расширение типа CPP).

Например, я мог бы создать файл с таким содержимым:

Application {
    Rectangle { ... }
}

Говоря, что приложение должно быть представлено как Rectangle ; затем, когда я создаю объект сцены, который имеет список приложений (или одно уникальное приложение, для начала), я хотел бы, чтобы он отображался автоматически (потому что это свойство объекта сцены). Это вообще возможно? Как я могу это сделать ?

Я думал, что если я расширим объект C++ и объявлю для него некоторые графические элементы, это будет автоматически. Но на самом деле это не так!

Может, есть другой обходной путь?

Спасибо


person Jérémy Dutheil    schedule 26.03.2014    source источник
comment
В чем проблема? Что значит не похоже? Нам нужна конкретика.   -  person Mitch    schedule 26.03.2014


Ответы (2)


Мне не очень нравится этот вопрос, так как он ничего особенного не спрашивает. Документация по Qt очень обширна, поэтому мне часто кажется странным, когда люди говорят, что прочитали документацию, руководства и примеры, но так и не нашли то, что искали. Тем не менее, я думаю, что понимаю суть того, о чем вы спрашиваете, и думаю, что ответ может быть полезен для некоторых, поэтому я попытаюсь ответить на него.

main.cpp

#include <QtGui/QGuiApplication>
#include <QtQml>
#include <QQuickItem>
#include "qtquick2applicationviewer.h"

class ApplicationItem : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QString title MEMBER mTitle NOTIFY titleChanged)
public:
    ApplicationItem(QQuickItem *parent = 0) : QQuickItem(parent) {
    }

public slots:
    void close() {
        emit closed(this);
    }
signals:
    void titleChanged(QString title);
    void closed(ApplicationItem *app);
private:
    QString mTitle;
};

class SceneItem : public QQuickItem
{
    Q_OBJECT
public:
    SceneItem() {
    }

public slots:
    void startApp(const QString &qmlFile) {
        QQmlComponent *component = new QQmlComponent(qmlEngine(this), QUrl(qmlFile));
        if (component->isLoading()) {
            QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
                this, SLOT(componentStatusChanged()));
        } else {
            // The component was synchronously loaded, but it may have errors.
            if (component->isError()) {
                qWarning() << "Failed to start application:" << component->errorString();
            } else {
                addApp(component);
            }
        }
    }

    void componentStatusChanged(QQmlComponent::Status status) {
        QQmlComponent *component = qobject_cast<QQmlComponent*>(sender());
        if (status == QQmlComponent::Ready) {
            addApp(component);
        } else if (status == QQmlComponent::Error) {
            qWarning() << "Failed to start application:" << component->errorString();
        }
    }

    void appClosed(ApplicationItem *app) {
        int appIndex = mApplications.indexOf(app);
        if (appIndex != -1) {
            mApplications.removeAt(appIndex);
            app->deleteLater();
        }
    }
private:
    void addApp(QQmlComponent *component) {
        ApplicationItem *appItem = qobject_cast<ApplicationItem*>(component->create());
        appItem->setParentItem(this);

        connect(appItem, SIGNAL(closed(ApplicationItem*)), this, SLOT(appClosed(ApplicationItem*)));

        mApplications.append(appItem);
        delete component;
    }

    QList<ApplicationItem*> mApplications;
};

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

    QtQuick2ApplicationViewer viewer;
    qmlRegisterType<ApplicationItem>("Test", 1, 0, "ApplicationItem");
    qmlRegisterType<SceneItem>("Test", 1, 0, "SceneItem");
    viewer.setMainQmlFile(QStringLiteral("qml/quick/main.qml"));
    viewer.showExpanded();

    return app.exec();
}

#include "main.moc"

Я представил оба класса как подклассы QQuickItem. SceneItem состоит из множества экземпляров ApplicationItem, которые добавляются к сцене путем вызова startApp(). Этот слот принимает в качестве аргумента путь к файлу QML. Этот файл может быть загружен по сети или из локального файла, поэтому мы учитываем возможность как синхронной, так и асинхронной загрузки.

Файл QML должен описывать внешний вид приложения, и сцена ожидает, что его корневой тип будет ApplicationItem. Например, вот MySweetApp.qml:

import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0

ApplicationItem {
    id: someAppStyle
    title: "My Sweet App"
    width: 100
    height: 100

    MouseArea {
        anchors.fill: parent
        drag.target: parent
    }

    Rectangle {
        radius: 4
        color: "lightblue"
        anchors.fill: parent

        Text {
            anchors.left: parent.left
            anchors.right: closeButton.right
            anchors.leftMargin: 4
            anchors.top: parent.top
            anchors.topMargin: 4
            text: someAppStyle.title
        }

        Button {
            id: closeButton
            anchors.right: parent.right
            anchors.rightMargin: 4
            anchors.top: parent.top
            anchors.topMargin: 2
            onClicked: close()
            text: "x"
            width: 20
            height: width
        }
    }
}

Приложения могут закрыться, вызвав слот close(), объявленный в ApplicationItem.

Вот main.qml:

import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0

SceneItem {
    id: scene
    width: 360
    height: 360

    Button {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottom: parent.bottom
        text: "Launch app"

        onClicked: scene.startApp("qml/quick/MySweetApp.qml")
    }
}

Здесь объявляется SceneItem, а также простой интерфейс для запуска нескольких экземпляров My Sweet App (это очень полезное приложение).

Я считаю, что это наиболее подходящий способ сделать то, что вы просите. Это позволяет избежать проблем с настройкой списков ApplicationItems в C++, которые доступны для QML (на самом деле это не так уж сложно, но это одна из областей, где документация может быть более очевидной), и дает пользователям вашей ОС свободу в том, как выглядят приложения. . Если вы хотите быть более строгим в том, что можно стилизовать, я бы посоветовал посмотреть, как Qt Quick Controls выполняет стилизацию.

person Mitch    schedule 26.03.2014

Я бы посоветовал не использовать C++ для логики, если вам это действительно не нужно - используйте casese для использования C++ для логики, если у вас есть требования к высокой производительности, такие как данные в реальном времени, которые необходимо обрабатывать, например, 10 раз в секунду.

Поскольку в большинстве вариантов использования это требование отсутствует, лучше использовать QML также и для логики приложения, поскольку это сэкономит до 90% исходного кода (и времени) по сравнению с C++. Особенно в начале разработки вы намного быстрее кодируете логику в QML и быстрее получаете результаты. Позже вы можете перенести логику на C++, если это необходимо.

Есть 2 хороших руководства по этой теме, которые объясняют это более подробно и содержат примеры исходного кода:

  1. Советы по архитектуре QML и почему/как избегать C++ в вашем приложении Qt
  2. Рекомендации и примеры архитектуры QML
person Christian Feldbacher    schedule 27.08.2019