[QML]从零开始QML开发(二)QML开发,浅谈控件、槽函数、锚等基本概念。QML和C++怎么交互?

发布时间 2023-06-24 18:26:37作者: 轩先生。

[QML]从零开始QML开发(二)QML开发,浅谈控件、锚等,Layout?差不多得了!

先看代码:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5

Window {
    visible: true
    width: 320
    height: 480
    title: qsTr("Hello World")
    color: "gray"
    Button{
		x:100  //设置按钮的横坐标
		y:100  //设置纵坐标
		text:"我是按钮"   //按钮标题

		//一个类似JS风格的函数
		function slotAnyway(){
			console.log("slotAnyway")
		}
		//信号槽连接
		onClicked: {
			slotAnyway()
			console.log("点击")
		}
	}
    Button
    {
        id:btn1
        x:100
        y:100
        text:"按钮1"
        width: 100
        height: 30
 
        background: Rectangle {
               color:"#0857C9"
               }
    }
 
    Button
    {
        id:btn2
        x:200
        y:160
        text:"按钮2"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(77/255,76/255,167/255,1)
               }
    }
 
    Button
    {
        id:btn3
        x:10
        y:220
        text:"按钮3"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(54/255,54/255,167/255,1)
               }
    }
 
    Button
    {
        id:btn4
        x:100
        y:280
        text:"按钮4"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(177/255,76/255,67/255,1)
               }
    }
}

槽函数

由上面这个代码,我们了解这个槽函数就通过这段代码来看:

image

在这个qml脚本中,我们仍然是通过on + 槽函数 来调用这样一个槽函数,比如onClicked,这个函数可以构造一个类javaScript语言的写法,具体怎么写,你就看看这个代码吧,我也很难形容....

然后我们可以通过function的方法生命一个函数,在里面调用一些方法,然后再槽函数里面去调用这样的函数。当然了,我们也可以直接在槽函数内调用已经写好的槽函数。

锚应该也是一个前端的概念,anchors需要通过控件的id来设计布局,例如上面的实现思路:先指定第一个控件的位置,其它控件根据它的位置进行布局。btn2的顶部和btn1的顶部对其,btn2的左边处在btn1的右边,距离为10.btn3,btn4类似的设计

可以看另外一段代码:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
 
Window {
    visible: true
    width: 320
    height: 480
    title: qsTr("Hello World")
    color: "gray"
 
    Button
    {
        id:btn1
        x:10
        y:10
        text:"按钮1"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: "blue"
               }
    }
 
    Button
    {
        id:btn2
//        x:200
//        y:160
        text:"按钮2"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(77/255,76/255,167/255,1)
               }
			   
			   

 
        anchors.left: btn1.right
        anchors.leftMargin: 20
        anchors.top: btn1.top
    }
 
    Button
    {
        id:btn3
//        x:10
//        y:220
        text:"按钮3"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(54/255,54/255,167/255,1)
               }
 
        anchors.top: btn1.bottom
        anchors.topMargin: 10
        anchors.left: btn1.left
    }
 
    Button
    {
        id:btn4
//        x:100
//        y:280
        text:"按钮4"
        width: 100
        height: 30
 
        //设置按钮背景颜色
        background: Rectangle {
               color: Qt.rgba(177/255,76/255,67/255,1)
               }
 
        anchors.left: btn2.left
        anchors.top: btn2.bottom
        anchors.topMargin: 10
    }
}

从上我们就可以看得出,在这里实际上是用的一个锚来做的一个简单相对关系,而不是通过layout来进行一个整体布局。这样做的好处非常明显,就是控件之间的关系异常明显,而不是layout那样的黑盒---当layout整体放大或者缩小时,我将无法的得知这个控件会以何种方式放大或者缩小。

在Qt开发中,QML中的anchors和QWidget中的Layout都是用于界面布局的机制,但它们有着不同的优劣势。

对于QML中的anchors(锚点)来说,它是一种相对定位的方式。通过设置元素之间的关系,如上下左右的锚点关联,可以使得界面元素能够自动适应不同分辨率、窗口大小和设备方向的变化。这种灵活性使得开发者可以更加方便地设计自适应的界面,在移动设备或不同平台上具有良好的兼容性。同时,QML还支持动态切换布局,使得界面的交互效果更加流畅。

而在QWidget中,可以使用各种不同的布局管理器(Layout)来实现界面布局。布局管理器提供了一种自动化的方式来管理界面元素的位置和大小。通过布局管理器,开发者可以以更加直观的方式定义界面的结构和组织,而无需手动计算和设置每个元素的位置。此外,布局管理器还可以根据窗口大小自动调整布局,保持界面的整体美观和一致性。

总体来说,QML中的anchors适合用于需要较为自由和灵活的界面设计,特别是针对移动设备或多平台的开发;而QWidget中的Layout适合用于传统桌面应用程序的界面设计,更强调自动化的布局管理。选择使用哪种方式,可以根据具体的需求、开发者的习惯和项目特点来决定。

qml C++交互

当然了,需要注意的一点是,QML和C++已经完全是两个体系了,不像我们在QWidget中和C++的交互那样浑然天成,或者直接通过信号槽就可以进行的,而是需要一些别的手段来获取QML中的控件或者信息,以下介绍几种方法:

一、qml调用C++

​ Qt 提供了两种在 QML 环境中使用 C++对象的方式∶

