QML Book技术文档

目录

认识QML

开始学习

QML快速入门

基本元素

布局

输入组件

动态元素

模型-视图-代理

画布元素

粒子模型

着色器效果

多媒体

网络

存储

动态QML


认识QML

  1. 首先,以可以旋转的风车作为例子

    • 首先,整个窗口包括背景、风车、风车杆,点击风车的时候,会开始旋转

  2. Qt界面设计工具可以实现基本的属性设置,但是对于动画、定时器等功能还不能支持,需要放在ui.qml文件外处理。

  3. 动画的处理方案一般是先建立一系列状态,为每个状态制定好样式,然后根据不同的事件下修改状态值。

  4. 状态值里面有when,可以设置在某种条件下的状态

  5. 状态的动画在qml文件中,使用translates处理动画。

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
​
Window {
    visible: true
    width: 600
    height: 400
    title: qsTr("Hello World")
​
    MainWindow {
        id: mainWindow
        anchors.fill: parent
    }
}

MainWindow.qml

import QtQuick 2.4
​
MainWindowForm {
    transitions: [
        Transition {
            from: "Normal"
            to: "RotateState"
​
            NumberAnimation {
                target: windmill
                property: "rotation"
                duration: 200
                easing.type: Easing.InOutQuad
                loops: Animation.Infinite
            }
        }
    ]
}

MainWindowForm.ui.qml

import QtQuick 2.4
​
Item {
    id: element
    width: 600
    height: 400
    property alias windmill: windmill
    property bool rotationState: false
    Image {
        id: image
        anchors.fill: parent
        fillMode: Image.PreserveAspectFit
        source: "img/bg.jpg"
​
        Rectangle {
            id: windmill
            x: 136
            y: 134
            width: 100
            height: 100
            color: "#88b3f7"
​
            MouseArea {
                id: mouseArea
                anchors.fill: parent
            }
        }
​
        Rectangle {
            id: flagpole
            x: 266
            width: 1
            color: "#000000"
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 0
            anchors.horizontalCenter: windmill.horizontalCenter
            anchors.top: windmill.verticalCenter
            anchors.topMargin: 0
        }
    }
​
    Connections {
        target: mouseArea
        onClicked: rotationState = true
    }
    states: [
        State {
            name: "Normal"
            when: rotationState == false
            PropertyChanges {
                target: windmill
                rotation: 0
            }
        },
        State {
            name: "RotateState"
            when: rotationState == true
            PropertyChanges {
                target: windmill
                rotation: 360
            }
        }
    ]
}

开始学习

  1. QML常用的模块依赖图

     

QML快速入门

属性绑定和属性赋值

MainWindow.qml

import QtQuick 2.4
​
MainWindowForm {
    function increment(){
        spacePresses = spacePresses + 1
    }
    Keys.onSpacePressed : {
        increment()
    }
    Keys.onEscapePressed : {
        element1.text = ''
    }
}

MainWindowForm.ui.qml

import QtQuick 2.4
​
Item {
    id: element
    width: 600
    height: 400
    property alias element1: element1
    property int spacePresses: 0
    Text {
        id: element1
        x: 60
        y: 62
        width: 480
        height: 148
        text: 'Space pressed: ' + spacePresses + 'times'
        font.pixelSize: 20
        focus: true
    }
​
    Connections {
        target: element1
        onTextChanged: console.log('text Change to', element1.text)
    }
}
  1. 注意:Key只能写在根元素,而不能写在子元素上;Key不能写在元素内部。

  2. 元素绑定只在初始化的时候使用,后面使用=赋值是无法对属性进行绑定的。

