Qt Quick基础用法

Posted Arrow

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Quick基础用法相关的知识,希望对你有一定的参考价值。

Qt Quick基础用法

1. 简介

  • Qt 是一个跨平台的, 基于C++ 和 QML进行GUI开发的工具。

1.1 Qt Widgets 与 QML/Qt Quick

  • Qt 4.7 发布时,引入了 QML,用于移动开发,其全面支持触摸操作、流畅的动画效果等。但在 Qt 5 中,QML 已经不再局限于移动开发,也可用于开发传统的桌面程序。
    • 相比之下,Qt Widgets 更“老”、更成熟,而 QML/Qt Quick 则更“新”、更“现代”。
    • 无论如何,Qt Widgets 和 QML/Qt Quick 都可以在多个平台上使用(Windows、Linux、OS X等)
  • 对于传统的桌面程序来说,优先考虑使用 Qt Widgets,若要开发更“现代”的 UI 与高级应用,建议使用 Qt5.x + QML 2.x + QtQuick 2.x。
  • 对于移动端开发来说,建议使用 QML,协同 javascript,简单快捷、渲染效果更佳、界面更炫酷。不建议使用 Qt Widgets,其显示效果、适应性都不好。

2. QML与QtQuick

  • QML 是一种用户界面规范和标记语言,允许开发人员和设计师创建高性能、流畅的动画和视觉吸引人的应用程序。
    • 用户界面规范:QML 提供了一种高度可读、声明性、类似 JSON 的语法,支持与动态属性绑定相结合的命令式 JavaScript 表达式。
    • 标记语言:像 C++ 一样,QML 是一种语言,文件格式以 .qml 结尾。
  • Qt Quick 是 QML 类型和功能的标准库,包括视觉类型、交互式类型、动画、模型和视图、粒子效果和着色效果。
  • Qt Quick 使用 QML 作为声明语言,来设计以用户界面为中心的应用程序。
  • QML 包含大量使用手机移动设备的功能模块,比如基本部件(QtQuick 模块)、GPS 定位、渲染特效、蓝牙、NFC、WebkKit 等等。

2.1 QtQuick 1.x VS QtQuick 2.x

  • 全新的 Qt 版本
    • QtQuick 1.x 基于 Qt4.x。
    • QtQuick 2.x 随 Qt5.0 一起引入。
  • 全新的绘图系统
    • QtQuick 1.x 使用 QGraphicsView/QPainter API 来绘制场景。
    • QtQuick 2.x 基于 Scene Graph,一个 OpenGL(ES)2.0 抽象层,对绘图进行了高度优化,效率更高。
  • 全新的 QML 引擎
    • Qt 4.x 中,QML 引擎基于JSC(JavaScriptCore - Webkit 的 JS 引擎)。
    • Qt 5.0 中引入 V8(Google 的开源高性能 JavaScript 引擎,用 C++ 编写,用于 Chromium、Node.js 和多个其他嵌入应用程序)。
    • Qt 5.2 中引入了 V4 JS 引擎,针对 QML 用例进行了优化,并且可以选择关闭 JIT(Just-In-Time)编译,以符合 ios 和 WinRT 平台的限制。个头更小、反应更快、扩展性也非常好。
    • 从 Qt 5.5 开始,加入了一个新模块 QtQuick3D,它提供使用 QML 语言创建 3D 应用程序/游戏的能力,其使用的是一个被命名为 FrameGraph 的新引擎,而非 Scene Graph(因为太 2D/2.4D)。
  • 模块、属性和方法、类型和 API、C++ 代码(QtDeclarative 被移除了,替代的它是Qt QML 和 Qt Quick 模块)、QML 插件的更改。

