NotePublic/Software/Development/Environment/Qt/QML/QML_应用程序开发技术总结.md

13 KiB
Raw Blame History

QML 应用程序开发技术总结

1. 方法/属性名称大小写

QML 语法与 JS 相同,具体请参考相关教程,此处只对一些需要特殊注意的地方进行说明。

QML 的属性、变量、函数等一般为小写字母开头,大写字母开头通常具有特殊意义,比如使用 QML 连接名为 demo() 的信号时,系统将自动连接 onDemo() 作为其槽函数。以下以连接 Button 的 clicked 信号为例进行演示:

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

Window  {
    width: 640
    height: 480
    visible: true

    Button {
        onClicked: {
            // Do something...
        }
    }
}

2. 全局属性

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

Window  {
    width: 640
    height: 480
    visible: true

    property int demoInt: 1
    property string demoStr: "Hello"
}

3. 连接信号槽

TODO: 细化

信号与信号处理器

signal()

on<Signal>: {}

属性变化信号与属性变化信号处理器

property

on<Property>Changed: {}

附加属性与附加信号处理器

Attached Signal(附加属性)

XX元素.on<附加属性>: {}

Connections 建立信号与槽的连接

Connections {
    target: 发送者
    发送者信号处理器:{}
}

connect()方法 信号连接信号

元素对象.信号.connect(信号)

4. 界面加载完成信号

许多时候需要在 QML 程序界面完成加载后做一些事情,比如设置一些属性的初始值。这时候就需要用到 Component 的 completed() 信号:

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

Window  {
    width: 640
    height: 480
    visible: true

    Text {
        id: demoText
        anchors.fill: parent
    }

    Component.onCompleted: {
        demoText.text = "Hello"
    }
}

5. 包别名

编写 QML 应用时需要像 include 那样引用一些软件包,有的时候软件包中的对象会有重名的情况,比如 QtQuick.Dialogs 1.3 和 Qt.labs.qmlmodels 1.0 中均包含了 FileDialog 对象,但他们的功能并不相同。此时可以使用 import as 来实现类似别名或者命名空间的功能。

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Dialogs 1.3
import Qt.labs.platform 1.1 as QLP

Window  {
    width: 640
    height: 480
    visible: true

    FileDialog {
        title: qsTr("保存到…")
        folder: QLP.StandardPaths.writableLocation(QLP.StandardPaths.DocumentsLocation)
    }
}

6. 添加图标

6.1. 制作图标

先安装 ImageMagic 工具,并将其添加到系统 PATH 下方便使用。

使用以下命令将多个不同尺寸的图片打包成一个 ICO 文件。

magick.exe convert icon-16.png icon-32.png icon-256.png myappico.ico

6.2. 添加图标到应用

在 Qt .pro 文件中添加以下内容,以便将图标编译到 Qt 程序中:

RC_ICONS = myappico.ico

However, if you already have an .rc file, for example, with the name myapp.rc, which you want to reuse, the following two steps will be required. First, put a single line of text to the myapp.rc file:

IDI_ICON1               ICON    "myappico.ico"

Then, add this line to your myapp.pro file:

RC_FILE = myapp.rc

If you do not use qmake, the necessary steps are: first, create an .rc file and run the rc or windres program on the .rc file, then link your application with the resulting .res file.

更详细内容见《Setting the Application Icon》。

7. 绘制圆形

设置 Rectangle 的 radius 为边长的一半即可。

8. Dialog 对象

在使用 QML 的 Dialog 对象时,如果使用 QGuiApplication 来执行则会导致无法加载主题风格,并且对话框无法正确显示图标。如果最初使用 QGuiApplication 创建了 app则需要进行如下修改

/**
 * .pro 文件中增加 widgets
 */
QT += widgets

/**
 * main.cpp 中替换 QGuiApplication 为 QApplication
 */
// #include <QGuiApplication>
#include <QApplication>
// QGuiApplication app(argc, argv);
QApplication app(argc, argv);

Dialog 对象默认不显示,当调用 Dialog 的 open() 方法后弹出窗口并阻塞父窗体的执行。

8.1. FileDialog

FileDialog 为标准文件对话框。

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import Qt.labs.platform 1.1 as QLP

Window  {
    width: 640
    height: 480
    visible: true

    Button {
        text: qsTr("保存到")

        onClicked: {
            savetoDialog.open()
        }
    }

    FileDialog {
        id: savetoDialog
        title: qsTr("保存到…")
        selectFolder: true
        onAccepted: {
            // ...
        }
    }

    Component.onCompleted: {
        savetoDialog.folder = QLP.StandardPaths.writableLocation(QLP.StandardPaths.DocumentsLocation)
    }
}

8.2. MessageDialog

MessageDialog 为标准消息对话框。

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import Qt.labs.platform 1.1 as QLP

Window  {
    width: 640
    height: 480
    visible: true

    function showInfoDialog(title, info, detail) {
        infoDialog.title = title
        infoDialog.text = info
        infoDialog.detailedText = detail
        infoDialog.open()
    }

    MessageDialog {
        id: infoDialog
        standardButtons: MessageDialog.Ok
        title: ""
        text: ""
    }

    Button {
        text: qsTr("显示消息")

        onClicked: {
            showInfoDialog(qsTr("标题"), qsTr("信息"), qsTr("细节"))
        }
    }
}

9. ComboBox

ComboBox 为标准组合框

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

Window  {
    width: 640
    height: 480
    visible: true

    ComboBox {
        id: baudrate
        width: 88
        height: 24
        anchors.left: port.right
        anchors.top: parent.top
        currentIndex: 6
        anchors.leftMargin: 8
        anchors.topMargin: 8
        model: ["2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800"]
    }
}

10. ScrollView

ScrollView 会为其所容纳的对象创建滚动条。