基本元素

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
​
Window {
    visible: true
    width: 600
    height: 400
    title: qsTr("Hello World")
​
    MainWindow {
        id: mainWindow
        anchors.fill: parent
    }
​

MainWindow.qml(空)

MainWindow.ui.qml

import QtQuick 2.12
import QtQuick.Controls 2.12
​
Item {
    id: element
    width: 600
    height: 400
    property alias image: image
​
    Rectangle {
        id: rectangle
        x: 14
        y: 16
        width: 76
        height: 96
​
        border.width: 4
        border.color: 'lightblue'
        radius: 8
        gradient: Gradient {
            GradientStop {
                position: 0
                color: "lightsteelblue"
            }
​
            GradientStop {
                position: 1
                color: "slategray"
            }
        }
    }
​
    Text {
        id: element1
        x: 104
        y: 16
        width: 228
        height: 96
        text: qsTr("Hello,Welcome to China! My Name is yongHeng")
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
        font.pixelSize: 30
        font.family: '宋体'
        elide: Text.ElideRight
        wrapMode: Text.WordWrap
        style: Text.Sunken
        styleColor: 'blue'
    }
​
    BusyIndicator {
        id: busy
        anchors.rightMargin: 0
        anchors.leftMargin: 0
        anchors.bottomMargin: 0
        anchors.top: image.top
        anchors.right: image.right
        anchors.bottom: image.bottom
        anchors.left: image.left
        anchors.topMargin: 0
    }
​
    Image {
        id: image
        x: 350
        y: 16
        width: 228
        height: 154
        fillMode: Image.PreserveAspectFit
        asynchronous: true
        Component.onCompleted: image.source = "http://i1.hdslb.com/bfs/archive/d4c618eea29b3157c1afabd06aec6827eb3ba22b.jpg"
    }
​
    Image {
        id: image2
        x: 14
        y: 140
        width: 200
        height: 200
        source: 'image://ImageProvider/img/bg.jpg'
    }
}
  • 其中,Image元素不仅可以加载本地的图片,也可以加载网络图片,但是,必须要等到组件完全加载好后才能去指定url地址。

  • 对于自绘的图片,需要重写继承QQuickImageProvider的类作为加载器

    ImageProvider.py

    from PySide2.QtQuick import QQuickImageProvider
    from PySide2.QtGui import QPixmap
    from PySide2.QtCore import QCoreApplication
    ​
    ​
    class ImageProvider(QQuickImageProvider):
        def __init__(self, type):
            super().__init__(type)
    ​
        def requestPixmap(self, id, size, requestedSize):
            strFileName =  'D:/Documents/project/QtQuickT/' + id
            print('strFileName >> ' + strFileName)
            pixmap = QPixmap(strFileName)
            return pixmap
    ​

    main.py

    from PySide2.QtGui import QGuiApplication
    from PySide2.QtQml import QQmlApplicationEngine, QQmlImageProviderBase
    from PySide2.QtCore import QUrl
    import ImageProvider
    
    if __name__ == '__main__':
        app = QGuiApplication([])
        engine = QQmlApplicationEngine()
        engine.addImageProvider('ImageProvider', ImageProvider.ImageProvider(QQmlImageProviderBase.ImageType.Pixmap))
        engine.load(QUrl('main.qml'))
        app.exec_()
    
    

布局

  • 首先,布局和定位并不是一个概念,布局只有在QtQuick 1

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

Rectangle {
    id: root
    width: 600
    height: 400
    GridLayout {
        id: gridlayout
        anchors.fill: parent
        rows: 2
        columns: 3
        Rectangle {
            color: "#ff5252"
            Layout.columnSpan: 2
            Layout.fillWidth: true
            Layout.row: 0
            Layout.column: 0
            Layout.fillHeight: true
        }
        Rectangle {
            color: "#44a2ff"
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.row: 0
            Layout.column: 2
        }
        Rectangle {
            color: "#bb09ff"
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.row: 1
            Layout.column: 0
        }
        Rectangle {
            color: "#36ff90"
            Layout.columnSpan: 2
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.row: 1
            Layout.column: 1
        }
    }
}
  • 布局中可以使用Span指定一个元素占据多少格子

  • 布局中不需要指定元素的width和height,需要只需要使用Layout.fileWidth和Layout.fileHeight自动填满整个格子即可。

  • 布局中有重复元素时,可以使用Repeater

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    
    Rectangle {
        id: root
        width: 600
        height: 400
        Grid{
            Repeater{
                  model: 16
                  Rectangle{
                      width: 56
                      height: 56
                      border.color: 'black'
                      Text{
                          anchors.centerIn: parent
                          color: 'black'
                          text: 'Cell' + index
                      }
                  }
            }
        }
    }
    

     

输入组件

  • TextInput只能输入一行

  • TextEdit可以输入多行

  • 输入元素一般都需要自定义样式

    MainWindowForm.ui.qml

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    
    Rectangle {
        id: root
        width: 600
        height: 400
    
        Column {
            id: rowLayout
            anchors.fill: parent
    
            TLineEdit {
                id: tLineEdit
                height: 30
                KeyNavigation.tab: tLineEdit1
                focus: true
            }
    
            TLineEdit {
                id: tLineEdit1
                height: 30
                KeyNavigation.tab: tLineEdit2
            }
    
            TLineEdit {
                id: tLineEdit2
                height: 30
                KeyNavigation.tab: tTextEdit
            }
            TTextEdit {
                id: tTextEdit
                height: 200
                KeyNavigation.tab: tLineEdit1
            }
        }
    
    }
    

    TLineEditForm.ui.qml

    import QtQuick 2.4
    
    FocusScope {
        width: 96
        height: 20
        id: focusScope
        Rectangle{
            anchors.fill: parent
            color: 'lightsteelblue'
            border.color: 'gray'
        }
    
        TextInput {
            id: textInput
            text: qsTr("Text Edit")
            font.pointSize: 12
            anchors.fill: parent
            anchors.margins: 4
            focus: true
        }
    }
    

    TTextEditForm.ui.qml

    import QtQuick 2.4
    
    Item {
        width: 200
        height: 200
        property alias textInputText: textInput.text
        property alias textInput: textInput
        Rectangle {
            id: rectangle
            color: "lightsteelblue"
            anchors.fill: parent
            border.color: 'gray'
        }
    
        TextEdit {
            id: textInput
            text: qsTr("Text Input")
            anchors.fill: parent
            font.pixelSize: 12
            focus: true
            anchors.margins: 4
        }
    }

     

使用键盘控制位置的Demo

MainWindowForm.ui.qml

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

Rectangle {
    id: root
    width: 600
    height: 400
    property alias square: square
    property alias root: root
    Rectangle {
        id: square
        x: 0
        y: 0
        width: 200
        height: 200
        color: 'blue'
    }
    focus: true
    Keys.onLeftPressed: square.x -= 8
    Keys.onRightPressed: square.x += 8
    Keys.onUpPressed: square.y -= 8
    Keys.onDownPressed: square.y += 8
    Keys.onPressed:
        switch(event.key){
        case Qt.Key_Plus:
            square.scale += 0.2
            break
        case Qt.Key_Minus:
            square.scale -= 0.2
            break
        }
}
  • 注意,其中的有些Javascript语句块是不能直接写在ui文件中的,但是语句是可以的。

动态元素

  • 动画启动可以和在状态里面描述,也可以直接写在动画里面

  • 动画可以分组,可以定义“同步动画”和“序列动画”

    import QtQuick 2.4
    
    MainWindowForm {
        button.onClicked: {
            anim.restart()
        }
    
        button1.onClicked: {
            anim.stop()
        }
    
        ParallelAnimation{
            id: anim
            SequentialAnimation{
                id: seqanim
                NumberAnimation {
                    id: anim1
                    target: image
                    property: "x"
                    duration: 500
                    from: 19
                    to: 378
                    easing.type: Easing.Linear
    
                }
                NumberAnimation {
                    id: anim2
                    target: image
                    property: "y"
                    duration: 500
                    from: 238
                    to: 8
                }
            }
    
    
            RotationAnimation {
                id: anim3
                target: image
                property: "rotation"
                duration: 1000
                from: 0
                to: 90
            }
        }
    
    }

     

模型-视图-代理

Repeat

  • 最基础的模型是Repeat元素

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    
    Rectangle {
        id: root
        width: 600
        height: 400
        Column{
            width: 300
            height: 400
            Repeater{
                model: 5
                Text {
                    width: 100
                    height: 30
                    text: qsTr("text" + index)
                }
            }
        }
        Column{
            width: 300
            height: 400
            x: 300
            y: 0
            Repeater{
                model: ListModel{
                    ListElement{
                        name: "Mercury"
                        surfaceColor: "gray"
                    }
                    ListElement{
                        name: "Venus"
                        surfaceColor: "yellow"
                    }
                    ListElement{
                        name: "Mercury"
                        surfaceColor: "blue"
                    }
                    ListElement{
                        name: "Mercury"
                        surfaceColor: "orange"
                    }
                    ListElement{
                        name: "Mercury"
                        surfaceColor: "lightblue"
                    }
                }
                delegate:  Rectangle{
                    width: 300
                    height: 30
                    radius: 3
    
                    color: "lightsteelblue"
                    Rectangle{
                        width: 24
                        height: 24
                        radius: 12
                        anchors.left: parent
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.leftMargin: 2
                        color: surfaceColor
                    }
                    Text {
                        font.pixelSize: 30
                        anchors.centerIn: parent
                        text: name
                    }
                }
            }
        }
    }

     

ListView、GridView

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

Rectangle {
    id: root
    width: 600
    height: 400

    ListView {
        id: listView
        x: 0
        y: 20
        width: 300
        height: 360
        orientation: ListView.Vertical //list的方向
        boundsBehavior: Flickable.DragAndOvershootBounds //list是否在空白区可以拖动
        snapMode: ListView.SnapOneItem  //list停留位置
        cacheBuffer: 30
        clip: true

        delegate: Text {
            width: 300
            height: 40
            font.pixelSize: 38
            text: modeltext
            font.bold: true
        }
        model: ListModel {
            id: listmodel

            ListElement {
                modeltext: 'zhangsanzhangsanzhangsanzhangsan'
            }
            ListElement {
                modeltext: 'lisi'
            }
            ListElement {
                modeltext: 'wangwu'
            }

        }
    }

    Button {
        id: button
        x: 306
        y: 20
        width: 149
        height: 43
        text: "插入"
    }

    Connections {
        target: button
        onClicked: listmodel.append({
                                        "modeltext": 'zhaoliu'
                                    })
    }
}
  • 使用视图-代理,可以实时的增加需要显示的元素

  • 可以使用hightlight设置选中的背景(可以响应键盘事件,但是不能响应鼠标),使用header和footer来设置头和尾

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    
    Rectangle {
        id: root
        width: 300
        height: 400
    
        ListView {
            id: listView
            anchors.fill: parent
            spacing: 5
            model: 20
            focus: true
            clip: true
            delegate: Item{
                width: 300
                height: 40
                Text{
                    font.pixelSize: 10
                    text: index
                    anchors.centerIn: parent
                }
            }
            highlight: Rectangle{
                width: 300
                height: 40
                color: 'lightgreen'
            }
            header: Rectangle{
                width: 300
                height: 40
                color: 'lightblue'
            }
            footer: Rectangle{
                width: 300
                height: 40
                color: 'lightblue'
            }
        }
    }
    

     

ListView动画

MainWindow.qml

import QtQuick 2.12

MainWindowForm {
    mouseArea.onClicked: {
        theModel.append({
                            "number": ++rectangle.count
                        })
    }

    gridView.delegate: numberDelegate
    gridView.model: theModel

    gridView.add: Transition {
        SequentialAnimation{
            NumberAnimation {
                property: "scale";
                from: 0; to: 1;
                duration: 250;
                easing.type: Easing.InOutQuad
            }
        }
    }
    gridView.remove: Transition {
        SequentialAnimation{
            NumberAnimation {
                property: "scale";
                to: 0;
                duration: 250;
                easing.type: Easing.InOutQuad
            }
        }
    }

    ListModel {
        id: theModel
        ListElement {
            number: 0
        }
        ListElement {
            number: 1
        }
        ListElement {
            number: 2
        }
        ListElement {
            number: 3
        }
        ListElement {
            number: 4
        }
        ListElement {
            number: 5
        }
        ListElement {
            number: 6
        }
        ListElement {
            number: 7
        }
        ListElement {
            number: 8
        }
        ListElement {
            number: 9
        }
    }

    Component{
        id: numberDelegate
        Rectangle {
            id: wrapper
            width: 40
            height: 40
            color: 'lightgreen'
            Text {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: number
            }
            MouseArea {
                id: mouseArea2
                anchors.fill: parent
                onClicked: {
                    if (!wrapper.GridView.delayRemove) {
                        theModel.remove(index)
                    }
                }
            }

        }
    }

}

MainWindow.ui.qml

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

Rectangle {
    width: 300
    height: 400
    property alias mouseArea: mouseArea
    property alias gridView: gridView
    property alias rectangle: rectangle


    Rectangle {
        id: rectangle
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.border
        anchors.margins: 20
        height: 40
        color: 'darkgreen'
        Text {
            anchors.centerIn: parent
            text: qsTr("Add Item")
        }
        MouseArea {
            id: mouseArea
            anchors.fill: parent
        }
        property int count: 9
    }

    GridView {
        id: gridView
        anchors.top: rectangle.bottom
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.rightMargin: 20
        anchors.leftMargin: 20
        anchors.topMargin: 10
        anchors.margins: 20
        anchors.bottomMargin: 50
        clip: true
        model: theModel
        cellWidth: 45
        cellHeight: 45
    }
}
  • 上面的例子可以看出,如果使用Component,则需要将事件、组件都写在ui文件以外

  • 其二,注意到Component内部直接饮用了onAdd,其实add信号是一个附加信号

  • 前面的例子可以有另一种写法,就是不建立ui.qml文件,这样就避免了在ui文件中写JS的限制,但是,对于QML里面的“用对象代替方法的”隐式转换同样不能省略。

    import QtQuick 2.12
    
    Rectangle {
        id: rectangle
        width: 480
        height: 300
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#dbddde" }
            GradientStop { position: 1.0; color: "#5fc9f8" }
        }
    
        ListModel {
            id: theModel
    
            ListElement { number: 0 }
            ListElement { number: 1 }
            ListElement { number: 2 }
            ListElement { number: 3 }
            ListElement { number: 4 }
            ListElement { number: 5 }
            ListElement { number: 6 }
            ListElement { number: 7 }
            ListElement { number: 8 }
            ListElement { number: 9 }
        }
    
        Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            anchors.margins: 20
    
            height: 40
    
            color: "#53d769"
            border.color: Qt.lighter(color, 1.1)
    
            Text {
                anchors.centerIn: parent
    
                text: "Add item!"
            }
    
            MouseArea {
                anchors.fill: parent
    
                onClicked: {
                    theModel.append({"number": ++parent.count});
                }
            }
    
            property int count: 9
        }
    
        GridView {
            anchors.fill: parent
            anchors.margins: 20
            anchors.bottomMargin: 80
    
            clip: true
    
            model: theModel
    
            cellWidth: 45
            cellHeight: 42
    
            delegate: numberDelegate
        }
    
        Component {
            id: numberDelegate
    
            Rectangle {
                id: wrapper
    
                width: 40
                height: 40
    
                gradient: Gradient {
                    GradientStop { position: 0.0; color: "#f8306a" }
                    GradientStop { position: 1.0; color: "#fb5b40" }
                }
    
                Text {
                    anchors.centerIn: parent
    
                    font.pixelSize: 10
    
                    text: number
                }
    
                MouseArea {
                    anchors.fill: parent
    
                    onClicked: {
                        if (!wrapper.GridView.delayRemove)
                            theModel.remove(index);
                    }
                }
    
                SequentialAnimation {
                    id: anmiremove
                    PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: true }
                    NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing.type: Easing.InOutQuad }
                    PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: false }
                }
    
                SequentialAnimation {
                    id: anmiadd
                    NumberAnimation { target: wrapper; property: "scale"; from: 0; to: 1; duration: 250; easing.type: Easing.InOutQuad }
                }
    
                GridView.onRemove: {
                    anmiremove.start()
                }
    
                GridView.onAdd: {
                    anmiadd.start()
                }
            }
        }
    
    
    }
    

     

ListView创建下拉列表动画

main.qml

import QtQuick 2.12

Rectangle {
    width: 300
    height: 480

    ListModel {
        id: model
        ListElement {
            name: "动物"
            infomations: "生物的一个种类。它们一般以有机物为食,能感觉,可运动,能够自主运动。活动或能够活动之物"
        }

        ListElement {
            name: "植物"
            infomations: "把能固着生活和自养的生物称为植物界,简称植物"
        }

        ListElement {
            name: "微生物"
            infomations: "个体难以用肉眼观察的一切微小生物之统称"
        }
    }

    ListView {
        id: listView
        anchors.fill: parent
        model: model
        delegate: DetailsDelegate {
        }
    }


}

