QT基础——QML及其相关的qt模块

QML 语言 (qt6)

https://doc.qt.io/qt-6/qmlreference.html
以qt6版本说明,其他版本不一定对的上

基础语法

主要是三个方面:import关键字,对象的声明(子对象),注释

import QtQuick 2.0

Rectangle {
    //注释
    /*
        这是一些注释
     */
    width: 100
    height: 100
    color: "red"
}

QML类型系统

支持三种类型:值类型,对象类型,js类型(js的对象和数组)
对象类型拥有Attributes,下一节说明

QML对象类型的属性(Attributes)

每个QML对象类型都有一组已定义(或者说内置的)的Attributes,当一个对象被创建,该对象总会包含一些Attributes。
Attributes有如下分类

  • id attribute
  • property attributes
  • signal attributes
  • signal handler attributes
  • method attributes
  • attached properties and attached signal handler attributes
  • enumeration attributes

id

id必须以小写字母或下划线开头,并且不能包含字母、数字和下划线以外的字符

Item{
    id:root
}

Property Attributes

Property是沿袭自qt的c++里的特性,这里不详细说。

定义一个对象的Proterty:[default] [required] [readonly] property <propertyType> <propertyName>

Rectangle {
    property color previousColor
    property color nextColor
}

property名称必须以小写字母开头,并且只能包含字母、数字和下划线。
声明一个自定义属性会隐式创建该属性的 “值更改信号”,以及对<PropertyName>Changed调用的关联信号处理程序,其中<PropertyName>是属性的名称,第一个字母大写。还是上面的例子,实际上属性nextColor被修改的时候触发会信号,onNextColor(注意字母n这里被大写了)会处理相关信号

Rectangle {
    property color previousColor
    property color nextColor
    onNextColorChanged: console.log("The next color will be: " + nextColor.toString())
}

枚举类型不能作为合法的property
注意,var值类型是一种通用占位符类型,可以保存任何类型的值,包括列表和对象:

property var someNumber: 1.5
property var someString: "abc"
property var someBool: true
property var someList: [1, 2, "three", "four"]
property var someObject: Rectangle { width: 100; height: 100; color: "red" }

此外,任何QML对象类型都可以用作属性类型。例如:

property Item someItem
property Rectangle someRectangle

property的初始化用冒号':'

import QtQuick 2.0

Rectangle {
    color: "red"
    property color nextColor: "blue" // combined property declaration and initialization
}

在代码块中,property的赋值要用赋值号'='

import QtQuick 2.0

Rectangle {
    id: rect
    Component.onCompleted: {
        rect.color = "red"
    }
}

property可以设置静态值也可以动态绑定:

import QtQuick 2.0

Rectangle {
    // 这俩是静态值
    width: 400
    height: 200

    Rectangle {
        // 动态绑定的,需要根据parent相关值确定
        width: parent.width / 2
        height: parent.height
    }
}

property是类型安全的,赋值错误的数据类型会报错

property可以是一个列表,用到方括号

import QtQuick 2.0

Rectangle {
    // 未初始化的list
    property list<Rectangle> siblingRects

    // 带初始化的list
    property list<Rectangle> childRects: [
        Rectangle { color: "red" },
        Rectangle { color: "blue"}
    ]
}

property可以拥有别名'[default] property alias : '
使用别名有诸多限制:
它只能引用在声明别名的类型范围内的对象或对象的属性。
它不能包含任意JavaScript表达式
它不能引用在其类型范围之外声明的对象。
与普通属性的可选默认值不同,别名引用不是可选的;首次声明别名时必须提供别名引用。
它不能引用附加的属性。
它不能引用深度为3或更大的层次结构内的属性。以下代码将不起作用:

property alias color: myItem.myRect.border.color

Item {
    id: myItem
    property Rectangle myRect
}

深度为2层就可以生效:

property alias color: rectangle.border.color

Rectangle {
    id: rectangle
}

例子:

import QtQuick 2.0

