目录
认识QML
-
首先,以可以旋转的风车作为例子
-
首先,整个窗口包括背景、风车、风车杆,点击风车的时候,会开始旋转
-
-
Qt界面设计工具可以实现基本的属性设置,但是对于动画、定时器等功能还不能支持,需要放在ui.qml文件外处理。
-
动画的处理方案一般是先建立一系列状态,为每个状态制定好样式,然后根据不同的事件下修改状态值。
-
状态值里面有when,可以设置在某种条件下的状态
-
状态的动画在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
}
}
]
}
开始学习
-
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)
}
}
-
注意:Key只能写在根元素,而不能写在子元素上;Key不能写在元素内部。
-
元素绑定只在初始化的时候使用,后面使用=赋值是无法对属性进行绑定的。
基本元素
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组件
-
使用Python创建一个简单服务器:python -m http.server
-
在本地创建可以加载远程组件的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 } }
-
使用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请求。
-
创建服务:安装python curl pip install flask
-
编写服务端程序
#!/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)
-
使用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
-
创建和服务端实时通信的列表
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
}
-
该例子不难看出,使用Loader指定source可以直接动态加载一个组件。
-
通过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,创建对象要指定父组件,最后一个参数是创建完成后的回调函数。