DetailsDelegate.qml

import QtQuick 2.12

Item {
    id: wrapper

    width: 300
    height: 30
    property bool bExpand: false
    Rectangle {
        id: rectangle
        height: 30
        color: "#ffffff"
        anchors.right: parent.right
        anchors.left: parent.left
        anchors.top: parent.top

        Text {
            id: text1
            width: 80
            height: 20
            text: name
            anchors.top: parent.top
            anchors.topMargin: 2
            anchors.left: parent.left
            anchors.leftMargin: 2
            anchors.verticalCenter: parent.verticalCenter
            font.pixelSize: 28
        }
    }

    Image {
        id: closebutton
        width: 26
        height: 26
        anchors.right: parent.right
        anchors.rightMargin: 2
        anchors.top: parent.top
        anchors.topMargin: 2
        source: 'images/1172009.png'
        MouseArea{
            anchors.fill: parent
            onClicked: {
                bExpand = !bExpand
            }
        }
    }

    Rectangle {
        id: inforect
        color: "#ffffff"
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: rectangle.bottom
        anchors.bottom: wrapper.bottom
        opacity: 0
        Text {
            id: textInput1
            anchors.fill: parent
            text: infomations
            font.pixelSize: 22
        }
    }

    states: [
        State {
            name: "expand"
            when: bExpand == true
            PropertyChanges {
                target: wrapper
                height: 150
            }

            PropertyChanges {
                target: inforect
                opacity: 1.0
            }

            PropertyChanges {
                target: textInput1
                wrapMode: Text.WrapAnywhere
            }
        }
    ]
    transitions: [
        Transition {
            NumberAnimation {
                property: "height"
                duration: 200
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                property: "opacity"
                duration: 200
                easing.type: Easing.InOutQuad
            }
        }
    ]

}

路径视图

  • 用户可以自己指定图形的移动方向,密度以及滑动速度。

XMLListModel

可以从本地\网络加载XML文件,指定需要查询的键值,直接指定到对应的属性上。

列表分段

import QtQuick 2.12

Rectangle {
    width: 300
    height: 480

    ListView {
        id: listView
        anchors.fill: parent
        model: model
        delegate:childcomp
        section.delegate: comp
        section.property: 'stype'
    }

    ListModel{
        id: model
        ListElement{
            stype: '天气'
            childtype: '春'
        }
        ListElement{
            stype: '天气'
            childtype: '夏'
        }
        ListElement{
            stype: '天气'
            childtype: '秋'
        }
        ListElement{
            stype: '天气'
            childtype: '冬'
        }
        ListElement{
            stype: '动物'
            childtype: '长颈鹿'
        }
        ListElement{
            stype: '天气'
            childtype: '老虎'
        }
    }
    Component{
        id: comp
        Rectangle{
            height: 30
            width: 300
            color: 'lightblue'
            Text {
                id: name
                text: section
                anchors.centerIn: parent.Center
            }
        }
    }
    Component{
        id: childcomp
        Rectangle{
            height: 30
            width: 300
            Text {
                id: name2
                text: childtype
                anchors.centerIn: parent.Center
            }
        }
    }
}

画布元素

绘制矩形

  • 使用Canvas绘制一个矩形

    import QtQuick 2.12
    
    Rectangle {
        width: 300
        height: 480
        Canvas{
            anchors.fill: parent
            onPaint: {
                var ctx = getContext('2d')
    
                //设置或返回当前的线条宽度
                ctx.lineWidth = 4
    
                //设置或返回用于笔触的颜色、渐变或模式
                ctx.strokeStyle = 'blue'
    
                //设置或返回用于填充绘画的颜色、渐变或模式
                ctx.fillStyle = 'steelblue'
    
                //起始一条路径,或重置当前路径
                ctx.beginPath()
    
                //把路径移动到画布中的指定点,不创建线条
                ctx.moveTo(50,50)
    
                //添加一个新点,然后在画布中创建从该点到最后指定点的线条
                ctx.lineTo(150,50)
                ctx.lineTo(150,150)
                ctx.lineTo(50,150)
    
                //创建从当前点回到起始点的路径
                ctx.closePath()
    
                //填充当前绘图(路径)
                ctx.fill()
    
                //绘制已定义的路径
                ctx.stroke()
            }
        }
    }

     

  • QML提供直接绘制矩形的方法

    import QtQuick 2.12
    
    Rectangle {
        width: 300
        height: 480
        Canvas{
            anchors.fill: parent
            onPaint: {
                var ctx = getContext('2d')
                ctx.fileStyle = 'green'
                ctx.strokeStyle = 'blue'
                ctx.lineWidth = 4
    
                //绘制“被填充”的矩形
                ctx.fillRect(20,20,80,80)
    
                //在给定的矩形内清除指定的像素
                ctx.clearRect(30,30,60,60)
    
                //绘制矩形(无填充)
                ctx.strokeRect(20,20,40,40)
            }
        }
    }

     

绘制渐变

import QtQuick 2.12

Rectangle {
    width: 300
    height: 480
    Canvas{
        anchors.fill: parent
        onPaint: {
            var ctx = getContext('2d')

            //创建线性渐变(用在画布内容上)
            var gradient = ctx.createLinearGradient(100,0,100,200)

            //规定渐变对象中的颜色和停止位置
            gradient.addColorStop(0,'blue')
            gradient.addColorStop(0.5,'lightsteelblue')

            //设置或返回用于填充绘画的颜色、渐变或模式
            ctx.fillStyle = gradient

            //绘制“被填充”的矩形
            ctx.fillRect(50,50,100,100)
        }
    }
}

绘制阴影字体

import QtQuick 2.12

Rectangle {
    width: 300
    height: 480
    Canvas{
        //下面的canvas指代的是上面的id
        id: canvas
        anchors.fill: parent
        onPaint: {
            var ctx = getContext('2d')
            ctx.strokeStyle = '#333'
            ctx.fillRect(0,0,canvas.width,canvas.height)

            //设置或返回用于阴影的颜色
            ctx.shadowColor = 'blue'

            //设置或返回阴影距形状的水平距离
            ctx.shadowOffsetX = 2

            //设置或返回阴影距形状的垂直距离
            ctx.shadowOffsetY = 2

            //设置或返回用于阴影的模糊级别
            ctx.shadowBlur = 10

            //设置或返回文本内容的当前字体属性
            ctx.font = 'bold 120px Arial'
            ctx.fillStyle = '#33a9ff'

            //在画布上绘制“被填充的”文本
            ctx.fillText('地球',30, 180)

        }
    }
}

 

加载图片&裁剪

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    Canvas{
        id: canvas
        anchors.fill: parent

        onPaint: {
            var ctx = getContext('2d')
            ctx.drawImage('girl.jpg', 0,0,300, 200)

            //保存当前环境的状态
            ctx.save()

            ctx.strokeStyle = 'red'
            ctx.beginPath()
            ctx.moveTo(150,220)
            ctx.lineTo(0, 400)
            ctx.lineTo(300,400)

            //创建从当前点回到起始点的路径
            ctx.closePath()

            //	重新映射画布上的 (0,0) 位置
            ctx.translate(0,220)

            //从原始画布剪切任意形状和尺寸的区域
            ctx.clip()
            ctx.drawImage('girl.jpg',0, 0, 300,200)
            ctx.stroke()

            //返回之前保存过的路径状态和属性
            ctx.restore()
        }
        Component.onCompleted: {
            loadImage("girl.jpg")
        }
    }
}
  • 注意:绘制图片前一定要先加载图片

平移&旋转

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    Canvas{
        id: canvas
        anchors.fill: parent

        onPaint: {
            var ctx = getContext('2d')
            ctx.strokeStyle = 'blue'
            ctx.lineWidth = 4
            ctx.beginPath()
            ctx.translate(120,60)
            ctx.rect(-20,-20,40,40)

            ctx.stroke()

            ctx.strokeStyle = 'green'
            ctx.rotate(Math.PI/4)
            ctx.rect(-20,-20,40,40)
            ctx.stroke()
        }
    }
}

组合模式

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    Canvas{
        id: canvas
        anchors.fill: parent

        onPaint: {
            var ctx = getContext('2d')
            
            //设置组合模式
            ctx.globalCompositeOperation = 'xor'
            ctx.fillStyle = '#33a9ff'
            ctx.fillRect(0,0,100,100)
            ctx.fillRect(50,50,150,150)
        }
    }
}

像素缓冲

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
 id: window
    visible: true
 width: 640
    height: 480
    title: qsTr("Hello World")
    Canvas{
     id: canvas
        width: 320
     anchors.left: parent.left
        anchors.leftMargin: 0
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
        anchors.top: parent.top
        anchors.topMargin: 0
        onPaint: {
            var ctx = getContext('2d')
            var w = canvas.width
            var h = canvas.height
            var r = Math.random() * w / 10
            var x = Math.random() * w
            var y = Math.random() * h
            ctx.beginPath()
            ctx.arc(x,y,r,0,360)
            ctx.fill()
        }
        MouseArea{
            id: mouseArea
            anchors.fill: parent
            onClicked: {
                var url = canvas.toDataURL('image/png')
                image.source = url
            }
        }
    }
    Image{
        id: image
        width: 320
        anchors.right: parent.right
        anchors.rightMargin: 0
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
        anchors.top: parent.top
        anchors.topMargin: 0

    }
    Timer{
        id: timer
        interval: 1000
        running: true
        triggeredOnStart: false
        repeat: true

        //canvas重新绘制
        onTriggered: canvas.requestPaint()
    }
}

