5 Qt Quick事件处理
信号共有两类:
- 由用户输出产生,如按键、鼠标、触摸屏、传感器等
- 由对象状态或属性变化产生的,比如Image的status属性
5.1 信号处理器
import QtQuick 2.2
import QtQuick.Controls 1.2
Rectangle {
width: 320
height: 240
color: "gray"
Button {
text: "Quit"
anchors.centerIn: parent
onClicked: {
Qt.quit()
}
}
}
onClicked
:信号处理器等价于Qt Widget中的槽{ Qt.quick() }
:ECMAScript中的代码块,理解为匿名函数- 信号处理器的形式一般为on<Signal>这种形式
- 信号处理器位于拥有信号的元素内部,当元素信号发射时处理器被调用
5.2 附加信号处理器
import QtQuick 2.2
Rectangle {
width: 320
height: 480
color: "gray"
focus: true
Keys.enabled: true
Keys.onEscapePressed: {
Qt.quit()
}
}
- 附加信号处理器位于元素内部,但处理的信号并非当前元素发出,而是来自其他元素,如按键Keys
- 附加信号处理器的形式为<AttachingType>.on<Signal>语法
Component对象的附加信号
Component.oncompleted()
,元素创建完成时的信号
Component.destruction()
,元素销毁时的信号
5.3 Connections专门的信号处理
一个Connections对象创建一个到QML信号的连接
用途如下:
- 将多个对象连接到同一个QML信号上
- 在发出信号的对象作用域之外来建立连接
- 发射信号的对象没有在QML中定义(可能通过C++导出的)
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "gray"
Text {
id: text1
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 20
text: "Text One"
color: "blue"
font.pixelSize: 28
}
Text {
id: text2
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: text1.bottom
anchors.topMargin: 8
text: "Text Two"
color: "blue"
font.pixelSize: 28
}
Button {
id: changeButton
anchors.top: text2.bottom
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
text: "Change"
}
Connections {
target: changeButton
function onClicked() {
text1.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
text2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
}
Connections的用法:
Connections {
target: area;
function on<Signal>:{ function or code block; }
}
5.5 属性变化的信号
Text {
text: "Some Text";
onTextChanged: console.log(text);
}
- 只有部分属性变化时,才会发射信号
- 有的
<property>Changed
有参数,有的没有参数,没有统一的规则 - 属性变化时,会自动发出一个信号,形如
<property>Changed
,对应的信号处理器on<property>Changed
如何知道那些属性会发射信号,以及信号有没有参数?
答:通过找到该元素对应C++源代码中的定义来进行确认,如果通过Q_PROPERTY来定义属性就是具备信号的
5.6 定义自己的信号
语法如下:signal <name>[([<type> <parameter name>[, ...]])]
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#C0C0C0"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Component {
id: colorComponent
Rectangle {
id: colorPicker
width: 50
height: 30
signal colorPicked(color clr)
MouseArea {
anchors.fill: parent
onPressed: colorPicker.colorPicked(colorPicker.color)
}
}
}
Loader {
id: redLoader
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "red"
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "blue"
}
}
Connections {
target: redLoader.item
onColorPicked: {
coloredText.color = clr
}
}
Connections {
target: blueLoader.item
onColorPicked: {
coloredText.color = clr
}
}
}
MouseArea
是专门处理鼠标的Item,具有一个pressed
信号Loader
是专门用来动态创建组件的,可以从QML文件中创建组件,也可以通过指定sourceComponent属性为要创建的组件idLoader
具有一个信号loaded
,当组件被创建完成时被触发。还有一个属性Item
表示当前已经加载的对象
5.7 信号和槽的直接连接
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#C0C0C0"
Rectangle {
id: relay
signal messageReceived(string person, string notice)
Component.onCompleted: {
relay.messageReceived.connect(sendToPost)
relay.messageReceived.connect(sendToTelegraph)
relay.messageReceived.connect(sendToEmail)
relay.messageReceived("Tom", "Happy Birthday")
}
function sendToPost(person, notice) {
console.log("Sending to post: " + person + ", " + notice)
}
function sendToTelegraph(person, notice) {
console.log("Sending to telegraph: " + person + ", " + notice)
}
function sendToEmail(person, notice) {
console.log("Sending to email: " + person + ", " + notice)
}
}
}
//输出
qml: Sending to post: Tom, Happy Birthday
qml: Sending to telegraph: Tom, Happy Birthday
qml: Sending to email: Tom, Happy Birthday
//第二个例子
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#C0C0C0"
Rectangle {
id: forwarder
width: 100
height: 100
signal send
onSend: console.log("Send clicked")
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: console.log("MouseArea clicked")
}
Component.onCompleted: {
mousearea.clicked.connect(send)
}
}
}
//点击Rectangle输出:
qml: MouseArea clicked
qml: Send clicked
- 信号其实也是一个对象
- 触发信号的方法
relay.messageReceived("Tom", "Happy Birthday")
,像函数一样调用即可 - 信号对象具有一个
connect()
方法,用于连接触发信号时,调用设置的槽 - 可以对一个信号调用多个
connect()
,当信号触发时,会安装connect的顺序依次调用槽
6. QML自带的常用事件
6.1 鼠标对象MouseArea
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
Rectangle {
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton) {
Qt.quit()
} else if (mouse.button == Qt.LeftButton) {
parent.color = Qt.rgba((mouse.x % 255) / 255.0,
(mouse.y % 255) / 255.0, 0.6, 1.0)
}
}
onDoubleClicked: {
parent.color = "red"
}
}
}
}
MouseArea
对象可以附加到一个Item上提供处理鼠标事件,本身是一个不可见的ItemMouseArea
的属性enabled
用来控制是否处理鼠标事件,默认为trueMouseArea
的属性acceptedButtons
属性设定接受那些鼠标按键产生的事件(左键、右键、中键)MouseArea
作为一个Item本身也是有一个大小范围的,这决定了有效的鼠标区域MouseArea
具有的clicked信号被触发时会传递一个MouseEvent类型名为mouse的参数,其中button属性表示按下了哪个键,x和y表示按下时鼠标的位置,accepted属性为true时表示已经处理完毕,无需将信号继续往下发送
6.2 键盘对象Keys
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 320
height: 240
visible: true
Rectangle {
anchors.fill: parent
color: "gray"
focus: true
Keys.enabled: true
Keys.onEscapePressed: {
Qt.quit()
}
Keys.forwardTo: [moveText, likeQt]
Text {
id: moveText
x: 20
y: 20
width: 200
height: 30
text: "Moving Text"
color: "blue"
font {
bold: true
pixelSize: 24
}
Keys.enabled: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Left:
x -= 5
break
case Qt.Key_Right:
x += 5
break
case Qt.Key_Down:
y += 5
break
case Qt.Key_Up:
y -= 5
break
default:
return
}
event.accepted = true
}
}
CheckBox {
id: likeQt
text: "Like Qt Quick"
anchors.left: parent.left
anchors.leftMargin: 10
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
z: 1
}
}
}
Keys
对象是Qt Quick提供以处理键盘事件的对象Keys
具有很多针对特定按键的信号returnPressed 、escapePressed、downPressed、backPressed等Keys
还定义了普适性的按键信号,pressed和released信号,均具有一个类型KeyEvent的event参数- 如果一个按键被处理了,最好将event.accepted参数设为true,以免信号继续传递给其他Item,发生奇怪的问题
Keys
的enabled属性,代表是否处理按键,默认为trueKeys
的forwardTo属性,一个列表,表示按键事件应该依次传递给列表中的对象,如果某个对象accepted了按键信息,那么列表中这个对象后面的对象均不会收到按键事件了keys
的priority属性,表示设置Keys附加属性的优先级,Item之前处理按键,Item之后处理按键- 如果想要让某个元素Item处理按键,则必须要把焦点给它,通过Item的focus来控制,设置为true
6.3 定时器 Timer
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 320
height: 240
visible: true
Rectangle {
width: 320
height: 240
color: "gray"
QtObject {
id: attrs
property int counter
Component.onCompleted: {
attrs.counter = 10
}
}
Text {
id: countShow
anchors.centerIn: parent
color: "blue"
font.pixelSize: 40
}
Timer {
id: countDown
interval: 1000
repeat: true
triggeredOnStart: true
onTriggered: {
countShow.text = attrs.counter
attrs.counter -= 1
if (attrs.counter < 0) {
countDown.stop()
countShow.text = "Clap Now!"
}
}
}
Button {
id: startButton
anchors.top: countShow.bottom
anchors.topMargin: 20
anchors.horizontalCenter: countShow.horizontalCenter
text: "Start"
onClicked: {
attrs.counter = 10
countDown.start()
}
}
}
}
- 定时器
interval
属性,指定触发周期,单位是毫秒,默认1000 - 定时器
repeat
属性,指定定时器是周期触发,还是只触发一次,默认一次性 - 定时器
running
属性,true定时器启动,false定时器停止,默认false,可通过start()
、stop()
、restart()
设置 - 定时器
triggeredOnStart
属性,true定时器启动时立马执行一次,false定时器启动后达到触发周期才执行
7. 组件与动态对象
7.1 Component(组件)
Component是由Qt框架封装好的,只暴露必要接口的QML类型,可以重复利用
一个QML组件就是一个黑盒子,通过属性、信号、函数和外部世界交互
Component既可以定义在独立的QML文件中,也可以嵌入其他的QML文档中定义
嵌入式Component
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#C0C0C0"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Component {
id: colorComponent
Rectangle {
id: colorPicker
width: 50
height: 30
signal colorPicked(color clr)
MouseArea {
anchors.fill: parent
onPressed: colorPicker.colorPicked(colorPicker.color)
}
}
}
Loader {
id: redLoader
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "red"
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "blue"
}
}
Connections {
target: redLoader.item
onColorPicked: {
coloredText.color = clr
}
}
Connections {
target: blueLoader.item
onColorPicked: {
coloredText.color = clr
}
}
}
- 在一个QML文档中嵌入Component需要使用Component对象来定义
- Compoment并不是Item的派生类,虽然它可以通过自己的顶层Item来为其他view提供可视化组件,但其本身是不可见的元素
- 定义一个Component和定义一个QML文档类似,只能包含一个顶层Item,且在这个Item以外不能定义任何的数据,除了id。
- 要显示出Component必须对其进行实例化,要实例化一个Component可以通过
Loader
在单独的文件中定义组件
//<ColorPicker.qml>
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
id: colorPicker
width: 50
height: 30
signal colorPicked(color clr)
function configureBorder() {
colorPicker.border.width = colorPicker.focus ? 2 : 0
colorPicker.border.color = colorPicker.focus ? "#90D750" : "#808080"
}
MouseArea {
anchors.fill: parent
onClicked: {
colorPicker.colorPicked(colorPicker.color)
mouse.accepted = true
colorPicker.focus = true
}
}
Keys.onReturnPressed: {
colorPicker.colorPicked(colorPicker.color)
event.accepted = true
}
Keys.onSpacePressed: {
colorPicker.colorPicked(colorPicker.color)
event.accepted = true
}
onFocusChanged: {
configureBorder()
}
Component.onCompleted: {
configureBorder()
}
}
//<main.qml>
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#EEEEEE"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
function setTextColor(clr) {
coloredText.color = clr
}
ColorPicker {
id: redColor
color: "red"
focus: true
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
KeyNavigation.right: blueColor
KeyNavigation.tab: blueColor
onColorPicked: {
coloredText.color = clr
}
}
ColorPicker {
id: blueColor
color: "blue"
anchors.left: redColor.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
KeyNavigation.left: redColor
KeyNavigation.right: pinkColor
KeyNavigation.tab: pinkColor
}
ColorPicker {
id: pinkColor
color: "pink"
anchors.left: blueColor.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
KeyNavigation.left: blueColor
KeyNavigation.tab: redColor
}
Component.onCompleted: {
blueColor.colorPicked.connect(setTextColor)
pinkColor.colorPicked.connect(setTextColor)
}
}
- 在单独的qml文件中定义组件,无需Component对象。只有在其他qml中嵌入组件才会需要Commponent
- 定义组件时,可以使用Item或其派生类作为组件的根Item
- 上述代码使用了两种自定义信号方式,redColor使用了信号处理器,blueColor和pinkColor则使用了signal对象的connect方法连接到setTextColor()上
7.2 动态创建组件Loader
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#C0C0C0"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Component {
id: colorComponent
Rectangle {
id: colorPicker
width: 50
height: 30
signal colorPicked(color clr)
MouseArea {
anchors.fill: parent
onPressed: colorPicker.colorPicked(colorPicker.color)
}
}
}
Loader {
id: redLoader
width: 80 //[1]
height: 60 //[2]
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "red"
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
onLoaded: {
item.color = "blue"
}
}
Connections {
target: redLoader.item
function onColorPicked(clr) {
coloredText.color = clr
}
}
Connections {
target: blueLoader.item
function onColorPicked(clr) {
coloredText.color = clr
}
}
}
Loader用于动态创建组件,把Loader看作占位符,当需要显示某个元素时,才使用Loader把它加载进来
Loader动态加载的两种方式:
- Loader的source属性,用于加载一个QML文档(单独的文件)
- Loader的sourceComponent属性,用于加载一个Component(嵌入到其他QML中的组件)
- 当source或sourceComponent属性发生变化时,它之前加载的Component会自动销毁,然后加载新对象
- 将source设置为一个空字符串,或将sourceComponent设置为undefined,将会销毁当前加载的对象
- Loader的Item属性指向加载的顶层Item,如Loader加载了ColorPicker组件,则Item指向Rectangle
- Loader和它所加载的Item始终具有相同的尺寸,优先使用Loader自身所指定的大小
- 如果Loader所加载的Item需要处理按键事件,则必须将Loader对象的focus设置为true,当Item处理了按键事件后,应当将按键事件的accepted设置为true,以免处理完的事件再次传递给Loader
从Component调用Loader创建组件
//颜色组件根据焦点来绘制边框
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#EEEEEE"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Component {
id: colorComponent
Rectangle {
id: colorPicker
width: 50
height: 30
signal colorPicked(color clr)
property Item loader
border.width: focus ? 2 : 0
border.color: focus ? "#90D750" : "#808080"
MouseArea {
anchors.fill: parent
onClicked: {
colorPicker.colorPicked(colorPicker.color)
loader.focus = true
}
}
Keys.onReturnPressed: {
colorPicker.colorPicked(colorPicker.color)
event.accepted = true
}
Keys.onSpacePressed: {
colorPicker.colorPicked(colorPicker.color)
event.accepted = true
}
}
}
Loader {
id: redLoader
width: 80
height: 60
focus: true
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
KeyNavigation.right: blueLoader
KeyNavigation.tab: blueLoader
onLoaded: {
item.color = "red"
item.focus = true
item.loader = redLoader
}
onFocusChanged: {
item.focus = focus
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
sourceComponent: colorComponent
KeyNavigation.left: redLoader
KeyNavigation.tab: redLoader
onLoaded: {
item.color = "blue"
item.loader = blueLoader
}
onFocusChanged: {
item.focus = focus
}
}
Connections {
target: redLoader.item
function onColorPicked(clr) {
coloredText.color = clr
}
}
Connections {
target: blueLoader.item
function onColorPicked(clr) {
coloredText.color = clr
}
}
}
从独立的文件中使用Loader创建组件
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 240
visible: true
color: "#EEEEEE"
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Loader {
id: redLoader
width: 80
height: 60
focus: true
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
source: "ColorPicker.qml"
KeyNavigation.right: blueLoader
KeyNavigation.tab: blueLoader
onLoaded: {
item.color = "red"
item.focus = true
}
onFocusChanged: {
item.focus = focus
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
source: "ColorPicker.qml"
KeyNavigation.left: redLoader
KeyNavigation.tab: redLoader
onLoaded: {
item.color = "blue"
}
onFocusChanged: {
item.focus = focus
}
}
Connections {
target: redLoader.item
function onColorPicked(clr) {
coloredText.color = clr
if (!redLoader.focus) {
redLoader.focus = true
blueLoader.focus = false
}
}
}
Connections {
target: blueLoader.item
function onColorPicked(clr) {
coloredText.color = clr
if (!blueLoader.focus) {
blueLoader.focus = true
redLoader.focus = false
}
}
}
}
利用Loader动态创建、销毁组件
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 320
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
property bool colorPickerShow: false
Text {
id: coloredText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 4
text: "Hello World!"
font.pixelSize: 32
}
Button {
id: ctrlButton
text: "Show"
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
onClicked: {
if (rootItem.colorPickerShow) {
redLoader.sourceComponent = undefined
blueLoader.source = ""
rootItem.colorPickerShow = false
ctrlButton.text = "Show"
} else {
redLoader.source = "ColorPicker.qml"
redLoader.item.colorPicked.connect(onPickedRed)
blueLoader.source = "ColorPicker.qml"
blueLoader.item.colorPicked.connect(onPickedBlue)
redLoader.focus = true
rootItem.colorPickerShow = true
ctrlButton.text = "Hide"
}
}
}
Loader {
id: redLoader
anchors.left: ctrlButton.right
anchors.leftMargin: 4
anchors.bottom: ctrlButton.bottom
KeyNavigation.right: blueLoader
KeyNavigation.tab: blueLoader
onLoaded: {
if (item != null) {
item.color = "red"
item.focus = true
}
}
onFocusChanged: {
if (item != null) {
item.focus = focus
}
}
}
Loader {
id: blueLoader
anchors.left: redLoader.right
anchors.leftMargin: 4
anchors.bottom: redLoader.bottom
KeyNavigation.left: redLoader
KeyNavigation.tab: redLoader
onLoaded: {
if (item != null) {
item.color = "blue"
}
}
onFocusChanged: {
if (item != null) {
item.focus = focus
}
}
}
function onPickedBlue(clr) {
coloredText.color = clr
if (!blueLoader.focus) {
blueLoader.focus = true
redLoader.focus = false
}
}
function onPickedRed(clr) {
coloredText.color = clr
if (!redLoader.focus) {
redLoader.focus = true
blueLoader.focus = false
}
}
}
通过设置Loader属性source来销毁或显示Loader组件,在Loader属性onLoaded中设置加载后时的设置
7.3 ECMAScript脚本中动态创建对象
在ECMAScript中共有两种方式:
- 使用
Qt.createComponent()
动态的创建一个组件,然后使用Component的createObject()
方法创建对象 - 使用
Qt.createQmlObject()
从一个QML字符串直接创建一个对象
如果在QML中定义了一个组件,使用Qt.createComponent()比较好;如果QML对象本身是在应用运行时产生的,则Qt.createQmlObject()更好
7.3.1 从组件文件调用Qt.createComponent()创建
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: rootItem
width: 360
height: 300
visible: true
property int count: 0
property Component component: null
Text {
id: coloredText
text: "Hello World!"
anchors.centerIn: parent
font.pixelSize: 24
}
function changeTextColor(clr) {
coloredText.color = clr
}
function createColorPicker(clr) {
if (rootItem.component == null) {
rootItem.component = Qt.createComponent("ColorPicker.qml")
}
var colorPicker
if (rootItem.component.status == Component.Ready) {
colorPicker = rootItem.component.createObject(rootItem, {
"color": clr,
"x": rootItem.count * 55,
"y": 10
})
colorPicker.colorPicked.connect(rootItem.changeTextColor)
}
rootItem.count++
}
Button {
id: add
text: "add"
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
onClicked: {
createColorPicker(Qt.rgba(Math.random(), Math.random(), Math.random(), 1))
}
}
}
创建组件函数:
object createComponent(url, mode, parent)
url
:指定QML文档的本地路径或网络地址
mode
:指定创建组件的模式
Component.PreferSynchronous
(优先同步),默认模式Component.Asynchronous
(异步模式)
parent
:指定组件的父对象
创建对象函数:
item createObject(parent, initparm)
parent
:创建出来的Item的parent
initparm
:初始化参数列表,用于初始化创建出来的Item,以key-value形式保存在一个对象中
对于嵌入在qml中的Component,因为Component是现成的已经定义出来了,因此只需要直接调用
Qt.createObject()
方法即可
7.3.2 从QML字符串动态创建Component
var newObject = Qt.createQmlObject(
'import QtQuick 2.2; Rectangle {color: "red"; width: 20; height: 20}',
parentItem,
"dynamicSnippet1"
);
第一个参数:要创建对象的QML字符串
第二个参数:指定创建对象的父对象
第三个参数:给新创建的对象关联一个文件路径,主要用于报告错误
7.3.3 销毁通过ECMAScript动态创建的对象
对于使用Loader创建的对象销毁:
- 将source设置为空串,或者将sourceComponent设置为undefined触发Loader销毁
对于使用Qt.createComponent()或Qt.createQmlObject()方法创建的对象销毁:
- 调用对象的destory()方法,有一个可选参数,指定延迟多少毫秒再删除,默认为0
对于destory()方法,QML引擎会在当前代码块结束后的某个合适的时刻删除它们,因此即便在对象内部调用destroy()也是安全的
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: rootItem
width: 360
height: 300
visible: true
property int count: 0
property Component component: null
property var dynamicObjects: []
Text {
id: coloredText
text: "Hello World!"
anchors.centerIn: parent
font.pixelSize: 24
}
function changeTextColor(clr) {
coloredText.color = clr
}
function createColorPicker(clr) {
if (rootItem.component == null) {
rootItem.component = Qt.createComponent("ColorPicker.qml")
}
var colorPicker
if (rootItem.component.status == Component.Ready) {
colorPicker = rootItem.component.createObject(rootItem, {
"color": clr,
"x": rootItem.dynamicObjects.length * 55,
"y": 10
})
colorPicker.colorPicked.connect(rootItem.changeTextColor)
rootItem.dynamicObjects[rootItem.dynamicObjects.length] = colorPicker
}
}
Button {
id: add
text: "add"
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
onClicked: {
createColorPicker(Qt.rgba(Math.random(), Math.random(),
Math.random(), 1))
}
}
Button {
id: del
text: "del"
anchors.left: add.right
anchors.leftMargin: 4
anchors.bottom: add.bottom
onClicked: {
if (rootItem.dynamicObjects.length > 0) {
var deleted = rootItem.dynamicObjects.splice(-1, 1)
deleted[0].destroy()
}
}
}
}
6. Qt Quick元素布局
Qt Quick共有三种方式布局元素:
- Item Positioner(定位器)
- Item Layout(布局)
- Item.anchors(锚属性)
6.1 定位器
定位器只会控制元素的位置,而不会改变元素的大小
当将元素交给定位器管理时,不要再使用Item的x、y、anchors属性
定位器本身也是一个Item,因此也可以对定位器使用锚属性布局
6.1.1 Row
Row沿着一行安置子元素
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
Row {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
}
Row中的元素可通过Positioner附加属性来获知自己在Row中的详细位置信息
Positioner.index
:元素在定位器中是第几个元素Positioner.isFirstItem
:元素在定位器中是否是第一个元素Positioner.isLastItem
:元素在定位器中是否是最后一个元素
Row.spacing
:指定它管理的子元素之间的间隔
Row.layoutDirection
:指定布局方向,Qt.LeftToRIght
从左到右放置, Qt.RightToLeft
从右到左放置
Row.add
:Item添加的过度动画
Row.move
:Item移动的过度动画
Row.populate
:定位器初始化创建Items的过度动画
6.1.2 Colomun
Colomun和Row类似,在垂直方向上安排子元素
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
Column {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
}
6.1.3 Grid
Grid在一个网格上安置它的子元素,它会创建一个拥有多个单元格的网格,足够容纳所有的子元素
Grid放置子元素按照从左到右、从上到下的方式
Grid放置子元素时,默认会放置在单元格的左上角,即(0, 0)的位置
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
Grid {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
rows: 3
columns: 3
rowSpacing: 4
columnSpacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
}
Grid.rows
:设定表格的行
Grid.coloumn
:设定表格的列,默认只有4列
Grid.rowSpacing
:设定行间距,单位像素
Grid.columnSpacing
:设定列间距,单位像素
Grid.flow
:描述表格的流模式
Grid.LeftToRight
:默认值,从左到右一个挨一个放置Item,一行放满再放下一行Grid.TopToBotton
:从上到下一个挨一个放置Item,一列放满再放下一列
Grid.horizontalItemAlignment
:指定元素在单元格内的水平对齐方式
Grid.verticalItemAlignment
:指定元素在单元格内的垂直对齐方式
Grid.add
:Item添加的过度动画
Grid.move
:Item移动的过度动画
Grid.populate
:定位器初始化创建Items的过度动画
6.1.4 Flow
Flow类似与文字排版系统
Flow和Grid类似,但它没有显式的行列数,它会计算Item的尺寸,然后和自身尺寸比较,按需折行
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 360
height: 240
color: "#EEEEEE"
visible: true
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
Flow {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
width: 280
height: 130
spacing: 4
ColorPicker {
width: 80
height: 20
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
width: 100
height: 40
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
width: 80
height: 25
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
width: 35
height: 35
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
width: 20
height: 80
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
}
Flow.flow
:描述流模式
Flow.LeftToRight
默认值,从左到右安排Item,直到Flow本身宽度无法容纳新的Item时折行Flow.TopToBotton
从上到下安排Item,直到Flow本身高度无法容纳新的Item时换列
Flow.spacing
:描述Item之间的间隔
Flow.add
:Item添加的过度动画
Flow.move
:Item移动的过度动画
Flow.populate
:定位器初始化创建Items的过度动画
6.1.5 定位器嵌套
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
Row {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
spacing: 4
Column {
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
Column {
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
Column {
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0)
onColorPicked: setTextColor(clr)
}
}
}
}
6.2 布局管理器
布局管理器和Qt Widgets中的类似
布局管理器和定位器不同在于,布局管理器会自动调整子元素的尺寸来适应界面大小
6.2.1 GridLayout布局
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
GridLayout {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
width: parent.width * 0.6 //[1]
rows: 3
columns: 3
rowSpacing: 4
columnSpacing: 4
flow: GridLayout.TopToBottom
ColorPicker {
id: "cp1"
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
Layout.rowSpan: 3 //[2]
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
Layout.fillWidth: true //[3]
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
}
}
布局管理器比定位器多了一些附加属性,这决定了两者的不同点
Layout.row
:指定元素在GridLayout中的行位置
Layout.column
:指定元素在GridLayout中的列位置
Layout.rowSpan
:元素占用几行
Layout.columnSpan
:元素占用几列
Layout.minimumWidth
:元素的最小宽度
Layout.minimumHeight
:元素的最小高度
Layout.preferredWidth
:设置元素在布局中的首选宽度
Layout.preferredHeight
:设置元素在布局中的首选高度
Layout.maximumWidth
:元素的最大宽度
Layout.maximumHeight
:元素的最大高度
Layout.fillWidth
:元素是否尽可能宽度填满格子
Layout.fillHeight
:元素是否尽可能高度填满格子
Layout.alignment
:元素在格子中的对齐方式
6.2.2 RowLayout布局
RowLayout布局可以看成是只有一行的GridLayout布局
RowLayout布局和Row定位器类似,但是可以使用一些附加属性
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
RowLayout {
//[2]
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
anchors.right: parent.right //[3]
anchors.rightMargin: 4 //[3]
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
Layout.fillWidth: true //[4]
}
}
}
RowLayout布局可使用如下附加属性:
Layout.minimumWidth
Layout.minimumHeight
Layout.preferredWidth
Layout.preferredHeight
Layout.maximumWidth
Layout.maximumHeight
Layout.fillWidth
Layout.fillHeight
Layout.alignment
6.2.3 ColumnLayout布局
ColumnLayout可看作只有一列的GridLayout
ColumnLayout布局和Column定位器类似,但是可以使用一些附加属性
import QtQuick 2.2
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
Window {
width: 360
height: 240
visible: true
color: "#EEEEEE"
id: rootItem
Text {
id: centerText
text: "A Single Text."
anchors.centerIn: parent
font.pixelSize: 24
font.bold: true
}
function setTextColor(clr) {
centerText.color = clr
}
ColumnLayout {
//[2]
anchors.left: parent.left
anchors.leftMargin: 4
anchors.top: parent.top
anchors.topMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
spacing: 4
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
}
ColorPicker {
color: Qt.rgba(Math.random(), Math.random(), Math.random())
onColorPicked: setTextColor(clr)
Layout.fillHeight: true //[4]
}
}
}
ColumnLayout布局可以使用如下附加属性:
Layout.minimumWidth
Layout.minimumHeight
Layout.preferredWidth
Layout.preferredHeight
Layout.maximumWidth
Layout.maximumHeight
Layout.fillWidth
Layout.fillHeight
Layout.alignment
7 Qt Quick常用元素简介
7.1 行编辑控件
Text、TextInput、TextEdit等控件不支持定制元素背景,可通过放置一个z序更小的Rectangle来实现
7.1.1 TextInput
TextInput用于编辑一行文本,类似QLineEdit,TextInput并不支持使用HTML标记的富文本
- 通过font分组属性设置元素的字体、大小、粗细、斜体、下划线等
- 通过text可设置或获取元素的文本
- 通过horizontalAlignment和verticalAlignment可设定文本的对齐方式
- 通过wrapMode设置文本的换行策略
- 通过color设置文本的颜色
- 通过contentWidth、contentHeight获取文本的宽和高
- 通过length属性获取编辑框内的字符个数
- 通过maximumLength可设置允许输入的最大长度
- 通过cursorDelegate可定制光标外观,cursorPosition设置或返回光标位置,cursorVisible设置或返回光标的可见状态 ,cursorRectangle可获取光标所在矩形和定制光标外观有关
- 通过echoMode可设置显示方式,用于密码框
- TextInput还支持canPaste粘贴、canUndo撤销、canRedo重做、autoScroll滚动特性
- 通过selectByMouse属性设置为true来允许用户通过鼠标选择文字
- 当用户按下回车或确认键,或者编辑框失去焦点时,会发出accepted和editingFinished信号(如果设置了inputMask或validator则只有当文本符合限制条件时,信号才会触发)
- 限制TextInput的字符输入,可通过inputMask和validator
TextInput {
width: 120;
height: 30;
font.pixelSize: 20;
anchors.centerIn: parent;
validator: IntValidator{ top: 15; bottom: 6;}
focus: true;
}
7.1.2 TextField
TextField和TextInput类似,区别如下:
文本颜色和背景色
TextInput使用color属性指定,TextField使用textColor属性指定
TextInput没有背景,背景是透明,可以和父控件无缝结合
TextField有背景,背景需要自行定义
TextInput可定制cursor,TextField不可定制cursor
7.2 文本块编辑控件
7.2.1 TextEdit
TextEdit是多行文本编辑框,大多数属性和TextInput类似
textDocument
:可以结合QSyntaxHighlighter实现语法高亮
text
:获取文本内容(或使用getText()
方法)
append()、insert()、cur()、paste()、remove()
等方法可修改文本
textFormat
:用于指定文本格式
TextEdit.PlainText
纯文本,默认值TextEdit.RichText
富文本TextEdit.AutoText
自动检测
lineCount
:返回编辑框内的文本行数
wrapMode
:折行模式
TextEdit.NoWrap
不折行,超出边界不显示TextEdit.WordWrap
折行,在单词边界处折行TextEdit.WrapAnywhere
折行,不考虑单词编辑TextEdit.Wrap
折行,尽量在单词边界处折行
TextEdit.linkActivated
信号,当用户点击富文本中的链接时触发
TextEdit.linkHovered
信号,鼠标悬停在富文本内嵌的链接上方触发
7.2.2 TextArea
TextArea和TextEdit类似,不同点如下:
文本颜色和背景色
TextEdit的文本颜色使用color属性指定,TextArea使用textColor属性指定
TextEdit没有背景,是透明的;TextArea有背景,可自定定义
定制游标
TextEdit和TextInput类似,可通过cursorDelegate属性定制游标,TextArea没有
文本滚动
TextArea由于从ScrollView继承而来,因此支持方向键、翻页键、鼠标滚动
TextEdit不支持
7.3 互斥分组ExclusiveGroup
ExclusiveGroup本身是不可见元素,用于将若干个元素组合在一起,以供用户选择其中的一项
两种实现方式:
- 在ExclusiveGroup对象中定义RadioButton、CheckBox、Action等元素,不要设置它们的exclusiveGroup属性
- 只定义一个只设置id属性的ExclusiveGroup对象,然后在别处定义RadioButton、CheckBox、Action等元素时,通过id初始化这些元素的exclusiveGroup属性
7.4 单选按钮RadioButton
RadioButton用于多选一的场景,使用时通过exclusiveGroup属性来为其指定一个分组
exclusiveGroup
:属性,用于区分多选一的范围,相同exclusiveGroup的组件才会进行多选一
text
:存储单选按钮的文本
checked
:读取或设置RadioButton是否被选中
hovered
:指示鼠标是否悬停在RadioButton上
pressed
:在鼠标被按下时true
clicked()
:信号,当点击了一个单选按钮时触发
7.5 多选按钮CheckBox
CheckBox用于多选一或多选多的场景
CheckBox比RadioButton多了两个属性:
partiallyCheckedEnabled
:指示是否允许部分选中状态,默认为false
checkState
:记录选中状态
给CheckBox指定了exclusiveGroup属性,同属于一个互斥组的复选框,也可以实现多选一的效果
7.6 分组框GroupBox
GroupBox用于将多个组件组合在一起显示
GroupBox的尺寸根据子控件的尺寸计算而来,如果想要通过锚布局来管理子控件,则必须显式指定GroupBox本身的尺寸
title
:属性,说明文字,会显示在组合框的上方
flat
:属性,true显示边框,false去掉左右底三条边框
checkable
:属性,为true时,标题框会出现一个复选框,会影响子控件是否可选
contentItem
:指向一个Item对象,代表分组框的内容区,是在分组框内的子控件的父亲(动态创建分组框的子控件时,需要显示指定contentItem为它们的父亲)
7.7 组合框ComboBox
组合框由一个列表框和一个标签控件组成
列表框可以一直显示,也可以隐藏,用户点击标签控件的下拉箭头时显示列表框
列表框中当前选中的项(如果有的话)显示在标签控件中
Qt Quick提供的ComboBox是一个下拉列表框,使用Menu实现,列表中每个条目都对应一个MenuItem
editable
属性决定下拉列表框的标签控件是否可以编辑,默认false,true时可以编辑,此时editText
保存编辑的文本,可以设置validator属性限制用户输入,当编辑完成时,会发送accepted信号
currentIndex
:用户当前选择的条目索引
currentText
:用户当前选择的条目文字
activated
:信号,用户选择条目时触发
find
:用于查找列表中的是否存在指定的字符串
textAt()
:返回指定索引位置的字符串
selectAll()
:可以选中可编辑ComboBox编辑框内的所有文本
count()
:返回列表内的条目个数
下拉列表的数据从model属性来:
model属性可以是一个简单的字符串{“TV”, “CD Player”, “Set Top Box”, “Router”}
也可以是一个ListModel,可以通过textRole属性指定列表条目对应的model内的role,如果不设置默认使用第一个role
ComboBox {
editable: true;
model: ListModel {
ListElement { text: "Banana"; color: "Yellow" }
}
textRole: "color";
}
7.8 进度条ProgressBar
minimumValue
:表示最小值
maximumValue
:表示最大值
value
:当前值,更新它,进度条就会随之变化
orientation
:表示方向,Qt.Horizontal(水平方向),Qt.Vertical(垂直方向)
indeterminate
:是否显示具体进度,默认true,false时变成BusyIndicator
7.9 选项卡TabView(QtQuick.Controls2中不可用)
count
:只读,返回标签页个数
currentIndex
:当前标签页的索引,从0开始,可以读取或设置它来切换标签
frameVisible
:标签页对应的内容周围边框是否可见
tabVisible
:设置标签栏是否可见
tabPosition
:保存标签栏的位置,默认Qt.TopEdge界面顶部,Qt.BottomEdge界面底部
addTab
:用于增加一个标签页,第一个参数是标签的标题,第二个参数是一个组件
insertTab
:在指定的索引位置插入一个标签
removeTab
:删除指定位置的标签
moveTab
:将一个标签从索引from移动到to
getTab
:返回指定位置的标签对象(类型为Tab),Tab只有一个title属性,是Loader的派生类
7.10 滑动选值控件Slider
maximumValue
:设置最大值,默认1.0
minimumValue
:设置最小值,默认0
value
:查看或设置当前值
stepSize
:设置滑块的步长,每次按动方向键变化的大小
orientation
:设置控件的方向,默认水平
7.11 Flickable
将一个Item放入Flickable中,那么这个Item就可以被拖动
Flickable就像一扇窗户,透过它,你可以看见比窗户本身更大的风景
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 320
height: 240
visible: true
color: "lightgray"
Flickable {
anchors.fill: parent
contentWidth: image.width
contentHeight: image.height
Image {
id: image
source: "file:z:\\图片\\aa.jpg"
}
}
}
7.12 Screen
Screen对象指的是显示Item的那个屏幕(有的设备有多个屏幕),它提供了一些只读属性来描述屏幕参数
Screen是Qt Quick提供的一个附加对象,可以在Item及其派生对象、Window及其派生类中使用
使用前提条件:必须在组件加载完成后才能使用
8 Canvas画布
QPainter绘图四大金刚:
画布(Canvas)、画师(Context2D)、画笔(lineWidth,strokeStyle)、画刷(fillStyle)
8.1 基本绘图
Canvas是Item的派生类,通过width和height决定绘图区域,然后在onPaint()信号处理器内使用Context2D对象来绘图
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 400
height: 300
visible: true
Canvas {
width: parent.width
height: parent.height
onPaint: {
var ctx = getContext("2d")
ctx.lineWidth = 2
ctx.strokeStyle = "red"
ctx.fillStyle = "blue"
ctx.beginPath()
ctx.rect(10, 10, 100, 80)
ctx.fill()
ctx.stroke()
}
}
Canvas {
width: parent.width
height: parent.height
contextType: "2d"
onPaint: {
context.lineWidth = 2
context.strokeStyle = "red"
context.fillStyle = "blue"
context.beginPath()
context.rect(150, 150, 100, 80)
context.fill()
context.stroke()
}
}
}
步骤如下:
- 定义一个Convas对象,设置width、height
- 定义onPaint信号处理器
- 获取Context2D对象
- 实际的绘图操作
paint
是Canvas的信号,当需要绘图(更新)时触发
Context2D
是QML中负责2d绘图的对象,两种使用Context2D的方式
- 信号处理器中调用getContext(“2d”)获取
- 设置Canvas的contentType属性为"2d",之后可直接利用context属性进行绘制
8.2 绘制路径
绘制路径的一般步骤:
- 调用beginPath()
- 调用moveTo()、lineTo()、arcTo()、rect()、quadraticCurveTo()、arc()、bezierCurveTo()等构造路径元素的方法
- 调用fill()或stroke()
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.strokeStyle = "green"
ctx.rect(x, y, width, height)
ctx.stroke()
ctx.lineWidth = 2
ctx.strokeStyle = "red"
var gradient = ctx.createLinearGradient(60, 50, 180, 130)
gradient.addColorStop(0.0, Qt.rgba(1, 0, 0, 1.0))
gradient.addColorStop(1.0, Qt.rgba(0, 1, 0, 1.0))
ctx.fillStyle = gradient
ctx.beginPath()
ctx.rect(60, 50, 120, 80)
ctx.fill()
ctx.stroke()
gradient = ctx.createRadialGradient(230, 160, 30, 260, 200, 20)
gradient.addColorStop(0.0, Qt.rgba(1, 0, 0, 1.0))
gradient.addColorStop(1.0, Qt.rgba(0, 0, 1, 1.0))
ctx.fillStyle = gradient
ctx.beginPath()
ctx.rect(200, 140, 80, 80)
ctx.fill()
ctx.stroke()
}
}
画刷相关:
fillStyle
和QBrush类似,用于设置填充图元的画刷样式
fill()
方法使用画刷并携带fillstyle
保存的颜色来填充路径
画笔相关:
lineWidth
设置画笔的宽度
strokeStyle
和QPen类似,用于设置描绘图元边框的画笔样式
stroke()
方法使用画笔并携带strokeStyle
保存的颜色描画路径的边框
样式:
样式可以是一个颜色值,或CanvasGradient
或CanvasPattern
对象
createLinearGradient()
方法用于创建一个线性渐变对象(类型为CanvasGradient)
createRadialGradient()
方法用于创建一个放射渐变对象(类型为CanvasGradient)
CanvasGradient
对象的addColorStop()
方法可以添加渐变路径上的关键点的颜色
路径相关:
moveTo()
设置一个下一个路径点
closePath()
结束当前路径,从最后一次moveTo()
的点到第一次moveTo()
的点画一条直线封闭路径
quadraticCurveTo()
二次贝塞尔曲线
bezierCurveTo()
三次贝塞尔曲线
arc()、arcTo()
弧线
ellipse()
:椭圆
text()
:文字
//绘制三角形
Canvas {
anchors.fill: parent
contextType: "2d"
onPaint: {
context.lineWidth = 2
context.strokeStyle = "red"
context.fillStyle = "blue"
context.beginPath()
context.moveTo(100, 80)
context.lineTo(100, 200)
context.lineTo(300, 200)
context.closePath()
context.fill()
context.stroke()
}
}
8.3 绘制文本
filltext()
:使用fillStyle填充文字
strokeText()
:使用strokeStyle描绘文字边框
text()
:将一串文本添加作为路径
font
:属性,设置字体,举例"italic bold 32pt serif",顺序为
font-style
(可选),可以取normal、italic、oblique三值之一。font-variant
(可选),可以取normal、small-caps二值之一。font-weight
(可选),可以取normal、bold二值之一,或0~99的数字。font-size
,取Npx或Npt,其中N为数字,px代表像素,pt代表点,对于移动设备,使用pt为单位更合适一些,能够适应各种屏幕尺寸。font-family
,常见的有serif、sans-serif、cursive、fantasy 、 monospace
ctx.beginPath()
ctx.text("Stroke Text on Path", 10, 10)
ctx.stroke()
//相当于
ctx.strokeText("Stroke Text on Path", 10, 10)
ctx.beginPath()
ctx.text("Fill Text on Path", 10, 10)
ctx.fill()
//相当于
ctx.fillText("Fill Text on Path", 10, 10)
8.4 绘制图片
drawImage(variant image, real dx, real dy)
在(dx, dy)位置绘制指定image对象所代表的图片
//通过Canvas提供的loadImage方法加载绘制图片
Window {
width: 600
height: 300
visible: true
Canvas {
anchors.fill: parent
id: root
property string dartlikeWeapon: "file:C:\\Users\\admin\\Desktop\\aaa.png"
onPaint: {
var ctx = getContext("2d")
ctx.drawImage(dartlikeWeapon, 0, 0)
}
Component.onCompleted: {
loadImage(dartlikeWeapon)
}
onImageLoaded: {
requestPaint()
}
}
}
//通过Image对象,来进行绘制
Canvas {
id: root
anchors.fill: parent
Image {
id: poster
source: "file:c:\\users\\admin\\desktop\\aaa.png"
visible: false
onStatusChanged: {
if (status == Image.Ready) {
root.requestPaint()
}
}
}
onPaint: {
var ctx = getContext("2d")
ctx.drawImage(poster, 50, 0)
}
}
可以通过drawImage
直接绘制CanvasImageData
对象。
CanvasImageData
对象使用一维数组,按照RGBA顺序保存图像数据
通过context.createImageData()
可将imageurl转化为CanvasImageData
对象
Canvas {
id: randomImageData
width: 120
height: 100
contextType: "2d"
property var imageData: null
onPaint: {
if (imageData == null) {
imageData = context.createImageData(120, 100)
for (var i = 0; i < 48000; i += 4) {
imageData.data[i] = Math.floor(Math.random() * 255)
imageData.data[i + 1] = Math.floor(Math.random() * 255)
imageData.data[i + 2] = Math.floor(Math.random() * 255)
imageData.data[i + 3] = 255
}
}
context.drawImage(imageData, 0, 0)
}
}
8.5 变换
Canvas {
width: 300;
height: 300;
contextType: "2d";
onPaint: {
context.lineWidth = 2;
context.strokeStyle = "blue";
context.fillStyle = "red";
context.save();
context.translate(width/2, height/2);
context.beginPath();
context.arc(0, 0, 30, 0, Math.PI*2);
context.arc(0, 0, 50, 0, Math.PI*2);
context.arc(0, 0, 70, 0, Math.PI*2);
context.arc(0, 0, 90, 0, Math.PI*2);
context.stroke();
context.restore();
context.save();
context.translate(width/2, 30);
context.font = "26px serif";
context.textAlign = "center";
context.fillText("concentric circles", 0, 0);
context.restore();
}
}
translate()
平移画布
rotate()
旋转画布
scale()
缩放画布
shear()
错切画布
setTransform
对画布进行矩阵变换
save()
保存当前的画布状态
restore()
恢复之前保存的画布状态
8.6 裁切
Canvas {
width: 480
height: 400
contextType: "2d"
property var comicRole: "file:d:\\aaa.png"
onPaint: {
context.lineWidth = 2
context.strokeStyle = "blue"
context.fillStyle = Qt.rgba(0.3, 0.5, 0.7, 0.3)
context.save()
context.beginPath()
context.arc(180, 150, 80, 0, Math.PI * 2, true)
context.moveTo(180, 230)
context.lineTo(420, 280)
context.lineTo(160, 320)
context.closePath()
context.clip()
context.drawImage(comicRole, 0, 0, 600, 600, 0, 0, 400, 400)
context.stroke()
context.fill()
context.rotate(Math.PI / 5)
context.font = "italic bold 32px serif"
context.fillStyle = "red"
context.fillText("the text will be clipped!", 100, 70)
context.restore()
}
Component.onCompleted: loadImage(comicRole)
onImageLoaded: requestPaint()
}
clip()
方法可以根据当前路径包围起来的区域来裁切后续的绘图操作,类似于蒙版。
使用步骤:
- 调用beginPath()
- 使用lineTo()、arc()、bezierCurveTo()、moveTo()、closePath()等创建路径
- 调用clip()确定裁切区域
- 进行正常绘图
8.7 图像合成
Context2D允许绘制一个图元,将其与已有的图像按照globalCompositeOperation
属性指定的模式合成
source-over
,默认模式,新图形覆盖在原有内容之上。
source-in
,新图形中仅仅出现与原有内容重叠的部分,其他区域都变成透明的。
source-out
,只有新图形中与原有内容不重叠的部分会被绘制出来。
source-atop
,新图形中与原有内容重叠的部分会被绘制,并覆盖于原有内容之上。
destination-over
,在原有内容之下绘制新图形。
destination-in
,原有内容中与新图形重叠的部分会被保留,其他区域都变成透明的。
destination-out
,原有内容中与新图形不重叠的部分会被保留。
destination-atop
,原有内容中与新内容重叠的部分会被保留,并会在原有内容之下绘制新图形。
lighter
,两图形中的重叠部分做加色处理。
copy
,只有新图形会被保留,其他都被清除掉。
xor
,重叠部分会变成透明的。