QT McQuickBoot — 二

  • Post author:
  • Post category:其他


承接上文,前一篇文章说了QuickBoot框架中WidgetIoc的作用及用法,至此我们把界面搭建好了,现在我们需要实现业务功能了。



优化界面

前面我们搭建的界面只是为了演示如何组装各个模块,但模块内啥功能都没有,现在我们给第二个界面添加一个按钮。

ChildWidget2.cpp

#include "ChildWidget2.h"

#include <QDebug>
#include <QPushButton>

#include <McWidgetIoc/McWidgetGlobal.h>

MC_STATIC()
qRegisterMetaType<ChildWidget2 *>();
MC_STATIC_END

ChildWidget2::ChildWidget2(QWidget *parent) : QWidget(parent)
{
    auto button = new QPushButton("ChildWidget2", this);
    connect(button, &QAbstractButton::clicked, []() { qDebug() << QStringLiteral(u"在这里面调用业务方法"); });
}


添加业务功能
  • 添加具体业务类

    Service2.h
#pragma once

#include <McBoot/McBootGlobal.h>

class Service2 : public QObject
{
    Q_OBJECT
    //! 声明为一个业务类,括号里表示该类实例化后的对象名
    MC_SERVICE("service2")
public:
    void method1();
};

//! 元对象声明
MC_DECL_METATYPE(Service2)

Service2.cpp

#include "Service2.h"

#include <QDebug>
#include <QThread>

MC_STATIC()
MC_REGISTER_BEAN_FACTORY(Service2)
MC_STATIC_END

void Service2::method1()
{
    qDebug() << QStringLiteral(u"业务功能1,将耗时1s。执行线程为:") << QThread::currentThread();
    QThread::sleep(1);
    qDebug() << QStringLiteral(u"业务功能1完成");
}
  • 添加对外接口,称之为Controller

    Controller2.h
#pragma once

#include <McBoot/McBootGlobal.h>

MC_FORWARD_DECL_CLASS(Service2)

class Controller2 : public QObject
{
    Q_OBJECT
    //! 声明为一个Controller,括号里为实例化后的类名
    MC_CONTROLLER("controller2")
    //! 向IOC容器请求为属性m_service2注入一个名为service2的对象,这里的名字就是Service2类中MC_SERVICE("service2")包裹的名字。
    //! 注意,m_service2为cpp类Controller2中的属性,而Q_PROPERTY宏声明的service2为元对象中的属性名,MC_AUTOWIRED表示向IOC容器请求注入。
    //! 这里MC_AUTOWIRED只有一个参数,是因为本类元对象中的属性名和IOC容器中的对象名同名,如果不同名的话,则需要用这种形式MC_AUTOWIRED("元对象中属性名", "IOC容器中对象名")。
    //! 元对象中属性名即为Q_PROPERTY声明的名字,IOC容器中对象名为MC_SERVICE包裹的名字。
    //! 同时,如果同名的话还可以 使用MC_PROPERTY宏简化。即下面的两个代码可以用一行代替: MC_PROPERTY(Service2Ptr, service2, MEMBER m_service2)
    MC_AUTOWIRED("service2")
    Q_PROPERTY(Service2Ptr service2 MEMBER m_service2)
public:
    Q_INVOKABLE void method1();

private:
    Service2Ptr m_service2;
};

MC_DECL_METATYPE(Controller2)

Controller2.cpp

#include "Controller2.h"

#include "Service/Service2.h"

MC_STATIC()
MC_REGISTER_BEAN_FACTORY(Controller2)
MC_STATIC_END

void Controller2::method1()
{
    m_service2->method1();
}

修改ChildWidget2.cpp中的代码如下

#include "ChildWidget2.h"

#include <QDebug>
#include <QPushButton>
#include <QThread>

#include <McBoot/McBootGlobal.h>
#include <McWidgetIoc/McWidgetGlobal.h>

MC_STATIC()
qRegisterMetaType<ChildWidget2 *>();
MC_STATIC_END