​ 方式1:在C+中实现一个类,注册为 QML 环境的一个类型,在 QML 环境中使用该类型创建对象。
​ 方式2:在 C++中构造一个对象,将这个对象设置为 QML 的上下文属性,在QML 环境中直接使用该属性。

​ 不管哪种方式,对要导出的 C++类都有要求,不是一个类的所有方法、变量都可以在 QML 语境中使用,定义可以导出的 C++类
前提条件
要想将一个类或对象导出到 QML 中,下列的前提条件必须满足∶

(1)从 QObject 或 QObject 的派生类继承,并使用Q_OBJECT宏,这和使用信号与槽的前提条件一样的,这两个条件是为了让一个类能够进入Qt强大的元对象系统(meta-object system)中,只有使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用,才可以在 QML 中被访问。

(2)成员函数想在qml中被调用,则需要在声明前加上Q_INVOKABLE

(3)槽函数可以用类对象在qml代码中直接调用

(4)C++的成员属性可以用Q_PROPERTY宏设置

(5)枚举体需要用Q_ENUMS导出

现在来举个例子:

#ifndef TESTBOX_H
#define TESTBOX_H

#include <QObject>

class TestBox : public QObject
{
    Q_OBJECT
    Q_ENUMS(ColorType)
    Q_PROPERTY(int mValue READ getValue WRITE setValue)

public:
    explicit TestBox(QObject *parent = nullptr);

    enum ColorType
    {
        Red,
        Green,
        Blue
    };

    // 成员函数想在qml中被调用,则需要在声明前加上Q_INVOKABLE
    Q_INVOKABLE int fun1();

    int getValue()
    {
        return m_value;
    }

    Q_INVOKABLE void setValue(int value)
    {
        m_value = value;
    }

signals:
    void sig_Value();

public slots:
    void on_Get();

private:
    int m_value = 0;
};

#endif // TESTBOX_H

写完的类需要在main.cpp中注册一下,内容如下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "testbox.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
	//将这个类注册进qml系统内
    qmlRegisterType<TestBox>("cpp.qt.TestBox", 1, 0, "TestBox");

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

这样我们就可以在QML中使用我们声明的函数了,举例如下:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import cpp.qt.TestBox 1.0

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

    TestBox{
        id:tb
        mValue:123  //可以在这里给对象赋值
    }

    Button{
        id:btn1
        text:"getValue"
        anchors.left: parent.left
        anchors.leftMargin: 40
        anchors.top: parent.top
        anchors.topMargin: 60

        onClicked: {
            tb.on_Get()  //调用槽函数
        }
    }

    Button{
        id:btn2
        text:"setValue"
        anchors.left: btn1.left
        anchors.top: btn1.bottom
        anchors.topMargin: 20

        onClicked: {
            tb.setValue(3456)
        }
    }

    Button{
        id:btn3
        text:"getenum"
        anchors.left: btn2.left
        anchors.top: btn2.bottom
        anchors.topMargin: 10

        onClicked: {
            valueTextFeild.text = TestBox.Blue  //调用枚举
        }
    }

    Button{
        id:btn4
        text:"invoke fun"
        anchors.left: btn3.left
        anchors.top: btn3.bottom
        anchors.topMargin: 10

        onClicked: {
            valueTextFeild.text = tb.fun1()  //调用普通成员函数
        }
    }

    TextField
    {
        id:valueTextFeild
        anchors.left: btn1.right
        anchors.leftMargin: 15
        anchors.top: btn1.top
    }

    //链接信号槽
    Connections{
        target: tb

        onSig_Value:{
            valueTextFeild.text = tb.mValue  //获取成员属性值
        }
    }
}

但是这也有个问题,就相当于是你是在这个qml内创建了一个c++对象,那问题来了,这不就不符合我们的mvc模式了吗?
我知道你很急,但是你先别急,除了qml调用c++,也请让我们来看看c++调用qml,毕竟我们更多的是希望把QML当成一个纯粹的界面组件使用,类似前后端的模式,并不是说让qml去申请C++的对象,否则不是我们本末倒置了吗?

C++调用qml

在C++代码中获取qml控件时,需要用到objectName

Text{
        objectName: "textLabel"
        text:"Hello World"
        anchors.centerIn: parent
        font.pixelSize: 26
    }

    Button{
        objectName: "quitBtn"
        anchors.right: parent.right
        anchors.rightMargin: 10
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 10
        text:qsTr("退出")
    }

上面的控件在声明时,都设置了属性objectName, qt程序在初始化时有个对象树,在对象树中根据objectName调用findChild可以获取到控件对象指针

//C++获取qml的控件
QObject* pQuitBtn = pRoot->findChild<QObject*>("quitBtn");
if(pQuitBtn)
{
    QObject::connect(pQuitBtn, SIGNAL(clicked()), &app, SLOT(quit()));
}

QObject *pText = pRoot->findChild<QObject*>("textLabel");
if(pText)
{
    bool bRet = QMetaObject::invokeMethod(pText, "setText", Q_ARG(QString, "AAAA"));
    qDebug() << "bRet = " << bRet; //调用失败,没有该方法

    pText->setProperty("color", QColor::fromRgb(255,0,0));
}

也就是说,实际上是把QML的东西打包起来,当成一个QObject来使用,通过信号槽来交互,所有的事件绑定都写在总的进程中。这算是一种更好地实现MVC模式的方案,也就是说,我们需要把界面和业务拆开,肯定是不希望二者过度融合,而是将其做成一个单独的组件。