Rectangle {
    property alias buttonText: textItem.text

    width: 100; height: 30; color: "yellow"

    Text { id: textItem }
}

required关键字修饰的property在创建对象的时候必须赋值该property否则报错required property <propertyType> <propertyName>
readonly关键字可以设置只读propertyreadonly property <propertyType> <propertyName> : <initialValue>,只读property必须在初始化的时候赋值:

Item {
    readonly property int someNumber: 10
    Component.onCompleted: someNumber = 20  // 这将报错
}

信号Attribute

定义一个自定义信号:signal <signalName>[([<parameterName>: <parameterType>[, ...]])]

import QtQuick 2.0

Item {
    signal clicked
    signal hovered()
    signal actionPerformed(action: string, actionResult: int)
}

如果信号没有参数,“()”括号是可选的,如果使用参数,则必须声明参数类型
要触发信号,就类似于函数调用
Property Change Signals:QML类型还提供内置的属性更改信号,每当属性值发生更改时都会发出该信号,如前面property attributy部分所述。

信号处理Attributes

信号处理程序是一种特殊的方法属性,每当发出相关信号时,QML引擎就会调用方法实现。向QML中的对象定义添加信号将自动向对象定义添加关联的信号处理程序,默认情况下,对象定义具有空实现。客户端可以提供一个实现,以实现程序逻辑。
如下,定义了两个信号activated和deactivated:

// SquareButton.qml
Rectangle {
    id: root

    signal activated(xPosition: real, yPosition: real)
    signal deactivated

    property int side: 100
    width: side; height: side

    MouseArea {
        anchors.fill: parent
        //鼠标释放的时候触发该信号
        onReleased: root.deactivated()
        //鼠标按下的时候i触发该信号
        onPressed: (mouse)=> root.activated(mouse.x, mouse.y)
    }
}

这些信号可以由同一目录中另一个QML文件中的任何SquareButton对象接收,其中信号处理器的实现由客户端提供:

// myapplication.qml
SquareButton {
    onDeactivated: console.log("Deactivated!")
    onActivated: (xPosition, yPosition)=> console.log("Activated at " + xPosition + "," + yPosition)
}

Property Change Signal Handlers
属性更改信号的信号处理程序采用on<property>Changed的语法形式,其中<property>是属性的名称,第一个字母大写。例如,虽然TextInput类型文档没有记录textChanged信号,但由于TextInput具有文本属性,因此该信号是隐式可用的,因此可以编写一个onTextChanged信号处理程序,每当该属性更改时调用该处理程序:

import QtQuick 2.0

TextInput {
    text: "Change this!"

    onTextChanged: console.log("Text has changed to:", text)
}

Method Attributes

可以在对象内部定义方法function <functionName>([<parameterName>[: <parameterType>][, ...]]) [: <returnType>] { <body> }

import QtQuick 2.0
Rectangle {
    id: rect

    function calculateHeight() : real {
        return rect.width / 2;
    }

    width: 100
    height: calculateHeight()
}

附加(Attached) Properties 和附加(Attached)信号处理程序

语法

<AttachingType>.<propertyName>
<AttachingType>.on<SignalName>

例如,ListView类型具有附加的属性ListView.isCurrentItem,该属性可用于ListView中的每个委托对象。每个单独的委托对象都可以使用此选项来确定它是否是视图中当前选定的项目:

import QtQuick 2.0

ListView {
    width: 240; height: 320
    model: 3
    delegate: Rectangle {
        width: 100; height: 30
        color: ListView.isCurrentItem ? "red" : "yellow"
    }
}

Component.onCompleted附加信号处理程序通常用于在组件的创建过程完成时执行一些JavaScript代码。在下面的示例中,一旦ListModel被完全创建,它的Component.onCompleted信号处理程序将被自动调用以填充model:

import QtQuick 2.0