ChildWidget2::ChildWidget2(QWidget *parent) : QWidget(parent)
{
    auto button = new QPushButton("ChildWidget2", this);
    connect(button, &QAbstractButton::clicked, []() {
        qDebug() << QStringLiteral(u"在这里面调用业务方法,界面调用线程:") << QThread::currentThread();
        //! invoke方法的第一个参数有两个字符串,由.分割,第一个字符串为MC_CONTROLLER宏包裹的字符串,标识调用某一个具体的Controller对象。
        //! 第二个字符串为该对象中的函数名,该函数必须被Q_INVOKABLE宏标记。
        $.invoke("controller2.method1").then([]() {
            qDebug() << QStringLiteral(u"method1调用完成,回归线程:") << QThread::currentThread();
        });
    });
}

运行结果

"在这里面调用业务方法,界面调用线程:" QThread(0x1f92bf205d0)
"业务功能1,将耗时1s。执行线程为:" QThreadPoolThread(0x1f92dff5070, name = "Thread (pooled)")
"业务功能1完成"
"method1调用完成,回归线程:" QThread(0x1f92bf205d0)

以上可以看到业务方法的执行线程为子线程,和UI界面的主线程不同,以此来达到异步效果。同时,业务功能执行完之后返回到UI的线程仍然为主线程。



为业务方法传递参数并接受其返回值


QT内置类型

框架异步请求时的参数传递依赖QT元对象,而QT默认已经把部分类型提前注册了,那我们就能直接使用。

在Service2.h中添加第二个业务方法

int method2(const QString &arg);

Service2.cpp

int Service2::method2(const QString &arg)
{
    qDebug() << "method2:" << arg;
    return 2;
}

同样的,Controller2.h中也需要添加对外接口

Q_INVOKABLE int method2(const QString &arg);

Controller2.cpp

int Controller2::method2(const QString &arg)
{
    return m_service2->method2(arg);
}

在刚才的ChildWidget2.cpp的按钮接受函数中添加如下代码

//! invoke方法从第二个参数开始到最后一个参数,都会打包传递给最终的调用方法method2。then方法接受的lambda表达式可以指定一个参数,用来接受method2的返回值
$.invoke("controller2.method2", QStringLiteral(u"第二个业务功能的参数")).then([](int ret) {
    qDebug() << QStringLiteral(u"method2调用完成,其返回值:") << ret;
});

编译输出

method2: "第二个业务功能的参数"
"method2调用完成,其返回值:" 2


自定义类型

我们添加两个头文件ReturnVo.h和RequestVo.h

#pragma once

#include <McIoc/McGlobal.h>

struct ReturnVo
{
    QString ret;
};

MC_DECL_METATYPE(ReturnVo)
#pragma once

#include <McIoc/McGlobal.h>

struct RequestVo
{
    QString req;
};

MC_DECL_METATYPE(RequestVo)

添加一个global的cpp文件DomainGlobal.cpp,用于注册自定义类型。注: 在QT5.15中实际使用时发现,不需要这个cpp文件注册也能直接使用,我看了下相关源码,应该是调用$的invoke方法时,里面会用到

QVariant::fromValue

静态方法,而这个静态方法就会进行注册。但有两点需要注意: 1. 自动注册时只会注册元对象相关的部分,如果你仅仅只是像这个例子一样简单的使用,确实不需要手动注册,但后面会用到QML和一些其他功能,这就必须要手动注册了;2. 如果这两个自定义类型在一个动态库里面的话,你必须在这个动态库里面添加cpp文件并include这两个头文件,否则头文件不会参与编译,那么你在使用时可能会报

无法链接

的编译错误。所以这里建议都手动注册一下。

#include "Domain/RequestVo.h"
#include "Domain/ReturnVo.h"

MC_STATIC()
//! 注册自定义类型本身
MC_REGISTER_BEAN_FACTORY(RequestVo)
MC_REGISTER_BEAN_FACTORY(ReturnVo)

//! 因为需要用到QList类型,所以需要将其注册。所有的容器都是调用这个宏注册
MC_REGISTER_CONTAINER_CONVERTER(QList<RequestVoPtr>)
MC_STATIC_END

同样的添加业务方法和请求代码即可。Service2.h

ReturnVoPtr method3(const QList<RequestVoPtr> &vos);

Service2.cpp

ReturnVoPtr Service2::method3(const QList<RequestVoPtr> &vos)
{
    qDebug() << QStringLiteral(u"method3:") << vos;
    auto ret = ReturnVoPtr::create();
    ret->ret = QStringLiteral(u"method3返回");
    return ret;
}