11. GridView

GridView 可以以网格的形式显示模型内容。可以使用 ListModel 或 XmlListModel 作为模型。

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

Window  {
    width: 640
    height: 480
    visible: true
    id: window
    // 测试按钮, 添加项目
    Button {
        id: butAdd
        width: 64
        height: 24
        text: qsTr("添加")
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.leftMargin: 4
        anchors.topMargin: 4

        onClicked: {
            demoList.append({fname: "fruit", fcost: "price"})
        }
    }
    // 测试按钮, 清空全部项目
    Button {
        id: butClear
        width: 64
        height: 24
        text: qsTr("清空")
        anchors.left: butAdd.right
        anchors.top: parent.top
        anchors.leftMargin: 4
        anchors.topMargin: 4

        onClicked: {
            demoList.clear()
        }
    }

    // 使用 Rectangle 作为背景.
    Rectangle {
        color: "#e0e0e0"
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: butAdd.bottom
        anchors.bottom: parent.bottom
        anchors.rightMargin: 4
        anchors.bottomMargin: 4
        anchors.leftMargin: 4
        anchors.topMargin: 4

        GridView {
            id: demoView
            anchors.fill: parent
            cellWidth: 128
            cellHeight: 32
            contentWidth: cellWidth
            contentHeight: cellHeight
            // clip 属性非常重要, 如不设置该属性, 显示的单元信息有可能溢出 GridView 边界.
            clip: true

            // 滚动条设置.
            ScrollBar.vertical: ScrollBar {
                policy: ScrollBar.AlwaysOn
            }

            // 使用 ListModel 作为模型.
            model: ListModel {
                id: demoList
                ListElement {fname: "Apple"; fcost: "2.45"}
                ListElement {fname: "Orange"; fcost: "3.25"}
            }

            // 委托包含用于显示信息的 Text 对象和用于获取鼠标点击事件的 MouseArea
            delegate: Rectangle {
                id: demoDelegate
                width: demoView.cellWidth
                height: demoView.cellHeight
                color: "#f6f6f6"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        // index 为系统传入参数, 代表当前点击对象的索引值.
                        // fcost 和 fcost 为 demoList 中的对象属性, 需要特别注意其用法.
                        demoList.setProperty(index, "fcost", "free")
                        console.log(demoList.get(index).fcost)
                    }
                }

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text: fname+": "+fcost
                }

            }
        }
    }
}

12. QML 与 C++ 交互

QML 与 C++ 交互的主要实现方式是:

  1. QML 访问 C++ 中声明的类型;
  2. QML 访问 C++ 中创建的对象;
  3. QML 调用 C++ 中的方法或发射信号;
  4. QML 响应 C++ 中产生的信号;
  5. C++ 访问 QML 对象。

QML 与 C++ 之间主要通过信号槽机制来传递消息。

12.1. QML 访问 C++ 中声明的类型

QML 使用 C++ 中声明的类型可以为类、结构体或枚举等。若需要将 C++ 类导出给 QML则需要使用 qmlRegisterType() 方法进行注册:

qmlRegisterType<Type>("package.Type", <version>, <sub-version>,"Type");

之后在 QML 中使用 C++ 类创建对象即可。

若 QML 需要使用 C++ 中声明的枚举类型,则该枚举需要使用 Q_ENUM() 进行修饰;若访问属性成员则需要使用 Q_PROPERTY() 进行修饰;若访问方法则该方法需要使用 Q_INVOKABLE() 来修饰。C++ 中的信号不需要特殊处理QML 可直接访问。

若对象在 C++ 上下文中创建,并需要在 QML 中使用该对象,可以使用:

QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("qmlObj", cObj);

将 C++ 对象注册到 QML 上下文环境中。

12.2. C++ 访问 QML 对象

在 QML 中为对象添加 objectName 属性后,在 C++ 中可使用:

auto root = engine.rootObjects();
auto qmlObj = root.first()->findChild<QObject*>("object name");

查找该对象,再连接信号槽等。

大部分情况下在 QML 中访问 C++ 即可实现较完善的功能QML 传递信息给 C++ 完全可以通过信号槽机制实现。除非需要在 C++ 中动态创建对象并连接到 QML 中的信号槽,否则没必要这样设计。

12.3. qRegisterMetaType

当使用信号槽机制是,需要注意一点:如果需要通过信号槽传递自建类型数据,则需要使用 qRegisterMetaType() 方法进行注册。

qRegisterMetaType<MyClass>("Myclass");

12.4. QML 与 C++ 交互综合示例

TODO: 添加示例
TODO: 添加示例
TODO: 添加示例

13. Windows 下 QML 程序的打包发布

Qt 提供了导出 Qt 环境变量的命令行脚本比如“Qt 5.15.2 (MinGW 8.1.0 64-bit)”,运行该脚本可进入带有 Qt 环境变量的命令行界面,之后可通过如下命令打包程序(编译生成的可执行程序需要拷贝到<Package Output Path>

cd <Package Output Path>
windeployqt <Exe File> [--qmldir <Project QML File Path>]

Qt 自带的打包程序会添加额外的库,如果想进一步减小体积,可手动筛减。

14. 外部参考

  1. 深入了解JS中的整数
  2. QML 中的信号与槽
  3. QML 控件类型ScrollBar、ScrollIndicator
  4. QML类型GridView
  5. Qt Quick 常用元素ComboBox(下拉列表) 与 ProgressBar(进度条)
  6. 【QML Model-View】ListView-增删改查(二)
  7. 关于 Q_ENUMS 和 Q_ENUM 的区别和用法
  8. C++ 共享枚举类型给 QML
  9. QML Connections: Implicitly defined onFoo properties in Connections are deprecated.
  10. QML 调用 C++ 方法