画布绘制

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: window
    visible: true
    width: 640
    height: 480

    Rectangle {
        id: root
        color: "#404040"
        border.color: "#000000"
        anchors.fill: parent
        property color paintColor: '#33B5E5'
        property string nState: 'Pen'
        Row {
            id: columnLayout
            anchors.top: parent.top
            anchors.topMargin: 8

            width: 500
            height: 100
            anchors.horizontalCenter: parent.horizontalCenter
            Repeater{
                model: ['#33B5E5','#99CC00','#FFBB33','#FF4444','#00000000']
                Rectangle{
                    id: rect
                    width: 100
                    height: 100
                    color: modelData
                    Rectangle{
                        id: borderRect
                        border.color: root.paintColor == parent.color ? "#000000" : "#00000000"
                        color: "#00000000"
                        border.width: 3
                        anchors.fill: parent
                    }

                    MouseArea{
                        id: clickArea
                        anchors.fill: parent
                        onClicked: {
                            root.paintColor = parent.color
                            if(root.paintColor == '#00000000'){
                                root.nState = 'Eraser'
                            }else{
                                root.nState = 'Pen'
                            }
                        }
                    }
                }
            }
        }


        Canvas{
            id: canvas
            anchors.top: columnLayout.bottom
            anchors.topMargin: 10
            anchors.right: parent.right
            anchors.rightMargin: 10
            anchors.left: parent.left
            anchors.leftMargin: 10
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 10
            property int lastX: 0
            property int lastY: 0
            onPaint: {
                var ctx = getContext('2d')
                ctx.lineWidth = 3
                ctx.lineCap = 'round'
                ctx.strokeStyle = root.paintColor
                if(ctx.strokeStyle === 'rgba(0, 0, 0, 0.0)'){
                    ctx.globalCompositeOperation = 'destination-out'
                    ctx.strokeStyle = '#000000'
                    ctx.lineWidth = 64
                    ctx.lineCap = 'square'
                }else{
                    ctx.globalCompositeOperation = 'source-over'
                }


                ctx.beginPath()
                ctx.moveTo(lastX, lastY)
                lastX = canvasMouseArea.mouseX
                lastY = canvasMouseArea.mouseY
                ctx.lineTo(lastX,lastY)

                //注意,这个不能调用closePath
//                ctx.closePath()
                ctx.stroke()
            }

            Rectangle{
                id: canvasBorder
                border.color: 'white'
                border.width: 3
                anchors.fill: parent
                color: '#00000000'
            }

            Image {
                id: cursor
                source: "eraser.png"
                width: 64
                height: 96
                visible: false
            }

            MouseArea{
                id: canvasMouseArea
                anchors.fill: parent
                onPressed: {
                    canvas.lastX = mouseX
                    canvas.lastY = mouseY
                    if(root.nState == 'Eraser'){
                        cursor.x = mouseX - 32
                        cursor.y = mouseY - 48
                        cursor.visible = true
                        canvasMouseArea.cursorShape = Qt.BlankCursor
                    }
                }
                onReleased: {
                    cursor.visible = false
                    canvasMouseArea.cursorShape = Qt.ArrowCursor
                }

                onPositionChanged: {
                    canvas.requestPaint()
                    cursor.x = mouseX - 32
                    cursor.y = mouseY - 48
                }
            }
        }
    }

}

上面的画笔要比官方给的例子更加复杂一些,增加了板擦的功能。

粒子模型

一个简单的模型

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Particles 2.12

Window {
    id: window
    visible: true
    width: 640
    height: 480
    Rectangle{
        anchors.fill: parent
        color: '#1f1f1f'
        ParticleSystem{
            id: particleSystem
        }

        Emitter{
            id: emitter
            anchors.centerIn: parent
            anchors.fill: parent
            system: particleSystem

            //每秒发射粒子数量
            emitRate: 10

            //每个粒子的生命周期
            lifeSpan: 1000

            //一个已发射的粒子生命周期变化
            lifeSpanVariation: 500

            //开始大小
            size: 30

            //结束大小
            endSize: 32
        }

        ImageParticle{
            source: 'girl.jpg'
            system: particleSystem

            //初始化颜色
            color: '#FFD700'

            //颜色变化范围
            colorVariation: 0.2

            //开始时旋转角度
            rotation: 0

            //开始时粒子角度范围为+-45度
            rotationVariation: 45

            //每个粒子以每秒15度旋转
            rotationVelocity: 15

            //每个粒子旋转的速度范围在+-15度
            rotationVelocityVariation: 15

            //粒子入场效果:缩放
            entryEffect: ImageParticle.scale
        }
    }
}

速度、加速度

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Particles 2.12
Window {
    id: window
    visible: true
    width: 640
    height: 480
    Rectangle{
        anchors.fill: parent
        color: '#1f1f1f'
        ParticleSystem{
            id: particleSystem
        }

        Emitter{
            id: emitter
            anchors.verticalCenter: parent.verticalCenter
            width: 1
            height: 1
            system: particleSystem
            emitRate: 10
            lifeSpan: 10000
            lifeSpanVariation: 500
            size: 30
            endSize: 32
            velocity: AngleDirection{
                //方向
                angle: 0

                //角度变化+-15度
                angleVariation: 15

                //每秒前进速度
                magnitude: 100

                //前进速度范围+-50
                magnitudeVariation: 50
            }
            //PointDirection:{x,y,xVariation,yVariation}
            //TargetDirection{targetX,targetY,targetVariation,magnitude}

            //发射器加速度设置
            acceleration: AngleDirection{
                angle: 90
                magnitude: 50
            }
        }

        ImageParticle{
            source: 'girl.jpg'
            system: particleSystem
            color: '#FFD700'
            colorVariation: 0.2
            rotation: 0
            rotationVariation: 45
            rotationVelocity: 15
            rotationVelocityVariation: 15
            entryEffect: ImageParticle.scale
        }
    }
}
  • 因为粒子在实际项目中使用很少,其它有关粒子的控制暂不介绍

着色器效果

OpenGL Shader Language

  • 着色器主要使用OpenGL Shader Language,将代码放到GPU上运算,最后达到渲染的效果。关于OpenGL Shader Language在后面进行详细的学习。

    例子

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Particles 2.12
    
    Window {
        id: window
        visible: true
        width: 640
        height: 480
        Row{
            anchors.centerIn: parent
            spacing: 20
            Image {
                id: image
                width: 180
                height: 240
                source: "g.jpg"
            }
            ShaderEffect{
                id: effect
                width: 180
                height: 240
                property variant source: image
            }
            ShaderEffect {
                width: 180
                height: 240
                property variant src: image
                vertexShader: "
                              uniform highp mat4 qt_Matrix;
                              attribute highp vec4 qt_Vertex;
                              attribute highp vec2 qt_MultiTexCoord0;
                              varying highp vec2 coord;
                              void main() {
                                  coord = qt_MultiTexCoord0;
                                  gl_Position = qt_Matrix * qt_Vertex;
                              }"
                fragmentShader: "
                              varying highp vec2 coord;
                              uniform sampler2D src;
                              uniform lowp float qt_Opacity;
                              void main() {
                                  lowp vec4 tex = texture2D(src, coord);
                                  gl_FragColor = vec4(vec3(dot(tex.rgb,
                                                      vec3(0.344, 0.5, 0.156))),
                                                           tex.a) * qt_Opacity;
                              }"
            }
        }
    }
    

     

Qt图像效果库

模糊效果

import QtQuick 2.12
import QtQuick.Window 2.12
import QtGraphicalEffects 1.0

Window {
    id: window
    visible: true
    width: 640
    height: 480

    Row {
        id: column
        spacing: 40
        anchors.fill: parent
        Image {
            id: image
            width: 300
            height: 400
            anchors.verticalCenter: parent.verticalCenter
            fillMode: Image.PreserveAspectFit
            source: "g.jpg"
        }

        FastBlur{
            width: 300
            height: 400
            source: image
            radius: 32
            cached: true
            transparentBorder: true
            anchors.verticalCenter: parent.verticalCenter

        }
    }
}

QML还有其它的效果

种类效果描述
混合(Blend)混合(Blend)使用混合模式合并两个资源项
颜色(Color)亮度与对比度(BrightnessContrast)调整亮度与对比度
 着色(Colorize)设置HSL颜色空间颜色
 颜色叠加(ColorOverlay)应用一个颜色层
 降低饱和度(Desaturate)减少颜色饱和度
 伽马调整(GammaAdjust)调整发光度
 色调饱和度(HueSaturation)调整HSL颜色空间颜色
 色阶调整(LevelAdjust)调整RGB颜色空间颜色
渐变(Gradient)圆锥渐变(ConicalGradient)绘制一个圆锥渐变
 线性渐变(LinearGradient)绘制一个线性渐变
 射线渐变(RadialGradient)绘制一个射线渐变
失真(Distortion)置换(Displace)按照指定的置换源移动源项的像素
阴影(Drop Shadow)阴影 (DropShadow)绘制一个阴影
 内阴影(InnerShadow)绘制一个内阴影
模糊 (Blur)快速模糊(FastBlur)应用一个快速模糊效果
 高斯模糊(GaussianBlur)应用一个高质量模糊效果
 蒙版模糊(MaskedBlur)应用一个多种强度的模糊效果
 递归模糊(RecursiveBlur)重复模糊,提供一个更强的模糊效果
运动模糊(Motion Blur)方向模糊(DirectionalBlur)应用一个方向的运动模糊效果
 放射模糊(RadialBlur)应用一个放射运动模糊效果
 变焦模糊(ZoomBlur)应用一个变焦运动模糊效果
发光(Glow)发光(Glow)绘制一个外发光效果
 矩形发光(RectangularGlow)绘制一个矩形外发光效果
蒙版(Mask)透明蒙版(OpacityMask)使用一个源项遮挡另一个源项
 阈值蒙版(ThresholdMask)使用一个阈值,一个源项遮挡另一个源项

 

多媒体

视频文件

import QtQuick 2.12
import QtQuick.Window 2.12
import QtMultimedia 5.12

Window {
    id: window
    visible: true
    width: 640
    height: 480

    Item {
        id: root
        anchors.fill: parent
        MediaPlayer{
            id: player
            source: 'writter.mp4'
        }

        VideoOutput{
            anchors.fill: parent
            source: player
        }

        Component.onCompleted: {
            player.play()
        }
    }
}

QT底层使用了DirectShow,需要安装基于DirectShow的解码器,例如LAV Filters,安装完成后才能播放。