Controller2.h

Q_INVOKABLE ReturnVoPtr method3(const QList<RequestVoPtr> &vos);

Controller2.cpp

ReturnVoPtr Controller2::method3(const QList<RequestVoPtr> &vos)
{
    return m_service2->method3(vos);
}

ChildWidget2.cpp

QList<RequestVoPtr> reqs;
RequestVoPtr req = RequestVoPtr::create();
req->req = QStringLiteral(u"method3请求参数");
reqs.append(req);
$.invoke("controller2.method3", reqs).then([](const ReturnVoPtr ret) {
    qDebug() << QStringLiteral(u"method3调用完成,返回值:") << ret->ret;
});

输出结果

"method3:" (QSharedPointer(0x21637a0eb00))
"method3调用完成,返回值:" "method3返回"



最后看看在QML中的用法

如上一篇文章中所说,

main

函数中的的启动方式需要改成

return McQuickBoot::run<QApplication>(argc, argv, QLatin1String("qrc:/main.qml"));

,同样的,我们在

main.qml

中使用$来请求。

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Component.onCompleted: {
        $.invoke("controller2.method1").then(function(){
            console.log("qml请求method1完成");
        });
    }
}

输出结果

"业务功能1,将耗时1s。执行线程为:" QThreadPoolThread(0x20f87e18b30, name = "Thread (pooled)")
"业务功能1完成"
qml: qml请求method1完成


传递参数

同样的,对于某些内置类型可以直接使用,但是这里和cpp中有些许不同。cpp中的invoke方法从第二个参数开始到最后一个参数结束,都会按顺序打包传递给目标方法。但是QML是按照参数名来传递的,如同下面一样,有两种方法:

//! 和http的url拼接方式一样,{arg}必须和method2中的参数名一样
$.invoke("controller2.method2?arg={0}".format("qml请求method2参数")).then(function(ret){
    console.log("qml请求method2完成", ret);
});
var method2Obj = {
    arg: "qml请求method2参数"
};
$.invoke("controller2.method2", method2Obj).then(function(ret){
    console.log("qml请求method2完成", ret);
});

输出结果

method2: "qml请求method2参数"
method2: "qml请求method2参数"
qml: qml请求method2完成 2
qml: qml请求method2完成 2


自定义类型

传递自定义类型比较麻烦,而且参数和返回值都不能用容器,只能用对象,所以前面的method3不能调用,如果确实需要传递或者返回容器的话,可以将参数类型或者返回值改成QJsonArray,然后在cpp代码里面用

McJsonUtils::fromJson<T>();



McJsonUtils::toJson();

来手动转换,他们的头文件是

#include <McBoot/Utils/McJsonUtils.h>

。业务函数调用有两种方式,一种是直接返回

QObject *

,这是QT自带的方法,这里不过多介绍。这里介绍框架衍生出的一种方法。

如上面所说,我们在Controller2.h中新加一个方法

Q_INVOKABLE ReturnVoPtr method4(const RequestVoPtr &vo);

Controller2.cpp

ReturnVoPtr Controller2::method4(const RequestVoPtr &vo)
{
    qDebug() << QStringLiteral(u"method4:") << vo->req;
    auto ret = ReturnVoPtr::create();
    ret->ret = QStringLiteral(u"method4返回");
    return ret;
}

同时需要改造一下参数的类型RequestVo.h

#pragma once

#include <McBoot/McBootGlobal.h>

struct RequestVo
{
    Q_GADGET
    MC_JSON_SERIALIZATION()
public:
    MC_POCO_PROPERTY(QString, req)
};

MC_DECL_METATYPE(RequestVo)

ReturnVo.h

#pragma once

#include <McBoot/McBootGlobal.h>

struct ReturnVo
{
    Q_GADGET
    MC_JSON_SERIALIZATION()
public:
    MC_POCO_PROPERTY(QString, ret)
};

MC_DECL_METATYPE(ReturnVo)

qml中调用

var method4Obj = {
    vo: {
        req: "qml请求method4参数"
    }
}
$.invoke("controller2.method4", method4Obj).then(function(ret){
    console.log("qml请求method4完成", ret.ret);
});

输出结果

"method4:" "qml请求method4参数"
qml: qml请求method4完成 method4返回

待续…



版权声明:本文为m0_37989747原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。