《qt quick核心编程》笔记二

5 Qt Quick事件处理

信号共有两类:

  1. 由用户输出产生,如按键、鼠标、触摸屏、传感器等
  2. 由对象状态或属性变化产生的,比如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()
		}
	}
}
  1. onClicked:信号处理器等价于Qt Widget中的槽
  2. { Qt.quick() }:ECMAScript中的代码块,理解为匿名函数
  3. 信号处理器的形式一般为on<Signal>这种形式
  4. 信号处理器位于拥有信号的元素内部,当元素信号发射时处理器被调用

5.2 附加信号处理器

import QtQuick 2.2

Rectangle {
	width: 320
	height: 480
	color: "gray"
	focus: true
	Keys.enabled: true
	Keys.onEscapePressed: {
		Qt.quit()
	}
}
  1. 附加信号处理器位于元素内部,但处理的信号并非当前元素发出,而是来自其他元素,如按键Keys
  2. 附加信号处理器的形式为<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);
}
  1. 只有部分属性变化时,才会发射信号
  2. 有的<property>Changed有参数,有的没有参数,没有统一的规则
  3. 属性变化时,会自动发出一个信号,形如<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
		}
	}
}
  1. MouseArea是专门处理鼠标的Item,具有一个pressed信号
  2. Loader是专门用来动态创建组件的,可以从QML文件中创建组件,也可以通过指定sourceComponent属性为要创建的组件id
  3. Loader具有一个信号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
  1. 信号其实也是一个对象
  2. 触发信号的方法relay.messageReceived("Tom", "Happy Birthday"),像函数一样调用即可
  3. 信号对象具有一个connect()方法,用于连接触发信号时,调用设置的槽
  4. 可以对一个信号调用多个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"
			}
		}
	}
}
  1. MouseArea对象可以附加到一个Item上提供处理鼠标事件,本身是一个不可见的Item
  2. MouseArea的属性enabled用来控制是否处理鼠标事件,默认为true
  3. MouseArea的属性acceptedButtons属性设定接受那些鼠标按键产生的事件(左键、右键、中键)
  4. MouseArea作为一个Item本身也是有一个大小范围的,这决定了有效的鼠标区域
  5. 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
		}
	}
}
  1. Keys对象是Qt Quick提供以处理键盘事件的对象
  2. Keys具有很多针对特定按键的信号returnPressed 、escapePressed、downPressed、backPressed等
  3. Keys还定义了普适性的按键信号,pressed和released信号,均具有一个类型KeyEvent的event参数
  4. 如果一个按键被处理了,最好将event.accepted参数设为true,以免信号继续传递给其他Item,发生奇怪的问题
  5. Keys的enabled属性,代表是否处理按键,默认为true
  6. Keys的forwardTo属性,一个列表,表示按键事件应该依次传递给列表中的对象,如果某个对象accepted了按键信息,那么列表中这个对象后面的对象均不会收到按键事件了
  7. keys的priority属性,表示设置Keys附加属性的优先级,Item之前处理按键,Item之后处理按键
  8. 如果想要让某个元素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()
			}
		}
	}
}
  1. 定时器interval属性,指定触发周期,单位是毫秒,默认1000
  2. 定时器repeat属性,指定定时器是周期触发,还是只触发一次,默认一次性
  3. 定时器running属性,true定时器启动,false定时器停止,默认false,可通过start()stop()restart()设置
  4. 定时器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
		}
	}
}
  1. 在一个QML文档中嵌入Component需要使用Component对象来定义
  2. Compoment并不是Item的派生类,虽然它可以通过自己的顶层Item来为其他view提供可视化组件,但其本身是不可见的元素
  3. 定义一个Component和定义一个QML文档类似,只能包含一个顶层Item,且在这个Item以外不能定义任何的数据,除了id。
  4. 要显示出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)
	}
}
  1. 在单独的qml文件中定义组件,无需Component对象。只有在其他qml中嵌入组件才会需要Commponent
  2. 定义组件时,可以使用Item或其派生类作为组件的根Item
  3. 上述代码使用了两种自定义信号方式,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中的组件)
  1. 当source或sourceComponent属性发生变化时,它之前加载的Component会自动销毁,然后加载新对象
  2. 将source设置为一个空字符串,或将sourceComponent设置为undefined,将会销毁当前加载的对象
  3. Loader的Item属性指向加载的顶层Item,如Loader加载了ColorPicker组件,则Item指向Rectangle
  4. Loader和它所加载的Item始终具有相同的尺寸,优先使用Loader自身所指定的大小
  5. 如果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标记的富文本

  1. 通过font分组属性设置元素的字体、大小、粗细、斜体、下划线等
  2. 通过text可设置或获取元素的文本
  3. 通过horizontalAlignment和verticalAlignment可设定文本的对齐方式
  4. 通过wrapMode设置文本的换行策略
  5. 通过color设置文本的颜色
  6. 通过contentWidth、contentHeight获取文本的宽和高
  7. 通过length属性获取编辑框内的字符个数
  8. 通过maximumLength可设置允许输入的最大长度
  9. 通过cursorDelegate可定制光标外观,cursorPosition设置或返回光标位置,cursorVisible设置或返回光标的可见状态 ,cursorRectangle可获取光标所在矩形和定制光标外观有关
  10. 通过echoMode可设置显示方式,用于密码框
  11. TextInput还支持canPaste粘贴、canUndo撤销、canRedo重做、autoScroll滚动特性
  12. 通过selectByMouse属性设置为true来允许用户通过鼠标选择文字
  13. 当用户按下回车或确认键,或者编辑框失去焦点时,会发出accepted和editingFinished信号(如果设置了inputMask或validator则只有当文本符合限制条件时,信号才会触发)
  14. 限制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本身是不可见元素,用于将若干个元素组合在一起,以供用户选择其中的一项
两种实现方式:

  1. 在ExclusiveGroup对象中定义RadioButton、CheckBox、Action等元素,不要设置它们的exclusiveGroup属性
  2. 只定义一个只设置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()
		}
	}
}

步骤如下:

  1. 定义一个Convas对象,设置width、height
  2. 定义onPaint信号处理器
  3. 获取Context2D对象
  4. 实际的绘图操作

paint是Canvas的信号,当需要绘图(更新)时触发
Context2D是QML中负责2d绘图的对象,两种使用Context2D的方式

  • 信号处理器中调用getContext(“2d”)获取
  • 设置Canvas的contentType属性为"2d",之后可直接利用context属性进行绘制

8.2 绘制路径

绘制路径的一般步骤:

  1. 调用beginPath()
  2. 调用moveTo()、lineTo()、arcTo()、rect()、quadraticCurveTo()、arc()、bezierCurveTo()等构造路径元素的方法
  3. 调用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保存的颜色描画路径的边框

样式:
样式可以是一个颜色值,或CanvasGradientCanvasPattern对象
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,重叠部分会变成透明的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值