硬件加速视频播放组件

  • 因为VideoOutput的渲染效率问题,因此需要重写此组件,下面是自定义组件TaoItem使用ffmplay对视频解码,然后使用Share渲染界面的粒子,该Demo有锁的问题,以及FileDialog加载上都存在一些问题,但是还是可以使用的,在今后可以逐步改进。

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QSurfaceFormat>
    #include "TaoItem.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
        app.setOrganizationName("Tao");
        app.setOrganizationDomain("Tao");
    
        auto format = QSurfaceFormat::defaultFormat();
        format.setProfile(QSurfaceFormat::CoreProfile);
    //    format.setOption(QSurfaceFormat::DebugContext);
        QSurfaceFormat::setDefaultFormat(format);
    
        qmlRegisterType<TaoItem>("TaoItem", 1, 0, "TaoItem");
    
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/Qml/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    

    TaoDecoder.h

    #pragma once
    
    #include <QOpenGLFunctions>
    #include <QQuickFramebufferObject>
    #include <QOpenGLTexture>
    #include <QOpenGLShader>
    #include <QOpenGLShaderProgram>
    #include <QOpenGLBuffer>
    #include <memory>
    
    #include "TaoDecoder.h"
    
    class TaoRenderer : public QOpenGLFunctions
    {
    public:
        TaoRenderer();
        ~TaoRenderer();
        void init();
        void paint();
        void resize(int width, int height);
        void updateTextureInfo(int width, int height, int format);
        void updateTextureData(const YUVData &data);
    protected:
        void initTexture();
        void initShader();
        void initGeometry();
    private:
    
        QOpenGLShaderProgram mProgram;
        QOpenGLTexture *mTexY = nullptr;
        QOpenGLTexture *mTexU = nullptr;
        QOpenGLTexture *mTexV = nullptr;
        QVector<QVector3D> mVertices;
        QVector<QVector2D> mTexcoords;
        int mModelMatHandle, mViewMatHandle, mProjectMatHandle;
        int mVerticesHandle;
        int mTexCoordHandle;
    
        QMatrix4x4 mModelMatrix;
        QMatrix4x4 mViewMatrix;
        QMatrix4x4 mProjectionMatrix;
        GLint mPixFmt = 0;
        bool mTextureAlloced = false;
    };
    

    TaoDecoder.cpp

    #include "TaoDecoder.h"
    #include <QDebug>
    /* Enable or disable frame reference counting. You are not supposed to support
     * both paths in your application but pick the one most appropriate to your
     * needs. Look for the use of refcount in this example to see what are the
     * differences of API usage between them. */
    static int gRefCount = 0;
    static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                         char *filename)
    {
        FILE *f;
        int i;
    
        f = fopen(filename,"w");
        fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
        for (i = 0; i < ysize; i++)
            fwrite(buf + i * wrap, 1, xsize, f);
        fclose(f);
    }
    static void outError(int num)
    {
        char errorStr[1024];
        av_strerror(num, errorStr, sizeof errorStr);
        qDebug() << "FFMPEG ERROR:" << QString(errorStr);
    }
    static int open_codec_context(int &streamIndex
                                  , AVCodecContext **decCtx
                                  , AVFormatContext *fmtCtx
                                  , enum AVMediaType type)
    {
        int ret;
        int index;
        AVStream *st;
        AVCodec *codec = nullptr;
        AVDictionary *opts = nullptr;
        ret = av_find_best_stream(fmtCtx, type, -1, -1, nullptr, 0);
        if (ret < 0)
        {
            qWarning() << "Could not find stream " << av_get_media_type_string(type);
            return ret;
        }
        index = ret;
        st = fmtCtx->streams[index];
        codec = avcodec_find_decoder(st->codecpar->codec_id);
        if (!codec)
        {
            qWarning() << "Cound not find codec " << av_get_media_type_string(type);
            return AVERROR(EINVAL);
        }
        *decCtx = avcodec_alloc_context3(codec);
        if (!*decCtx)
        {
            qWarning() << "Failed to allocate codec context " << av_get_media_type_string(type);
            return AVERROR(ENOMEM);
        }
        ret = avcodec_parameters_to_context(*decCtx, st->codecpar);
        if (ret < 0)
        {
            qWarning() << "Failed to copy codec parameters to decoder context" << av_get_media_type_string(type);
            return ret;
        }
        av_dict_set(&opts, "refcounted_frames", gRefCount ? "1" : "0", 0);
    
        ret = avcodec_open2(*decCtx, codec, &opts);
        if (ret < 0)
        {
            qWarning() << "Failed to open codec " << av_get_media_type_string(type);
            return ret;
        }
        streamIndex = index;
        return 0;
    }
    void TaoDecoder::init()
    {
        continueRun = true;
        avformat_network_init();
    }
    
    void TaoDecoder::uninit()
    {
        continueRun = false;
        avformat_network_deinit();
    }
    
    void TaoDecoder::load(const QString &file)
    {
        int ret = 0;
        ret = avformat_open_input(&m_fmtCtx, file.toStdString().data(), nullptr, nullptr);
        if ( 0 > ret)
        {
            qWarning() << "open url error";
            outError(ret);
            return;
        }
        ret = avformat_find_stream_info(m_fmtCtx, nullptr);
        if (0 > ret)
        {
            qWarning() << "find stream failed";
            outError(ret);
            return;
        }
        ret = open_codec_context(m_videoStreamIndex, &m_videoCodecCtx, m_fmtCtx, AVMEDIA_TYPE_VIDEO);
        if (ret < 0)
        {
            qWarning() << "open_codec_context failed";
            return;
        }
        m_videoStream = m_fmtCtx->streams[m_videoStreamIndex];
        m_width = m_videoCodecCtx->width;
        m_height = m_videoCodecCtx->height;
        m_pixFmt = m_videoCodecCtx->pix_fmt;
    
        emit videoInfoReady(m_width, m_height, m_pixFmt);
    
        av_dump_format(m_fmtCtx, 0, file.toStdString().data(), 0);
        do {
            if (!m_videoStream)
            {
                qWarning() << "Could not find audio or video stream in the input, aborting";
                break;
            }
            m_frame = av_frame_alloc();
            if (!m_frame)
            {
                qWarning() << "Could not allocate frame";
                break;
            }
            demuxing();
        }while(0);
        avcodec_free_context(&m_videoCodecCtx);
        avformat_close_input(&m_fmtCtx);
        av_frame_free(&m_frame);
    
    }
    
    void TaoDecoder::suspendDecode()
    {
        qDebug()<<"暂停";
        continueRun = false;
    }
    
    void TaoDecoder::continueDecode()
    {
        qDebug()<<"继续";
        continueRun = true;
        m_condition.wakeAll();
    }
    
    void TaoDecoder::demuxing()
    {
        av_init_packet(&m_packet);
        m_packet.data = nullptr;
        m_packet.size = 0;
        while(av_read_frame(m_fmtCtx, &m_packet) >= 0)
        {
            if (m_packet.stream_index == m_videoStreamIndex)
            {
                if (avcodec_send_packet(m_videoCodecCtx, &m_packet) == 0)
                {
                    while (avcodec_receive_frame(m_videoCodecCtx, m_frame) == 0)
                    {
                        decodeFrame();
                    }
                }
            }
        }
        if (avcodec_send_packet(m_videoCodecCtx, nullptr) == 0)
        {
            while (avcodec_receive_frame(m_videoCodecCtx, m_frame) == 0)
            {
                decodeFrame();
            }
        }
    }
    
    void TaoDecoder::decodeFrame()
    {
        try {
            //
            m_mutex.lock();
            if(continueRun == false){
                m_condition.wait(&m_mutex);
            }
            m_mutex.unlock();
    
            switch (m_frame->format) {
                case AV_PIX_FMT_YUV420P:
                {
                    m_yuvData.Y.resize(m_frame->linesize[0] * m_frame->height);
                    memcpy_s(m_yuvData.Y.data(), static_cast<size_t>(m_yuvData.Y.size()), m_frame->data[0], static_cast<size_t>(m_yuvData.Y.size()));
                    m_yuvData.U.resize(m_frame->linesize[1] * m_frame->height / 2);
                    memcpy_s(m_yuvData.U.data(), static_cast<size_t>(m_yuvData.U.size()), m_frame->data[1], static_cast<size_t>(m_yuvData.U.size()));
                    m_yuvData.V.resize(m_frame->linesize[2] * m_frame->height / 2);
                    memcpy_s(m_yuvData.V.data(), static_cast<size_t>(m_yuvData.V.size()), m_frame->data[2], static_cast<size_t>(m_yuvData.V.size()));
                    m_yuvData.yLineSize = m_frame->linesize[0];
                    m_yuvData.uLineSize = m_frame->linesize[1];
                    m_yuvData.vLineSize = m_frame->linesize[2];
                    m_yuvData.height = m_frame->height;
                    emit videoFrameDataReady(m_yuvData);
                    break;
                }
                default:
                    break;
    
            }
        } catch (std::exception& e) {
            //帧率太高可能会在这里出现内存分配不足
            qDebug()<<e.what();
        }
    
    }
    
    //--------------------------------------------------------------------------------//
    TaoDecoderController::TaoDecoderController(QObject *parent) : QObject(parent)
    {
        m_videoDataCache.setCapacity(maxCache);
    
        m_decoder = new TaoDecoder;
        m_decoder->moveToThread(&m_thread);
        connect(&m_thread, &QThread::finished, m_decoder, &TaoDecoder::deleteLater);
        connect(this, &TaoDecoderController::init, m_decoder, &TaoDecoder::init);
        connect(this, &TaoDecoderController::uninit, m_decoder, &TaoDecoder::uninit);
        connect(this, &TaoDecoderController::load, m_decoder, &TaoDecoder::load);
    
        qRegisterMetaType<YUVData>();
        connect(m_decoder, &TaoDecoder::videoInfoReady, this, &TaoDecoderController::videoInfoReady);
        connect(m_decoder, &TaoDecoder::videoFrameDataReady, this, &TaoDecoderController::onVideoFrameDataReady);
    
        m_thread.start();
        emit init();
    }
    
    TaoDecoderController::~TaoDecoderController()
    {
        if (m_thread.isRunning())
        {
            emit uninit();
            m_thread.quit();
            m_thread.wait();
        }
    }
    
    YUVData TaoDecoderController::getFrame(bool &got)
    {
        if (m_videoDataCache.isEmpty())
        {
            got = false;
            return {};
        }
        got = true;
        if(m_videoDataCache.size() <= minCache){
            m_decoder->continueDecode();
        }
        return m_videoDataCache.takeFirst();
    }
    
    void TaoDecoderController::onVideoFrameDataReady(YUVData data)
    {
        m_videoDataCache.append(data);
        if(m_videoDataCache.size() >= maxCache){
             m_decoder->suspendDecode();
        }
    }

    TaoRenderer.h

    #pragma once
    
    #include <QOpenGLFunctions>
    #include <QQuickFramebufferObject>
    #include <QOpenGLTexture>
    #include <QOpenGLShader>
    #include <QOpenGLShaderProgram>
    #include <QOpenGLBuffer>
    #include <memory>
    
    #include "TaoDecoder.h"
    
    class TaoRenderer : public QOpenGLFunctions
    {
    public:
        TaoRenderer();
        ~TaoRenderer();
        void init();
        void paint();
        void resize(int width, int height);
        void updateTextureInfo(int width, int height, int format);
        void updateTextureData(const YUVData &data);
    protected:
        void initTexture();
        void initShader();
        void initGeometry();
    private:
    
        QOpenGLShaderProgram mProgram;
        QOpenGLTexture *mTexY = nullptr;
        QOpenGLTexture *mTexU = nullptr;
        QOpenGLTexture *mTexV = nullptr;
        QVector<QVector3D> mVertices;
        QVector<QVector2D> mTexcoords;
        int mModelMatHandle, mViewMatHandle, mProjectMatHandle;
        int mVerticesHandle;
        int mTexCoordHandle;
    
        QMatrix4x4 mModelMatrix;
        QMatrix4x4 mViewMatrix;
        QMatrix4x4 mProjectionMatrix;
        GLint mPixFmt = 0;
        bool mTextureAlloced = false;
    };

    TaoRenderer.cpp

    #include "TaoRenderer.h"
    
    #include <QOpenGLPixelTransferOptions>
    static void safeDeleteTexture(QOpenGLTexture *texture)
    {
        if (texture)
        {
            if (texture->isBound())
            {
                texture->release();
            }
            if (texture->isCreated())
            {
                texture->destroy();
            }
            delete texture;
            texture = nullptr;
        }
    }
    
    TaoRenderer::TaoRenderer()
    {
    }
    
    TaoRenderer::~TaoRenderer()
    {
        safeDeleteTexture(mTexY);
        safeDeleteTexture(mTexU);
        safeDeleteTexture(mTexV);
    }
    
    void TaoRenderer::init()
    {
        initializeOpenGLFunctions();
        glDepthMask(GL_TRUE);
        glEnable(GL_TEXTURE_2D);
        initShader();
        initTexture();
        initGeometry();
    }
    void TaoRenderer::resize(int width, int height)
    {
        glViewport(0, 0, width, height);
        float bottom = -1.0f;
        float top = 1.0f;
        float n = 1.0f;
        float f = 100.0f;
        mProjectionMatrix.setToIdentity();
        mProjectionMatrix.frustum(-1.0, 1.0, bottom, top, n, f);
    }
    void TaoRenderer::initShader()
    {
        if (!mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/Shaders/vertex.vsh"))
        {
            qWarning() << " add vertex shader file failed.";
            return;
        }
        if (!mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/Shaders/fragment.fsh"))
        {
            qWarning() << " add fragment shader file failed.";
            return;
        }
        mProgram.bindAttributeLocation("qt_Vertex", 0);
        mProgram.bindAttributeLocation("texCoord", 1);
        mProgram.link();
        mProgram.bind();
    }
    void TaoRenderer::initTexture()
    {
        // yuv420p
        mTexY = new QOpenGLTexture(QOpenGLTexture::Target2D);
        mTexY->setFormat(QOpenGLTexture::LuminanceFormat);
        //    mTexY->setFixedSamplePositions(false);
        mTexY->setMinificationFilter(QOpenGLTexture::Nearest);
        mTexY->setMagnificationFilter(QOpenGLTexture::Nearest);
        mTexY->setWrapMode(QOpenGLTexture::ClampToEdge);
    
        mTexU = new QOpenGLTexture(QOpenGLTexture::Target2D);
        mTexU->setFormat(QOpenGLTexture::LuminanceFormat);
        //    mTexU->setFixedSamplePositions(false);
        mTexU->setMinificationFilter(QOpenGLTexture::Nearest);
        mTexU->setMagnificationFilter(QOpenGLTexture::Nearest);
        mTexU->setWrapMode(QOpenGLTexture::ClampToEdge);
    
        mTexV = new QOpenGLTexture(QOpenGLTexture::Target2D);
        mTexV->setFormat(QOpenGLTexture::LuminanceFormat);
        //    mTexV->setFixedSamplePositions(false);
        mTexV->setMinificationFilter(QOpenGLTexture::Nearest);
        mTexV->setMagnificationFilter(QOpenGLTexture::Nearest);
        mTexV->setWrapMode(QOpenGLTexture::ClampToEdge);
    }
    
    void TaoRenderer::initGeometry()
    {
        mVertices << QVector3D(-1, 1, 0.0f)
                  << QVector3D(1, 1, 0.0f)
                  << QVector3D(1, -1, 0.0f)
                  << QVector3D(-1, -1, 0.0f);
        mTexcoords<< QVector2D(0, 1)
                   << QVector2D(1, 1)
                   << QVector2D(1, 0)
                   << QVector2D(0, 0);
    
        mViewMatrix.setToIdentity();
        mViewMatrix.lookAt(QVector3D(0.0f, 0.0f, 1.001f), QVector3D(0.0f, 0.0f, -5.0f), QVector3D(0.0f, 1.0f, 0.0f));
        mModelMatrix.setToIdentity();
    }
    void TaoRenderer::updateTextureInfo(int width, int height, int format)
    {
        if (format == AV_PIX_FMT_YUV420P)
        {
            // yuv420p
            mTexY->setSize(width, height);
            mTexY->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
    
            mTexU->setSize(width / 2, height / 2);
            mTexU->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
    
            mTexV->setSize(width / 2, height / 2);
            mTexV->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
        }
        else
        {
            // 先按yuv444p处理
            mTexY->setSize(width, height);
            mTexY->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
    
            mTexU->setSize(width, height);
            mTexU->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
    
            mTexV->setSize(width, height);
            mTexV->allocateStorage(QOpenGLTexture::Red, QOpenGLTexture::UInt8);
        }
        mTextureAlloced = true;
    }
    
    void TaoRenderer::updateTextureData(const YUVData &data)
    {
        try {
            if (data.Y.size() <= 0)
            {
                qWarning() << "y array is empty";
                return;
            }
            if (data.U.size() <= 0)
            {
                qWarning() << "u array is empty";
                return;
            }
            if (data.V.size() <= 0)
            {
                qWarning() << "v array is empty";
                return;
            }
            QOpenGLPixelTransferOptions options;
            options.setImageHeight(data.height);
            options.setRowLength(data.yLineSize);
            mTexY->setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, data.Y.data(), &options);
            options.setRowLength(data.uLineSize);
            mTexU->setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, data.U.data(), &options);
            options.setRowLength(data.vLineSize);
            mTexV->setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, data.V.data(), &options);
        } catch (...) {
    
        }
    
    }
    void TaoRenderer::paint()
    {
        glDepthMask(true);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        if (!mTextureAlloced)
        {
            return;
        }
        mProgram.bind();
    
        mModelMatHandle = mProgram.uniformLocation("u_modelMatrix");
        mViewMatHandle = mProgram.uniformLocation("u_viewMatrix");
        mProjectMatHandle = mProgram.uniformLocation("u_projectMatrix");
        mVerticesHandle = mProgram.attributeLocation("qt_Vertex");
        mTexCoordHandle = mProgram.attributeLocation("texCoord");
        //顶点
        mProgram.enableAttributeArray(mVerticesHandle);
        mProgram.setAttributeArray(mVerticesHandle, mVertices.constData());
    
        //纹理坐标
        mProgram.enableAttributeArray(mTexCoordHandle);
        mProgram.setAttributeArray(mTexCoordHandle, mTexcoords.constData());
    
        // MVP矩阵
        mProgram.setUniformValue(mModelMatHandle, mModelMatrix);
        mProgram.setUniformValue(mViewMatHandle, mViewMatrix);
        mProgram.setUniformValue(mProjectMatHandle, mProjectionMatrix);
    
        // pixFmt
        mProgram.setUniformValue("pixFmt", mPixFmt);
    
        //纹理
        // Y
        glActiveTexture(GL_TEXTURE0);
        mTexY->bind();
    
        // U
        glActiveTexture(GL_TEXTURE1);
        mTexU->bind();
    
        // V
        glActiveTexture(GL_TEXTURE2);
        mTexV->bind();
    
        mProgram.setUniformValue("tex_y", 0);
        mProgram.setUniformValue("tex_u", 1);
        mProgram.setUniformValue("tex_v", 2);
    
        glDrawArrays(GL_TRIANGLE_FAN, 0, mVertices.size());
    
        mProgram.disableAttributeArray(mVerticesHandle);
        mProgram.disableAttributeArray(mTexCoordHandle);
        mProgram.release();
    }
    

    TaoItem.h

    #pragma once
    
    #include <QQuickItem>
    #include <QQuickFramebufferObject>
    #include "TaoDecoder.h"
    class TaoItem : public QQuickFramebufferObject
    {
        Q_OBJECT
    public:
        TaoItem(QQuickItem *parent = nullptr);
        void timerEvent(QTimerEvent *event) override;
    
        YUVData getFrame(bool &got);
        bool infoDirty() const
        {
            return m_infoChanged;
        }
        void makeInfoDirty(bool dirty)
        {
            m_infoChanged = dirty;
        }
        int videoWidth() const
        {
            return m_videoWidth;
        }
        int videoHeght() const
        {
            return m_videoHeight;
        }
        int videoFormat() const
        {
            return m_videoFormat;
        }
    public slots:
        void loadFile(const QUrl &url);
    
    protected slots:
        void onVideoInfoReady(int width, int height, int format);
    public:
        Renderer *createRenderer() const override;
        std::unique_ptr<TaoDecoderController> m_decoderController = nullptr;
        int m_videoWidth;
        int m_videoHeight;
        int m_videoFormat;
        bool m_infoChanged = false;
    };

    TaoItem.cpp

    #include "TaoItem.h"
    #include "TaoRenderer.h"
    #include <QOpenGLFramebufferObject>
    #include <QQuickWindow>
    //************TaoItemRender************//
    class TaoItemRender : public QQuickFramebufferObject::Renderer
    {
    public:
        TaoItemRender();
    
        void render() override;
        QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;
        void synchronize(QQuickFramebufferObject *) override;
    private:
        TaoRenderer m_render;
        QQuickWindow *m_window = nullptr;
    };
    
    TaoItemRender::TaoItemRender()
    {
        m_render.init();
    }
    
    void TaoItemRender::render()
    {
        m_render.paint();
        m_window->resetOpenGLState();
    }
    
    QOpenGLFramebufferObject *TaoItemRender::createFramebufferObject(const QSize &size)
    {
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
        format.setSamples(4);
        m_render.resize(size.width(), size.height());
        return new QOpenGLFramebufferObject(size, format);
    }
    
    void TaoItemRender::synchronize(QQuickFramebufferObject *item)
    {
        TaoItem *pItem = qobject_cast<TaoItem *>(item);
        if (pItem)
        {
            if (!m_window)
            {
                m_window = pItem->window();
            }
            if (pItem->infoDirty())
            {
                m_render.updateTextureInfo(pItem->videoWidth(), pItem->videoHeght(), pItem->videoFormat());
                pItem->makeInfoDirty(false);
            }
            bool got = false;
            YUVData data = pItem->getFrame(got);
            if (got)
            {
                m_render.updateTextureData(data);
            }
        }
    }
    
    //************TaoItem************//
    TaoItem::TaoItem(QQuickItem *parent) : QQuickFramebufferObject (parent)
    {
        m_decoderController = std::unique_ptr<TaoDecoderController>(new TaoDecoderController());
        connect(m_decoderController.get(), &TaoDecoderController::videoInfoReady, this, &TaoItem::onVideoInfoReady);
        //按每秒60帧的帧率更新界面
        startTimer(1000 / 25);
    }
    
    void TaoItem::timerEvent(QTimerEvent *event)
    {
        Q_UNUSED(event);
        update();
    }
    
    YUVData TaoItem::getFrame(bool &got)
    {
        return m_decoderController->getFrame(got);
    }
    
    void TaoItem::loadFile(const QUrl &url)
    {
        m_decoderController->load("D:\\Documents\\project\\QtQuickStudy\\writter.mp4");
    //    m_decoderController->load(url.toLocalFile());
    }
    
    void TaoItem::onVideoInfoReady(int width, int height, int format)
    {
        if (m_videoWidth != width)
        {
            m_videoWidth = width;
            makeInfoDirty(true);
        }
        if (m_videoHeight != height)
        {
            m_videoHeight = height;
            makeInfoDirty(true);
        }
        if (m_videoFormat != format)
        {
            m_videoFormat = format;
            makeInfoDirty(true);
        }
    }
    
    QQuickFramebufferObject::Renderer *TaoItem::createRenderer() const
    {
        return new TaoItemRender;
    }
    

    vertex.vsh

    attribute highp vec3 qt_Vertex;
    attribute highp vec2 texCoord;
    
    uniform mat4 u_modelMatrix;
    uniform mat4 u_viewMatrix;
    uniform mat4 u_projectMatrix;
    
    varying vec2 v_texCoord;
    void main(void)
    {
        gl_Position = u_projectMatrix * u_viewMatrix * u_modelMatrix * vec4(qt_Vertex, 1.0f);
        v_texCoord = texCoord;
    }
    

    fragment.fsh

    varying vec2 v_texCoord;
    
    uniform sampler2D tex_y;
    uniform sampler2D tex_u;
    uniform sampler2D tex_v;
    uniform int pixFmt;
    void main(void)
    {
        vec3 yuv;
        vec3 rgb;
        if (pixFmt == 0) {
            yuv.x = texture2D(tex_y, v_texCoord).r;
            yuv.y = texture2D(tex_u, v_texCoord).r - 0.5;
            yuv.z = texture2D(tex_v, v_texCoord).r - 0.5;
            rgb = mat3( 1,       1,         1,
                            0,       -0.3455,  1.779,
                            1.4075, -0.7169,  0) * yuv;
        } else {
    //        yuv.x = texture2D(tex_y, v_texCoord).r;
    //        yuv.y = texture2D(tex_u, v_texCoord).r - 0.5;
    //        yuv.z = texture2D(tex_v, v_texCoord).r - 0.5;
    //        rgb.x = clamp( yuv.x + 1.402 *yuv.z, 0, 1);
    //        rgb.y = clamp( yuv.x - 0.34414 * yuv.y - 0.71414 * yuv.z, 0, 1);
    //        rgb.z = clamp( yuv.x + 1.772 * yuv.y, 0, 1);
        }
        gl_FragColor = vec4(rgb, 1);
    }
    

    main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    //import QtQuick.Dialogs 1.2
    import QtQuick.Controls 2.12
    import TaoItem 1.0
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
        menuBar: MenuBar {
            Menu {
                title: "File"
                Action {
                    text: qsTr("&Open...")
                    onTriggered: {
                        taoItem.loadFile('D:\\Documents\\project\\QtQuickStudy\\mv.mp4')
    //                    openFileDialog.open()
                    }
                }
            }
    
        }
        TaoItem {
            id: taoItem
            anchors.fill: parent
        }
    //    FileDialog {
    //        id: openFileDialog
    //        title: "Please choose a Video file"
    
    //        nameFilters: [ "MP4 files (*.mp4)", "YUV files(*.yuv)" ]
    //        onAccepted: {
    //            taoItem.loadFile(fileUrl)
    //        }
    //    }
        footer: ToolBar {
            Row{
                anchors.fill: parent
                ToolButton {
                    text: "<"
    
                }
            }
        }
    }
    

     

  • 通过上面的自定义控件,可以让视频在播放时的CPU基本保持在2~5%左右,而使用Videooutput基本在30%以上。

