从C++自定义Qml值类型

最近在做一个项目,前端UI框架用的是QtQuic(QML),后端逻辑用的C++(Qt)。在封装后端矩阵运算库的时候,看到Qt官方提供的matrix4x4类型可以像下面这样直接在QML代码里动态创建,感觉非常好用:

var m = Qt.matrix4x4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])

但是官方提供的功能函数太少了,无法满足项目使用需求。所以就想把自己基于Eigen封装的矩阵运算库模仿Qt.matrix4x4的这种用法定义成自己的类型,然后在前端代码里进行简单矩阵运算时就可以像下面这样:

var p1 = MyNamespace.pose([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
var p2 = OtherFuntion()
return p1.multiply(p2)

翻了一下Qt官方的自定义值类型教程,发现很简单,只需要在自己写的C++类型里添加两个宏即可,就像下面这样:

class Pose
{
    Q_GADGET
    QML_VALUE_TYPE(pose);

publi:
    Pose() {}
    ~Pose() {}

    Q_INVOKABLE void multiply(){}
};

需要注意的是QML_VALUE_TYPE宏里申明的值类型的名字必须小写字母开头。

然而这样写完之后,仅仅只是能够让QML代码在向C++代码里在传递矩阵时可以直接使用Pose这种类型,不用先转成QVariantList再重新解析成Eigen的矩阵。

扒了扒Qt的源码,发现在创建matrix4x4这个值类型的的代码里有这么个函数(Qt\6.6.2\Src\qtdeclarative\src\quick\util\qquickvaluetypes_p.h):

static QVariant create(const QJSValue &params);

实现在qquickvaluetypes_p.cpp文件里,是这样写的:

QVariant QQuickMatrix4x4ValueType::create(const QJSValue &params)
{
    if (params.isNull() || params.isUndefined())
        return QMatrix4x4();

    if (params.isString())
        return createValueTypeFromNumberString<QMatrix4x4, 16>(params.toString());

    if (params.isArray() && params.property(QStringLiteral("length")).toInt() == 16) {
        return QMatrix4x4(params.property(0).toNumber(),
                          params.property(1).toNumber(),
                          params.property(2).toNumber(),
                          params.property(3).toNumber(),
                          params.property(4).toNumber(),
                          params.property(5).toNumber(),
                          params.property(6).toNumber(),
                          params.property(7).toNumber(),
                          params.property(8).toNumber(),
                          params.property(9).toNumber(),
                          params.property(10).toNumber(),
                          params.property(11).toNumber(),
                          params.property(12).toNumber(),
                          params.property(13).toNumber(),
                          params.property(14).toNumber(),
                          params.property(15).toNumber());
    }

    return QVariant();
}

仿照Qt官方的代码给我们的Pose类增加一个构造函数(用Q_INVOKABLE 开给QML):

    Q_INVOKABLE Pose(const QJSValue &params)
    {
        if (params.isArray() && params.property(QStringLiteral("length")).toInt() == 16) {
            m_storage << params.property(0).toNumber(),
                params.property(1).toNumber(),
                params.property(2).toNumber(),
                params.property(3).toNumber(),
                params.property(4).toNumber(),
                params.property(5).toNumber(),
                params.property(6).toNumber(),
                params.property(7).toNumber(),
                params.property(8).toNumber(),
                params.property(9).toNumber(),
                params.property(10).toNumber(),
                params.property(11).toNumber(),
                params.property(12).toNumber(),
                params.property(13).toNumber(),
                params.property(14).toNumber(),
                params.property(15).toNumber();
        }
    }

然后在QML代码里就可以像用下面这样申明给QML类型的property:

Item{
    property pose p1: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
    property pose p2

    function func(){
        return p1.multiply(p2)
    }
}

但还是无法直接在某个函数里像申明一个整数变量一样来使用。

继续扒Qt的源码,然后发现 "Qt.matrix4x4" 的 "Qt" 居然是从C++注册到QML的类型名,对应的C++类是QtObject(Qt\6.6.2\Src\qtdeclarative\src\qml\qml\qqmlbuiltinfunctions_p.h),我们关心的主体部分是这么写的:

class Q_QML_EXPORT QtObject : public QObject
{
    ...
    QML_NAMED_ELEMENT(Qt)
    QML_SINGLETON  
    ...
public:
    ...
    Q_INVOKABLE QVariant matrix4x4() const;
    Q_INVOKABLE QVariant matrix4x4(double m11, double m12, double m13, double m14,
                                   double m21, double m22, double m23, double m24,
                                   double m31, double m32, double m33, double m34,
                                   double m41, double m42, double m43, double m44) const;
    Q_INVOKABLE QVariant matrix4x4(const QJSValue &value) const;
    ...
};

这下就简单了,直接开抄!写一个我们自定义的类,注册成想要的QML类型名,然后在里面仿照Qt的格式封装一下构造pose类型的函数即可。

class MyNamespaceObject : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_NAMED_ELEMENT(MyNamespace)
    QML_UNCREATABLE("MyNamespaceObject is uncreatable")

  public:
    MyNamespaceObject (QObject *parent = nullptr) : QObject(parent) {}
    ~MyNamespaceObject () = default;

    Q_INVOKABLE QVariant pose(const QJSValue &value) const { return QVariant::fromValue<Pose>(Pose(value)); }

};

至此就可以实现开头设想的自定义qml值类型的用法了,类似的我们还可以定义其他各种各样的值类型,瞬间舒服了有木有!

完整的的测试代码如下:

Pose.h

#ifndef _POSE_H_
#define _POSE_H_

#include "Eigen/Dense"
#include <QString>
#include <QtQml/qqml.h>
#include <iomanip>

namespace Test {
class Pose
{
    Q_GADGET
    QML_VALUE_TYPE(pose);

  public:
    Pose() : Pose(Eigen::Matrix4d::Zero()) {}
    Pose(const Eigen::Matrix4d &storage) : m_storage(storage) {}

    Q_INVOKABLE Pose(const QJSValue &params)
    {
        if (params.isArray() && params.property(QStringLiteral("length")).toInt() == 16) {
            m_storage << params.property(0).toNumber(),
                params.property(1).toNumber(),
                params.property(2).toNumber(),
                params.property(3).toNumber(),
                params.property(4).toNumber(),
                params.property(5).toNumber(),
                params.property(6).toNumber(),
                params.property(7).toNumber(),
                params.property(8).toNumber(),
                params.property(9).toNumber(),
                params.property(10).toNumber(),
                params.property(11).toNumber(),
                params.property(12).toNumber(),
                params.property(13).toNumber(),
                params.property(14).toNumber(),
                params.property(15).toNumber();
        }
    }

    ~Pose() = default;
    Pose(const Pose &other) { this->m_storage = other.m_storage; }

    Pose &operator=(const Pose &other)
    {
        this->m_storage = other.m_storage;
        return *this;
    }

    Q_INVOKABLE QString toQString() const
    {
        std::ostringstream strBuffer;
        for (int i = 0; i < 16; i++) {
            double element = m_storage(i / 4, i % 4);
            if (std::abs(element) < 0.0000000001) {
                element = 0;
            }
            strBuffer << std::left << std::setw(10) << element;
            if (i < 15)
                strBuffer << " ";
            if (3 == i % 4)
                strBuffer << "\n";
        }
        return QString(std::string(strBuffer.str()).c_str());
    }

    Q_INVOKABLE Pose multiply(Pose other) const { return Pose(m_storage * other.m_storage); }
    Q_INVOKABLE Pose inverse()const{return Pose(m_storage.inverse());}
    Q_INVOKABLE Pose copy() const { return Pose(m_storage); }
    static Pose Identity() { return Pose(Eigen::Matrix4d::Identity()); }

  private:
    Eigen::Matrix4d m_storage;
};
} // namespace Test

#endif // _POSE_H_

TestQmlValueTypeObject.h

#ifndef _TEST_QML_VALUE_TYPE_OBJECT_H_
#define _TEST_QML_VALUE_TYPE_OBJECT_H_

#include <Pose.h>
#include <QObject>
#include <QtQml/qqml.h>

class TestQmlValueTypeObject : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_NAMED_ELEMENT(Test)
    QML_UNCREATABLE("TestQmlValueTypeObject is uncreatable")

  public:
    TestQmlValueTypeObject(QObject *parent = nullptr) : QObject(parent) {}
    ~TestQmlValueTypeObject() = default;

    Q_INVOKABLE QVariant pose(const QJSValue &value) const { return QVariant::fromValue<Test::Pose>(Test::Pose(value)); }

    Q_INVOKABLE QVariant pose() const { return QVariant::fromValue<Test::Pose>(Test::Pose()); }

    Q_INVOKABLE QVariant identityPose() const { return QVariant::fromValue<Test::Pose>(Test::Pose::Identity()); }
};