ListView {
    width: 240; height: 320
    model: ListModel {
        id: listModel
        Component.onCompleted: {
            for (var i = 0; i < 10; i++)
                listModel.append({"Name": "Item " + i})
        }
    }
    delegate: Text { text: index }
}

由于附加类型的名称为Component,并且该类型具有完成信号,因此附加的信号处理程序称为Component.onCompleted

注意事项,如下代码无法正常工作:

import QtQuick 2.0

ListView {
    width: 240; height: 320
    model: 3
    delegate: Item {
        width: 100; height: 30

        Rectangle {
            width: 100; height: 30
            color: ListView.isCurrentItem ? "red" : "yellow"    // WRONG! This won't work.
        }
    }
}

因为ListView.isCurrentItem仅附加到根委托对象,而不是其子对象。由于矩形是委托的子对象,而不是委托本身,因此它无法访问作为ListView.isCurrentItem附加的isCurrentItem属性。因此,矩形应该通过根委托访问isCurrentItem:

ListView {
    //....
    delegate: Item {
        id: delegateItem
        width: 100; height: 30

        Rectangle {
            width: 100; height: 30
            color: delegateItem.ListView.isCurrentItem ? "red" : "yellow"   // correct
        }
    }
}

枚举Attributes

Text {
    enum TextType {
        Normal,
        Heading
    }
}

枚举类型(例如TextType)和值(例如Normal)必须以大写字母开头。
用法:

Text {
    enum TextType {
        Normal,
        Heading
    }

    property int textType: MyText.TextType.Normal

    font.bold: textType == MyText.TextType.Heading
    font.pixelSize: textType == MyText.TextType.Heading ? 24 : 12
}

属性(Property)绑定

这里的属性(Property)是Qt Core提供的机制(从c++那边延续过来的),与qml的属性(Attribute)不一样

信号处理

QML 与 js

QML模块

代码文件组织结构


