目录
认识QML
开始学习
QML快速入门
基本元素
布局
输入组件
动态元素
模型-视图-代理
画布元素
粒子模型
着色器效果
多媒体
网络
存储
动态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常用的模块依赖图
属性绑定和属性赋值
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
#include
#include
#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", 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
#include
#include
#include
#include
#include
#include
#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 mVertices;
QVector 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
/* 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(m_yuvData.Y.size()), m_frame->data[0], static_cast(m_yuvData.Y.size()));
m_yuvData.U.resize(m_frame->linesize[1] * m_frame->height / 2);
memcpy_s(m_yuvData.U.data(), static_cast(m_yuvData.U.size()), m_frame->data[1], static_cast(m_yuvData.U.size()));
m_yuvData.V.resize(m_frame->linesize[2] * m_frame->height / 2);
memcpy_s(m_yuvData.V.data(), static_cast(m_yuvData.V.size()), m_frame->data[2], static_cast(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()<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();
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
#include
#include
#include
#include
#include
#include
#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 mVertices;
QVector 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
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
#include
#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 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
#include
//************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(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(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/', 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/', 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/', 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
}
}
}
动态加载组件&动态绑定
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
}
// <>
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"; }
}
]
// <
该例子不难看出,使用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,创建对象要指定父组件,最后一个参数是创建完成后的回调函数。