call qmlRegisterType() in the registered class its

2019-09-10 11:12发布

In my program I have quite a lot of QObject subclasses which are instantiated in QML. Each time I add/remove a new class, I need to add/remove the corresponding call of qmlRegisterType() in main.cpp. I wonder if I can put the call in the code of the registered class itself. This makes it possible to remove a class by removing its cpp/header file and without altering any other C++ code. Also, I can have my main.cpp clean and don't need to include all the header files of the registered classes.

One way to do that seems to be this:

MyClass.h:

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass(QObject *parent = 0);

private:
    static int unused_val;
};

MyClass.cpp:

#include "MyClass.h"
#include <QtQml>

int MyClass::unused_val = qmlRegisterType<MyClass>("my_company", 1, 0, "MyClass");

// some other code

Is there a nicer way? For example, one that doesn't require "unused_val" variable?

3条回答
聊天终结者
2楼-- · 2019-09-10 11:29

Or you could use static objects to do the job for you.

namespace Register {
    template <class T>
    struct Type {
        Type() {
            qmlRegisterType<T>();
        }
    };

With different types of registration, depending on what you want:

    template <class T>
    struct CreatableType {
        CreatableType() = delete;
        CreatableType(const QString& name) {
            qmlRegisterType<T>("my_uri", 1, 0, name.toStdString().c_str());
        }
    };

    template <class T>
    struct UncreatableType {
        UncreatableType() = delete;
        UncreatableType(const QString& name) {
            qmlRegisterUncreatableType<T>("my_uri", 1, 0, name.toStdString().c_str(), name + " is not available.");
        }
    };
}

In my opinion more flexible and cleaner than using macros. Usage is simple: first, declare a static member in your class:

class A : public QObject {
    Q_OBJECT
    // this one to simply expose the class to QML
    static Register::Type<A> Register;

    // or this one to expose and also allow creation from QML
    static Register::CreatableType<A> Register;

    // or this one to expose the class but disallow creation from QML
    static Register::UncreatableType<A> Register;
};

Finally, define the member in the .cpp file, just as you did with the macro:

// either one of these

Register::Type<A> A::Register;

//or
Register::CreatableType<A> A::Register("MyClass");

// or
Register::UncreatableType<A> A::Register("MyClass");
查看更多
迷人小祖宗
3楼-- · 2019-09-10 11:46

The following solution expands on the method that qCring has above suggested. The solution that was provided suffered from the "static initialization order problem", which was for me reproducible whilst running the code in VS2017. Below is the header file that was used:

#pragma once

#include <QCoreApplication>
#include <QQmlEngine>

#define QML_REGISTER_TYPE(T) \
    static QmlRegister::Type<T> s_qmlRegister;

#define QML_REGISTER_CREATABLE_TYPE(T, N) \
    template<> \
    const char * const QmlRegister::CreatableType<T>::NAME = #N; \
    static QmlRegister::CreatableType<T> s_qmlRegister;

#define QML_REGISTER_UNCREATABLE_TYPE(T, N) \
    template<> \
    const char * const QmlRegister::UncreatableType<T>::NAME = #N; \
    static QmlRegister::UncreatableType<T> s_qmlRegister;

#define QML_REGISTER_CPP_SINGLETON_TYPE(T, N) \
    template<> \
    const char * const QmlRegister::CppSingletonType<T>::NAME = #N; \
    static QmlRegister::CppSingletonType<T> s_qmlRegister;

#define QML_REGISTER_JAVASCRIPT_SINGLETON_TYPE(T, N) \
    template<> \
    const char * const QmlRegister::JavaScriptSingletonType<T>::NAME = #N; \
    static QmlRegister::JavaScriptSingletonType<T> s_qmlRegister;

namespace QmlRegister
{
    static constexpr const char * const URI = "my_uri";
    static constexpr int MAJOR = 1;
    static constexpr int MINOR = 0;

    template <class T>
    struct Type
    {
        Type()
        {
            qAddPreRoutine(call);
        }

        static void call()
        {
            qmlRegisterType<T>();
        }
    };

    template <class T>
    struct CreatableType
    {
        static const char * const NAME;

        CreatableType()
        {
            qAddPreRoutine(call);
        }

        static void call()
        {
            qmlRegisterType<T>(URI, MAJOR, MINOR, NAME);
        }
    };

    template <class T>
    struct UncreatableType
    {
        static const char * const NAME;

        UncreatableType()
        {
            qAddPreRoutine(call);
        }

        static void call()
        {
            qmlRegisterUncreatableType<T>(URI, MAJOR, MINOR, NAME, QStringLiteral("%1 is not available.").arg(NAME));
        }
    };

    template <class T>
    struct CppSingletonType
    {
        static const char * const NAME;

        CppSingletonType()
        {
            qAddPreRoutine(call);
        }

        static void call()
        {
            static auto callback = [](QQmlEngine* engine, QJSEngine* scriptEngine) -> QObject*
            {
                QObject* ptr = &T::instance();
                engine->setObjectOwnership(ptr, QQmlEngine::ObjectOwnership::CppOwnership);
                return ptr;
            };

            qmlRegisterSingletonType<T>(URI, MAJOR, MINOR, NAME, callback);
        }
    };

    template <class T>
    struct JavaScriptSingletonType
    {
        static const char * const NAME;

        JavaScriptSingletonType()
        {
            qAddPreRoutine(call);
        }

        static void call()
        {
            static auto callback = [](QQmlEngine* engine, QJSEngine* scriptEngine) -> QObject*
            {
                QObject* ptr = new T();
                engine->setObjectOwnership(ptr, QQmlEngine::ObjectOwnership::JavaScriptOwnership);
                return ptr;
            };

            qmlRegisterSingletonType<T>(URI, MAJOR, MINOR, NAME, callback);
        }
    };
}

The key difference is that the above solution calls upon qAddPreRoutine function, which queues each callback to be invoked during QCoreApplications's construction.

As an example, to register a class (ex: namespace Common { class Result; }) as a QML registered type, simple add the following line inside Result's source file;

QML_REGISTER_TYPE(Common::Result)

Likewise to register a class as a none-creatable you simply need to added the following inside their respective source files:

QML_REGISTER_UNCREATABLE_TYPE(Company::Outcome, Outcome)  // class Company::Outcome is none-creatable and referred in QML as "Outcome"

The same structure applies for createable QML types (QML_REGISTER_CREATABLE_TYPE), singleton QML types whose live is managed by C++ code (QML_REGISTER_CPP_SINGLETON_TYPE)), and singleton QML types whose live is managed by the QML engine (QML_REGISTER_JAVASCRIPT_SINGLETON_TYPE).

查看更多
Juvenile、少年°
4楼-- · 2019-09-10 11:46

So far the simplest and cleanest solution I found is to make a C++ macro like this:

#define QML_REGISTER(a) static int unused_val = qmlRegisterType<a>("my_uri", 1, 0, #a)

MyClass.cpp then needs just this simple line outside of any function:

QML_REGISTER(MyClass);

EDIT: Sometimes this code makes the application to crash in debug mode. See this thread for solution.

查看更多
登录 后发表回答