QT分为几大模块,比如QtCore、QtGUI等(所有的模块参考 https://doc.qt.io/qt-6/qtmodules.html),这些模块都是以c++库的方式提供的,在Windows上每个模块(实际是c++库)都有.h文件 .lib文件 .dll文件,用户写的c++代码最终要链接这些库构建可执行文件。

QML是一种脚本语言,类似json格式,用来做QT的ui,比如创建一个空项目就会有一个main.qml,编译项目的时候qml文件会最先被翻译成标准c++代码,然后用c++编译器编译成obj文件,最后和上边说的那些qt模块(.h .lib或者.dll)链接成可执行文件(看你是动态链接还是静态,不具体展开说)

QT的几大核心模块中,与QML直接相关的有如下几个

  • Qt QML
  • Qt Quick
  • Qt Quick Controls
  • Qt Quick Dialogs
  • Qt Quick Layouts
  • Qt Quick Test

下面详细说明这几个qml相关的qt模块


Qt QML

参考 https://doc.qt.io/qt-6/qtqml-index.html
qt,本身由多个模块构成,比如最核心的QtCore,QtQML也是组成QT的一个模块,用于QML语言的支持,上一节所说的所有QML语言语法特性等,都在QtQml模块中被实现。
以下再重复啰嗦一些qml语言的东西

提供语言内置的数据类型

值类型:bool date double enumeration int list real string url var variant void
对象类型:Component QtObject Binding Connections Timer
Models QML 类型:DelegateModel DelegateModelGroup ListElement ListModel ObjectModel

Component对象类型
Component 类型本质上允许在 QML 文档中内联定义 QML 组件,而不是作为单独的 QML 文件.类似一个黑盒子,它常用于动态生成控件
信号有两个completed()和destruction(),分别是创建完成以及销毁的时候发出

QtObject对象类型
QtObject类型是一个仅包含objectName属性的非可视元素。
如果您需要一个非常轻量级的类型来封装一组自定义属性,那么创建QtObject可能很有用

Binding对象类型
有时需要将一个对象的属性绑定到另一个没有被QML直接实例化的对象的属性,例如C++导出到QML的类的属性。您可以使用绑定类型来建立此依赖关系;将任何值绑定到任何对象的属性。

Connections对象类型
Connections对象创建与QML信号的连接。https://doc.qt.io/qt-6/qml-qtqml-connections.html
在Connections对象里写事件处理函数,连接到事件上即可

Timer对象类型
计时器,比如下面定时更改文本

import QtQuick 2.0

Item {
    Timer {
        interval: 500; running: true; repeat: true
        onTriggered: time.text = Date().toString()
    }

    Text { id: time }
}

语言的语法支持

  • import 模块
    支持导入qml库,javascript文件,已注册类型的版本化命名空间,目录。用法例子:
import QtQuick 2.0
import QtQuick.LocalStorage 2.0 as Database
import "../privateComponents"
import "somefile.js" as Script
  • 注释
    和c语言一样

  • 对象的声明
    类似于json,用花括号,比如下面声明一个内置的Component类型的对象

Component{
  id: redSquare
}

QML Object

QML的基本概念是QML Object,比如Button就是一个QML Object。可以把QML Object理解为类,有方法(函数)、信号(函数)、槽函数(函数)、属性(public的成员变量),这些东西统称为QML Object Attributes,有如下:

信号 槽

信号的种类一般是三种:
(a)用户输入产生
比如clicked、touch,鼠标、键盘等等。
(b)属性被修改发出的信号
绝大多数属性被修改的时候能够产生名称为 <属性名>Changed 的信号,然后也会有 on<属性名>Changed 的信号处理器
(c)附加信号
附加属性提供的信号,也会有附加信号提供的信号处理器

在qml中槽被称作“信号处理器”,实际还是c++里的槽函数。
在qt实现的很多模块的对象中(比如Button)“信号”一般都会对应一个“信号处理器”命名方式则是on
下图是Button的信号列表(索引中可以看到,实际是button的父类AbstractButton)

比如clicked()就有一个onClick()的“信号处理器”

connect

自定义信号

JavaScript语言支持

可以在qml代码中嵌入js语言
js用法 https://doc.qt.io/qt-6/qtqml-javascript-expressions.html
可以在qml文件中定义js函数

也可以把js函数写到单独的文件中,比如main.js,qml文件中的用法如下:

可以把js函数链接到信号:

QML 与C++集成

使用上下文属性将C++对象嵌入到 QML 中

***.qml文件最终都会先编译成标准c++代码,然后被g++或者clang++这样的编译器编译为可执行文件。
c++类是可以暴露给QML,在qml文件中使用的。

c++类的特殊要求

c++的类想要被qml访问,有两个基本条件:
1、派生自QObject或者QObject的子类
2、使用Q_OBJECT宏
如下例子:

c++里的信号槽在qml里可以直接使用

c++里的普通成员函数需要特殊处理才可以在qml中调用

c++的枚举类型要做特殊处理才可以被qml访问

属性
使用Q_PROPERTY注册属性,以在qml中访问,需要使用Q_PROPERTY宏。

qml里用c++对象

有两种方式
1、在Qt元对象系统中注册C++类,在QML中实例化、访问;
可以参考 https://jingyan.baidu.com/article/046a7b3e912ef7f9c27fa92d.html
2、在C++中实例化并设置为QML上下文属性,在QML中直接使用。

qml里直接用即可

官方推荐原则,尽量不要在c++中查找qml元素,而是要在qml中操作c++元素。

一个简单的例子,创建串口
sp.h

#ifndef SP_H
#define SP_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <iostream>
using namespace std;

class sp : public QObject
{
    Q_OBJECT
public:
    explicit sp(QObject *parent = nullptr);
    void listPorts();

    Q_PROPERTY(QStringList ports READ getPorts);
    QStringList m_ports;


public:
    Q_INVOKABLE void openPort(QString portName){
        serial.setPortName(portName);
        serial.setBaudRate(9600);
        serial.setDataBits(QSerialPort::Data8);
        serial.setParity(QSerialPort::NoParity);
        serial.setFlowControl(QSerialPort::NoFlowControl);
        serial.open(QIODevice::ReadWrite);
        //cout << "open port" << endl;
    }

    Q_INVOKABLE void closePort(){
        serial.close();
        //cout << "close port" << endl;
    }


private:
    QSerialPort serial;
    QStringList getPorts();

signals:

};

#endif // SP_H

sp.cpp

#include "sp.h"



sp::sp(QObject *parent)
    : QObject{parent}
{

}


QStringList sp::getPorts() {
    m_ports.clear();
    const auto portInfos = QSerialPortInfo::availablePorts();
    for(int i = 0; i < portInfos.size(); i++){
        m_ports.push_back(portInfos[i].portName());
    }
    return m_ports;
}

main.cpp在创建engine变量后加上这两句

    sp *sport = new sp();
    engine.rootContext()->setContextProperty("sport", sport);

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

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

    Button {
        text: "I am a Button"
        onClicked: {
            sport.openPort("******");
            sport.closePort();
        }
    }
}