网络

加载远程QML组件

  1. 使用Python创建一个简单服务器:python -m http.server

  2. 在本地创建可以加载远程组件的Loader对象

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    Loader{
        id:root
        source: 'http://localhost:8000/remote.qml'
        onLoaded: {
            root.width = item.width
            root.height = item.height
        }
    }

     

  3. 使用qmlscreen加载远程QML组件

处理网络请求

import QtQuick 2.0
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
    width: 800
    height: 600


    Button{
        text: '访问百度'
        anchors.fill: parent
        onClicked: {
            var xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function(){
                if(xhr.readyState === XMLHttpRequest.HRADERS_RECEIVED){
                    print('HRADERS_RECEIVED')
                }else if(xhr.readyState === XMLHttpRequest.DONE){
                    print(xhr.responseText.toString())
                }
            }
            xhr.open('GET','http://www.baidu.com')
            xhr.send()
        }
    }
}
  • 使用 XMLHttpRequest 也可以访问本地文件,使用xhr.open('GET','text.json')可以读取JSON格式的文件。

  • 可以使用XmlListModel直接映射本地/网络的XML文件。

REST接口

  • 这里使用QML访问HTML请求。

    1. 创建服务:安装python curl pip install flask

    2. 编写服务端程序

      #!/usr/bin/env python
      
      from flask import Flask, jsonify, request
      import json
      
      colors = json.load(open('colors.json', 'r'))
      
      app = Flask(__name__)
      
      @app.route('/colors', methods=['GET'])
      def get_colors():
          return jsonify({"data": colors})
      
      @app.route('/colors/<name>', methods=['GET'])
      def get_color(name):
          for color in colors:
              if color["name"] == name:
                  return jsonify(color)
          return jsonify({'error': True})
      
      
      @app.route('/colors', methods=['POST'])
      def create_color():
          print('create color')
          color = {
              'name': request.json['name'],
              'value': request.json['value']
          }
          colors.append(color)
          return jsonify(color), 201
      
      
      @app.route('/colors/<name>', methods=['PUT'])
      def update_color(name):
          success = False
          for color in colors:
              if color["name"] == name:
                  color['value'] = request.json.get('value', color['value'])
                  return jsonify(color)
          return jsonify({'error': True})
      
      @app.route('/colors/<name>', methods=['DELETE'])
      def delete_color(name):
          success = False
          for color in colors:
              if color["name"] == name:
                  colors.remove(color)
                  return jsonify(color)
          return jsonify({'error': True})
      
      if __name__ == '__main__':
          app.run(debug=True)
      

       

    3. 使用curl访问服务:

      1. 读取请求:
      curl -i -GET http://localhost:5000/colors
      2. 读取条目
      curl -i -GET http://localhost:5000/colors/red
      3. 创建条目
      curl -i -H "Content-Type: application/json" -X POST -d {\"name\":\"gray1\",\"value\":\"#333\"} http://localhost:5000/colors
      4. 更新条目
      curl -i -H "Content-Type: application/json" -X PUT -d {\"value\":\"#666\"} http://localhost:5000/colors/red
      5. 删除条目
      curl -i -X DELETE http://localhost:5000/colors/red

       

    4. 创建和服务端实时通信的列表

      import QtQuick 2.0
      import QtQuick.Window 2.12
      import QtQuick.Controls 2.12
      import 'colorservice.js' as Service
      
      Window {
          id: window
          width: 800
          height: 600
          property variant colorList: "none.none"
          title: 'REST客户端'
          property int colorIndex: 0
          ListModel{
              id: listColor
          }
      
          Column{
              id: column
              anchors.bottom: buttonRow.top
              anchors.bottomMargin: 20
              anchors.top: parent.top
              anchors.topMargin: 20
              anchors.right: parent.right
              anchors.rightMargin: 20
              anchors.left: parent.left
              anchors.leftMargin: 20
              Repeater{
                  model: listColor
                  Item {
                      height: 30
                      anchors.right: parent.right
                      anchors.rightMargin: 0
                      anchors.left: parent.left
                      anchors.leftMargin: 0
      
                      Rectangle {
                          id: rectColor
                          width: 26
                          height: 26
                          color: model.value
                          anchors.left: parent.left
                          anchors.leftMargin: 2
                          anchors.top: parent.top
                          anchors.topMargin: 2
                      }
      
                      Text {
                          id: element
                          height: 26
                          text: model.name
                          anchors.verticalCenter: parent.verticalCenter
                          anchors.left: rectColor.right
                          anchors.leftMargin: 2
                          anchors.top: parent.top
                          anchors.topMargin: 2
                          font.pixelSize: 26
                      }
                  }
      
              }
      
      
          }
      
          Row{
              id: buttonRow
      
              width: 600
              height: 120
              anchors.bottom: parent.bottom
              anchors.bottomMargin: 30
              anchors.horizontalCenter: parent.horizontalCenter
              Button{
                  width: 120
                  height: 120
                  text: '获取颜色链表'
                  onClicked: {
                      Service.get_colors( function(resp) {
                          print('获取颜色链表: ' + JSON.stringify(resp));
                          listColor.clear()
                          window.colorIndex = 0
                          var entry = resp.data
                          for(let i =0 ;i< entry.length;++i){
                              listColor.append(entry[i])
                              window.colorIndex += 1
                          }
                      });
                  }
              }
              Button{
                  width: 120
                  height: 120
                  text: '创建颜色'
                  onClicked: {
                      window.colorIndex += 1
                      var entry = {
                          name: 'color-' + window.colorIndex,
                          value: Qt.hsla(Math.random(),0.5,0.5,1.0).toString()
                      }
                      Service.create_color(entry, function(resp){
                          print('创建颜色: '+ JSON.stringify(resp))
                          listColor.append(resp)
                      })
                  }
              }
              Button{
                  width: 120
                  height: 120
                  text: '读取最后的颜色'
                  onClicked: {
                      var color_name = listColor.get(window.colorIndex - 1).name
                      Service.get_color(color_name, function(resp){
                          print('读取最后的颜色 : ' + JSON.stringify(resp))
                          listColor.setProperty(window.colorIndex - 1,'value',resp.value)
                      })
                  }
              }
              Button{
                  width: 120
                  height: 120
                  text: '更新最后的颜色'
                  onClicked: {
                      var color_name = listColor.get(window.colorIndex - 1).name
                      var entry = {
                          name: color_name,
                          value: Qt.hsla(Math.random(),0.5,0.5,1.0).toString()
                      }
                      print(JSON.stringify(entry))
                      Service.update_color(color_name, entry, function(resp){
                          print('更新最后的颜色 : ' + JSON.stringify(resp))
                          listColor.setProperty(window.colorIndex - 1,'value',resp.value)
                      })
                  }
              }
              Button{
                  width: 120
                  height: 120
                  text: '删除最后的颜色'
                  onClicked: {
                      var color_name = 'color-' + window.colorIndex
                      Service.delete_color(color_name, function(resp){
                          print('删除最后的颜色 : ' + JSON.stringify(resp))
                          window.colorIndex -= 1
                          listColor.remove(window.colorIndex, 1)
                      })
                  }
              }
          }
      
      
      }

       

使用WebSocket

import QtQuick 2.0
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtWebSockets 1.1

Text {
    width: 480
    height: 48
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter

    WebSocket{
        id: socket
        url: 'ws://echo.websocket.org'
        active: true
        onTextMessageReceived: {
            text = message
        }
        onStatusChanged: {
            if(socket.status == WebSocket.Error){
                console.log('Error: ' + socket.errorString)
            }else if(socket.status == WebSocket.Open){
                socket.sendTextMessage('ping')
            }else if(socket.status == WebSocket.Closed){
                text += '\nSocket closed'
            }
        }
    }
}

websocket服务器采用类似应答的方式,在收到客户端发来的请求后,可以返回应答内容。可以使用node的ws模块模拟websocket服务。

存储

Settings

import QtQuick 2.0
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import Qt.labs.settings 1.1
Window{
    id: window
    width: 640
    height: 480
    Rectangle{
        id: rectangle
        anchors.fill: parent
        color: '#000000'
        Settings{
            id: settings
            property alias color: rectangle.color
            fileName: 'quickstudy.ini'
        }
        MouseArea{
            anchors.fill: parent
            onClicked: {
                rectangle.color = Qt.hsla(Math.random(),0.5,0.5,1.0).toString()
            }
        }
    }
}

Settings默认保存在注册表中,如果指定配置文件,则保存在注册文件中。

LocalStorage

import QtQuick 2.0
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.LocalStorage 2.12

Window{
    id: window
    width: 640
    height: 480

    property var db

    function initDatabase(){
        print('initDatabase()')
        db = LocalStorage.openDatabaseSync("CrazyBox", "1.0", "A box who remembers its position", 100000);
        db.transaction( function(tx) {
            print('... create table')
            tx.executeSql('CREATE TABLE IF NOT EXISTS data(name TEXT, value TEXT)');
        });
    }

    function storeData(){
        print('storeData()')
        if (!db) { return; }
        db.transaction( function(tx) {
            print('... check if a crazy object exists')
            var result = tx.executeSql('SELECT * from data where name = "crazy"');
            // 创建一个包含需要保存的数据的对象,之后需要将这个对象转换成 JSON
            var obj = { x: crazy.x, y: crazy.y };
            if (result.rows.length === 1) { // 已有数据,更新
                print('... crazy exists, update it')
                result = tx.executeSql('UPDATE data set value=? where name="crazy"', [JSON.stringify(obj)]);
            } else { // 没有数据,插入
                print('... crazy does not exists, create it')
                result = tx.executeSql('INSERT INTO data VALUES (?,?)', ['crazy', JSON.stringify(obj)]);
            }
        });

    }

    function readData(){
        print('readData()')
        if (!db) { return; }
        db.transaction( function(tx) {
            print('... read crazy object')
            var result = tx.executeSql('select * from data where name="crazy"');
            if (result.rows.length === 1) {
                print('... update crazy geometry')
                // 读取数据
                var value = result.rows[0].value;
                // 转换成 JS 对象
                var obj = JSON.parse(value)
                // 将数据应用到矩形对象
                crazy.x = obj.x;
                crazy.y = obj.y;
            }
        });
    }

    Component.onCompleted: {
        initDatabase()
        readData()
    }

    Component.onDestruction: {
        storeData()
    }

    Rectangle{
        id: crazy
        objectName: 'crazy'
        width: 100
        height: 100
        color: '#53d769'
        border.color: Qt.lighter(color, 1.1)
        Text {
            anchors.fill: parent
            text: Math.round(parent.x) + '/' + Math.round(parent.y)
        }
        MouseArea{
            anchors.fill: parent
            drag.target: parent
        }
    }
}

 

动态QML

动态加载组件&动态绑定

import QtQuick 2.0

Rectangle {
    id: root
    
    width: 600
    height: 400
    
    property int speed: 0
    
    SequentialAnimation {
        running: true
        loops: Animation.Infinite
        
        NumberAnimation { target: root; property: "speed"; to: 145; easing.type: Easing.InOutQuad; duration: 4000; }
        NumberAnimation { target: root; property: "speed"; to: 10; easing.type: Easing.InOutQuad; duration: 2000; }
    }
    // M1>>
    Loader {
        id: dialLoader
        
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: analogButton.top

        onLoaded: {
            binder.target = dialLoader.item;
        }
    }
    Binding {
        id: binder
        
        property: "speed"
        value: speed
    }
    // <<M1
    Rectangle {
        id: analogButton
        
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        
        color: "gray"
        
        width: parent.width/2
        height: 100
        
        Text {
            anchors.centerIn: parent
            text: "Analog"
        }
        
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "analog";
        }
    }
    
    Rectangle {
        id: digitalButton
        
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        
        color: "gray"
        
        width: parent.width/2
        height: 100
        
        Text {
            anchors.centerIn: parent
            text: "Digital"
        }
        
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "digital";
        }
    }
    
    state: "analog"
    
    // M3>>
    states: [
        State {
            name: "analog"
            PropertyChanges { target: analogButton; color: "green"; }
            PropertyChanges { target: dialLoader; source: "Analog.qml"; }
        },
        State {
            name: "digital"
            PropertyChanges { target: digitalButton; color: "green"; }
            PropertyChanges { target: dialLoader; source: "Digital.qml"; }
        }
    ]
    // <<M3
}
  1. 该例子不难看出,使用Loader指定source可以直接动态加载一个组件。

  2. 通过Bind,可以将一个内部组件的属性值绑定到外部组件上。

  • 另外一个更加简单的例子

    import QtQuick 2.0
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.12
    import QtQuick.LocalStorage 2.12
    
    Window{
        id: window
        width: 640
        height: 480
        property string btnString: 'Hello world'
        Loader{
            id: loader
            source: 'MyButton.qml'
            onLoaded: {
                //静态指定元素属性,不能喝数据实时绑定
    //            loader.item.btnText = btnString
    
                //将属性动态绑定到某个元素上,实现数据的双向同步
                binder.target = loader.item
            }
        }
    
        Binding{
            id: binder
            property: "btnText"
            value: btnString
        }
    
        Connections{
            target: loader.item
            onClicked:{
                btnString = 'Hello Welcome'
            }
        }
    }

    可以看出来,组件一旦被创建,就无法实现和数据的双向绑定了,但可以使用Bind作为代理,实时改变属性的值,从而变相的实现了数据的双向绑定。