#endif // _TEST_QML_VALUE_TYPE_OBJECT_H_

Main.qml

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import TestQmlValueType

Window {
    id: root
    visible: true
    width: 500
    height: 300

    property pose p: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]

    Row {
        spacing: 2
        Button {
            text: "assign"
            onClicked: {
                p = [0.128123, -0.352015, 0.927184, 0,
                     0.96495, -0.171652, -0.198511, 0,
                     0.229032, 0.92012, 0.317684, 0,
                     0, 0, 0, 1]
            }
        }
        Button {
            text: "multiply"
            onClicked: {
                var p1 = Test.pose([0.128123, -0.352015, 0.927184, 0,
                                    0.96495, -0.171652, -0.198511, 0,
                                    0.229032, 0.92012, 0.317684, 0,
                                    0, 0, 0, 1])
                var p2 = p;
                p = p1.inverse().multiply(p2)
                var m = Qt.matrix4x4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
                console.log(m)
            }
        }
    }

    Text {
        anchors.centerIn: parent
        id: name
        text: qsTr(p.toQString())
    }
}

main.cpp

#include <QGuiApplication>
#include <QtQml/QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    QObject::connect(
        &engine, &QQmlApplicationEngine::objectCreationFailed, &app,
        []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
    engine.loadFromModule("TestQmlValueType", "Main");

    return app.exec();
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.25)

project(TestQmlValueType VERSION 1.0.0 LANGUAGES CXX)

find_package(Eigen3 REQUIRED NO_MODULE)
find_package(Qt6 COMPONENTS Core Quick Qml REQUIRED)

if(Qt6_VERSION VERSION_GREATER_EQUAL 6.5.0)
    qt_standard_project_setup(REQUIRES 6.5)
endif()

file(GLOB SRC_H_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h)
file(GLOB SRC_CPP_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
file(GLOB SRC_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)

qt_add_executable(${PROJECT_NAME})
qt_add_qml_module(${PROJECT_NAME}
    URI ${PROJECT_NAME}
    SOURCES ${SRC_CPP_FILES} ${SRC_H_FILES}
    QML_FILES ${SRC_QML_FILES}
)

target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Quick Qt6::Qml)
target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen)

  • 22
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值