QtQuick

在QtQML之外又扩展了一些类型
提供UI方面的基础比如定位、动画等

QtQuick Controls

提供一些控件,比如按钮、下拉框等。

Qt Quick Dialogs

提供弹窗相关的UI元素:Dialog(后续弹窗的基类) ColorDialog(颜色选择框) FileDialog(文件选择框) FolderDialog(文件夹选择框) FontDialog(字体选择框) MessageDialog(消息弹窗)

RowLayout{
    spacing: 5
    visible: false
    Text {
        text: qsTr("ant bin path")
        Layout.preferredWidth: 120
    }
    TextField{
        Layout.fillWidth: true
        id:ant_bin_path_value
    }
    Button{
        text: qsTr("choose")
        onClicked: {
            ant_bin_path_folderdialog.open()
        }
        Layout.preferredWidth: 80
    }
    FolderDialog{
        id:ant_bin_path_folderdialog
        onAccepted: {
            var str = ant_bin_path_folderdialog.currentFolder.toString()
            str = str.replace("file:///", "")
            ant_bin_path_value.text = str
            cf.setAntBinPath(str)
        }
    }
}

Qt Quick Layouts

在QtQuick包里提供了Row和Column两种控件,可用于布局 Row和Column都继承自Item,内部可以用anchor定位,但是无法水平/垂直对齐,可以用padding,如下是官网的例子

import QtQuick 2.0

Row {
    spacing: 2
    Rectangle { color: "red"; width: 50; height: 50 }
    Rectangle { color: "green"; width: 20; height: 50 }
    Rectangle { color: "blue"; width: 50; height: 20 }
}


QtQuick.Layouts提供的布局方案

  • Layout(以下的基类)
  • ColumnLayout
  • GridLayout
  • RowLayout
  • StackLayout

padding是control类的属性,只有继承自control的控件才有padding
window没有padding
anchor是item类的属性,只有继承自item的类才有anchor,Window不是control也不是item,所以没有padding和anchor(肯定没有anchor,窗口是独立的)

alignment示例

Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 30

一个布局的示例

RowLayout{
    spacing: 5
    visible: false
    Text {
        text: qsTr("ant bin path")
        Layout.preferredWidth: 120
    }
    TextField{
        Layout.fillWidth: true
        id:ant_bin_path_value
    }
    Button{
        text: qsTr("choose")
        onClicked: {
            ant_bin_path_folderdialog.open()
        }
        Layout.preferredWidth: 80
    }
    FolderDialog{
        id:ant_bin_path_folderdialog
        onAccepted: {
            var str = ant_bin_path_folderdialog.currentFolder.toString()
            str = str.replace("file:///", "")
            ant_bin_path_value.text = str
            cf.setAntBinPath(str)
        }
    }
}

Qt Quick Test

posted @ 2023-01-05 23:11  feipeng8848  阅读(584)  评论(0编辑  收藏  举报