间接连接

可以动态指定Connections的target,将同一个处理函数在不同的时刻指向不同的发送者。

从文本中动态实例化项

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window{
    id: window
    width: 640
    height: 480
    property var item
    function createItem(){
        item = Qt.createQmlObject('import QtQuick 2.12;Rectangle{
        id: rect
        x: 100
        y: 100
        width: 100
        height: 100
        color: \"blue\"
    }', window, 'rect')
    }

    Button{
        x: 0
        y: 406
        width: 251
        height: 74
        text: "动态销毁元素"
        font.pointSize: 20
        onClicked: {
            item.destroy()
        }
    }

    Component.onCompleted: {
        window.createItem()
    }
}

动态创建对象的状态跟踪

var _component;
var _callback;
var _parent;
var _source;

function create(source, parent, callback)
{
    _parent = parent;
    _callback = callback;
    _source = source;
    
    _component = Qt.createComponent(source);
    if (_component.status === Component.Ready || _component.status === Component.Error)
        createDone();
    else
        _component.statusChanged.connect(createDone);
}

function createDone()
{
    if (_component.status === Component.Ready)
    {
        var obj = _component.createObject(_parent);
        if (obj != null)
            _callback(obj, _source);
        else
            console.log("Error creating object: " + _source);
        
        _component.destroy();
    }
    else if (_component.status === Component.Error)
        console.log("Error creating component: " + component.errorString());
}

外部调用

CreateObject.create("planet.qml", root, itemAdded);

使用createComponent可以从QML文件中创建一个组件,组件创建完成后可以创建改组件的一个对象,使用createObject,创建对象要指定父组件,最后一个参数是创建完成后的回调函数。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值