3. 信号(Signal )与槽(Slot)

  • 信号与槽(Signal & Slot)是 Qt 编程的基础,也是 Qt 的一大创新。因为有了信号与槽的编程机制,在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单。

  • 槽(Slot):就是对信号响应的函数。

    • 槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
  • 信号(Signal):就是对控件的点击事件或定时事件

    • 就是在特定情况下被发射的事件,例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。
  • 信号与槽关联是用 QObject::connect() 静态函数实现的,其基本格式是:

    • QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
      • connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
      • connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
    • 一个信号可以连接多个槽
    • 当信号和槽函数带有参数时,在 connect()函数里,要写明参数的类型,但可以不写参数名称。
    • 多个信号可以连接同一个槽
    • 一个信号可以连接另外一个信号,例如:connect(spinNum, SIGNAL(valueChanged(int)), this, SIGNAL (refreshInfo(int));
    • 严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
    • 在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT。
    • 当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。
    • 信号与槽机制是 Qt GUI 编程的基础,使用信号与槽机制可以比较容易地将信号与响应代码关联起来。
  • 在Qt c++中通过emit来发射信号,而在QML中直接将声明的信号当做函数来调用就可以触发了。

  • 事件与信号:只要记住事件比信号更底层就可以了。事件由操作系统发出,信号由Qt的对象发出

  • QML兼具了UI界面文件和QtScript的特点,非常方便的将界面设计和与C++交互融化在了一起,这是Qt Quick最重要的特性,可以让我们以最便捷的方式去开发应用。

  • 在QtWidgets中,信号与槽的连接方式使用的是QObject::connect()。相应的,在QtQuick中,signal对象也有一个connect()方法,用于将信号连接到一个或多个方法/信号。当信号连接到方法时,无论信号何时发出,该方法都将被自动调用。
    有了这种机制,可以通过方法来接收信号,而无需使用信号处理器。也可以通过disconnect()来取消连接。

4. QML与C++混合编程

  • Qt Quick能够使我们的界面生成非常绚丽的效果,但是,它本身也是有局限性的,对于一些业务逻辑和复杂算法,比如低阶的网络编程如 QTCPSocket ,多线程,又如 XML 文档处理类库 QXmlStreamReader / QXmlStreamWriter 等等,在 QML 中要么不可用,要么用起来不方便。所以呢,我们基于这种原因来混合编程。

4.1 原理和方法

  • 简单来说,混合编程就是通过QML高效便捷的构建UI界面,而使用C ++来实现业务逻辑和复杂算法。
  • Qt集成了QML引擎和Qt元对象系统,使得QML很容易从C ++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如:
    • 信号 (signals)
    • 槽函数 (slots)
    • 枚举类型 (Q_ENUMS)
    • 属性 (Q_PROPERTY)
    • 成员函数 (Q_INVOKABLE )
  • 实例代码如下
class Mixing : public QObject

    Q_OBJECT
    Q_ENUMS(BALL_COLOR) // 定义可被 QML访问的枚举类型
    // 定义可被QML访问的属性number
    Q_PROPERTY(unsigned int number READ getNumber WRITE setNumber NOTIFY Numberchanged) 
public:
    explicit Mixing(QObject *parent = nullptr);
    enum BALL_COLOR
        BALL_COLOR_YELLOW,
        BALL_COLOR_BLUE,
        BALL_COLOR_GREEN,
    ;
    unsigned int getNumber() const;
    void setNumber(const unsigned int &Number);
    Q_INVOKABLE void stop(); // 定义可被QML调用的成员函数
signals: // 定义可被QML调用的信号
    void colorChanged(const QColor & color);
    void Numberchanged();
public slots: // 定义可被QML调用的槽函数
    void start();
private:
    unsigned int m_Number;

  • 要想在QML中访问C++对象,必然要找到一种方法在两者之间建立联系,而Qt中提供了两种在 QML 环境中使用C ++对象的方式:
    • 在C ++中实现一个类,注册到QML环境中,QML环境中使用该类型创建对象
    • 在C ++中构造一个对象,将这个对象设置为QML的上下文属性,在QML环境中直接使用该属性
    • 两种方式之间的区别是第一种可以使C ++类在QML中作为一个数据类型,例如函数参数类型或属性类型,也可以使用其枚举类型、单例等,功能更强大。

4.2 QML访问C++ 类 (QML=>C++)

  • C++类要想被QML访问,首先必须满足两个条件:一是派生自QObject类或QObject类的子类,二是使用Q_OBJECT宏。
  • QObject类是所有Qt对象的基类,作为Qt对象模型的核心,提供了信号与槽机制等很多重要特性。
  • Q_OBJECT宏必须在private区(C++默认为private)声明,用来声明信号与槽,使用Qt元对象系统提供的内容,位置一般在语句块首行。

4.2.1 信号和槽

  • 可以把 C++ 对象的信号连接到 QML 中定义的方法上,也可以把 QML 对象的信号连接到 C++ 对象的槽上,还可以直接调用 C++ 对象的槽或信号
  • 槽必须被声明为public或protected,而且信号在 C++ 中使用时要用到emit关键字,但是在Qml中就是个普通的函数
void Mixing::start()

    qDebug() << "start";
    emit colorChanged(Qt::blue); // 传递了一个颜色到QML中

4.2.2 把类注册到QML中

  • 注册函数原型
template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
  • 参数说明:
    • 模板参数typename ,就是要实现的 C++ 类的类名。
    • uri:指定一个唯一的包名,类似Java 中的那种,一是用来避免名字冲突,二是可以把多个相关类聚合到一个包中方便引用。比如我们常写这个语句 “import QtQuick.Controls 2.3” ,其中的 “QtQuick.Controls” 就是包名 uri ,而2.3则是版本,是versionMajor和versionMinor的组合。
    • qmlName:是在QML中可以使用的类名
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "mixing.h"
int main(int argc, char *argv[]) 
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    qmlRegisterType<Mixing>("test.Mixing", 1, 0, "Mixing");
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();

  • 上述示例代码把C++ Mixing类注册成为QML类型Mixing,主版本是1,次版本是0,包名是test.Mixing。注意:注册动作一定要放在 QML 上下文创建之前,否则的话,注册是没有用的。
  • 在QML中导入Mixing类并使用它
import QtQuick 2.9
import QtQuick.Window 2.2
import test.Mixing 1.0
Window 
    id:root
    visible: true
    width: 640
    height: 480
    title: qsTr("mixing")
    MouseArea
        anchors.fill: parent
        onClicked: 
            mixing.start()
        
    
    Mixing  // 创建Mixing对象,并实现信号colorChanged的槽函数
        id: mixing
        onColorChanged: 
            root.color = color
        
    

4.2.3 C++ 类的属性和成员函数

  • C++中定义可被QML调用的成员函数(Q_INVOKABLE):
    • 在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,但是注意的是在QML中访问的前提是public或protected成员函数,而且这个宏必须放在返回函数前面。
  • C++中定义 可被QML访问的属性(Q_PROPERTY ):
    • 定义属性则需要使用Q_PROPERTY 宏,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。要想使用 Q_PROPERTY 宏,定义的类必须是QObject的后裔,必须在类首使用Q_OBJECT宏。
class Mixing : public QObject

    Q_OBJECT
    Q_ENUMS(BALL_COLOR)
    Q_PROPERTY(unsigned int number READ getNumber WRITE setNumber NOTIFY Numberchanged)
public:
    explicit Mixing(QObject *parent = nullptr);
    enum BALL_COLOR
        BALL_COLOR_YELLOW,
        BALL_COLOR_BLUE,
        BALL_COLOR_GREEN,
    ;
    unsigned int getNumber() const;
    void setNumber(const unsigned int &Number);
    Q_INVOKABLE void stop();
signals:
    void colorChanged(const QColor & color);
    void Numberchanged();
public slots:
    void start(BALL_COLOR ballColor);
private:
    unsigned int m_Number;
    
  • 通过Q_INVOKABLE修饰了stop函数。通过Q_PROPERTY修饰了名为number的属性,number通过Number函数读得数据,通过setNumber函数写入数据,触发信号是Numberchanged函数。
  • 成员函数和属性的实现
unsigned int Mixing::getNumber() const
    return m_Number;


void Mixing::setNumber(const unsigned int &number)
    if(number != m_Number)    
        m_Number = number;
        emit Numberchanged();
    

void Mixing::stop()
    qDebug() << "颜色改变啦!!!";

4.2.4 QML调用C++类的成员函数和属性

MouseArea
    anchors.fill: parent
    acceptedButtons:Qt.LeftButton | Qt.RightButton;
    onClicked: 
        if(mouse.button === Qt.LeftButton)
        
            mixing.start(Mixing.BALL_COLOR_BLUE)
        else if(mouse.button === Qt.RightButton)
            mixing.start(Mixing.BALL_COLOR_GREEN)
        
    
    onDoubleClicked: 
        mixing.start(Mixing.BALL_COLOR_YELLOW)
        mixing.number = 10;
    

Mixing
    id: mixing
    onColorChanged: 
        root.color = color
        mixing.stop(color)
    
    Component.onCompleted:
    
        console.log("default ball number is", number)
    
    onNumberChanged:
    
        console.log("new ball number is", number) 
    

4.3 QML上下文属性设置

  • 目标:直接嵌入一些 C++ 数据给QML使用,需要使用QQmlContext类
  • QQmlContext:
    • 定义了QML引擎内的上下文,上下文允许将数据暴露给由QML引擎实例化的QML组件
    • 每个QQmlContext包含一组属性,允许以名称将数据显式地绑定到上下文
    • 通过调用QQmlContext::setContextProperty()来定义和更新上下文属性
// 简单的上下文属性,对应的值为QVariant类型
void QQmlContext::setContextProperty(const QString &name, const QVariant &value)

// 相对来说稍微复杂一些,QObject*对象类型。
void QQmlContext::setContextProperty(const QString &name, QObject *value)

4.3.1 设置简单的 上下文属性

  • 实例
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
int main(int argc, char *argv[])
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    QQuickView view;
    view.rootContext()->setContextProperty("Data", QString("设置Qml上下文属性"));
    view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
    view.show();
    return app.exec();

  • 导入相关的类,设置了一个QString类型的属性,这个Data的值可以由加载QML组件的 C++ 程序直接设置,使用的就是setContextProperty()函数。然后,我们就可以直接在qml文件中直接使用Data了,不需要导入任何模块。
import QtQuick 2.9
import QtQuick.Window 2.2
Rectangle 
    width: 640
    height: 480
    color: "lightgray"
    Text 
        id: text
        anchors.centerIn: parent
        text: Data
    

4.3.2 设置对象为上下文属性

  • 设置对象为上下文属性
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "mixing.h"
int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    Mixing mixing; // 从堆上分配了一个Mixing对象实例
    QQuickView view;
    view.rootContext()->setContextProperty("mixing", &mixing);
    view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
    view.show();
    return app.exec();

  • 在.qml文件中使用上下文属性中的对象
import QtQuick 2.9
import QtQuick.Window 2.2
//import test.Mixing 1.0
Rectangle 
	id:root
    visible: true
    width: 640
    height: 480
    MouseArea
        anchors.fill: parent
        acceptedButtons:Qt.LeftButton | Qt.RightButton;
        onClicked: 
            mixing.start()
            mixing.number = 10
        
    
    Connections
        target: mixing
        onColorChanged: 
            root.color = color
            mixing.stop(color)
        
        onNumberChanged:
            console.log("new ball number is", mixing.number) // 10
        
    

  • 由于去掉了qmlRegisterType() 调用,所以在 main.qml中不能再访问Mixing类了,比如说不能通过类名来引用它定义的BALL_COLOR枚举类型了,否则会出现报错:“ReferenceError: Mixing is not defined”
  • 除了枚举类型的值不可以调用之外,其他的属性,以及关联的信号和槽函数,以及用Q_INVOKABLE 宏修饰的方法都是可以继续使用的。

4.4 C++类访问QML(C++=>QML)

  • C++ 访问QML的属性、函数和信号
  • 在 C++ 中加载QML文件可以用QQmlComponent或QQuickView,然后就可以在 C++ 中访问QML对象了。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。

4.4.1 在C++中访问QML中的属性

  • 在qml文件中对Window对象添加一个Rectangle,设置objectName为“rect”,这个值是为了在C++中能够找到这个Rectangle。
import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0
Window 
    id:root
    visible: true
    width: 640
    height: 480
    Rectangle 
          objectName: "rect"
          anchors.fill: parent
    
    MouseArea
        anchors.fill: parent
        acceptedButtons:Qt.LeftButton | Qt.RightButton;
        onClicked: 
            mixing.start()
            mixing.number = 10
            qmlSignal("这是qml文件中的qml信号")
        
    
    Mixing
        id:mixing
    

  • 在main.cpp文件中加载QML文件并进行组件实例化后,就可以在 C++ 中访问、修改这个实例的属性值了。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "mixing.h"
int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");
    //QQmlApplicationEngine engine;
    //engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    //if (engine.rootObjects().isEmpty())
    //    return -1;
    QQmlEngine engine;
    QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml")));
    QObject *object = compontext.create();
    qDebug() << "width value is" << object->property("width").toInt();
    object->setProperty("width", 320);
    qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
    QQmlProperty::write(object, "height", 240);
    QObject *rect = object->findChild<QObject*>("rect");
    if(rect) 
        rect->setProperty("color", "orange");
    
    return app.exec();

  • 使用QObject::property()/setProperty()来读取、修改width属性值。
  • 使用QQmlProperty::read()/write()来读取、修改height属性值。
  • 有时候,QML组件是一个复杂的树型结构,包含兄弟组件和孩子组件,我们可以使用QObject::findchild()/findchildren()来查找。如上面的代码中,我们查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色。

4.4.2 在C++中访问QML中的函数与信号

  • 在 C++ 中,使用 QMetaObject::invokeMethod() 可以调用QML中的函数,它 是个静态方法,其函数原型如下:
bool QMetaObject::invokeMethod(
     QObject * obj,              // 被调用对象的指针
     const char * member,        // 方法名字
     Qt::ConnectionType type,    // 连接类型
     QGenericReturnArgument ret, // 用来接收返回值
     QGenericArgument val0 = QGenericArgument( 0 ),  // 传递给被调用方法的参数
     QGenericArgument val1 = QGenericArgument(), 
     QGenericArgument val2 = QGenericArgument(), 
     QGenericArgument val3 = QGenericArgument(Qt 6.x中Qt Quick简介及示例

Qt和Qt Quick QML,

QT开发(五十五)———Qt Quick Controls

QT quick基础QML学习2

Qt Quick ui 表单不支持函数

[Qt及Qt Quick开发实战精解] 第1章 多文档编辑器