QML 常用控件(二)
QML(Qt Modeling Language)作为Qt框架中用于构建用户界面的声明式语言,提供了丰富多样的控件来满足各种交互需求。本文将深入探讨QML中常用的基础控件,包括ProgressBar、BusyIndicator、ComboBox、Dial、Slider、SpinBox、Tumbler、TextField和TextArea。
进度条控件:ProgressBar
ProgressBar是QML中最常用的进度指示控件,用于向用户展示任务的完成情况。根据Qt官方文档,ProgressBar控件提供了一种直观的方式来显示操作的进度,无论是确定进度还是不确定进度的情况都能很好地支持。
核心属性解析:
value
:当前进度值,范围在from
和to
之间from
/to
:进度范围的最小值和最大值(默认0.0-1.0)orientation
:进度条方向(水平或垂直)indeterminate
:是否启用不确定模式(用于未知进度时显示动画)
基础使用示例:
import QtQuick 2.15
import QtQuick.Controls 2.15ProgressBar {id: progressBarwidth: 200value: 0.5 // 当前进度50%from: 0to: 1
}
动态进度更新是ProgressBar的常见应用场景,通常结合Timer或后台任务实现:
Timer {interval: 100running: truerepeat: trueonTriggered: {if(progressBar.value < 1) progressBar.value += 0.01elseprogressBar.value = 0}
}
样式定制是ProgressBar的高级用法,通过修改background
和contentItem
可以完全自定义外观:
ProgressBar {id: customProgressBarvalue: 0.75background: Rectangle {implicitWidth: 200implicitHeight: 6color: "#e0e0e0"radius: 3}contentItem: Item {implicitWidth: 200implicitHeight: 6Rectangle {width: customProgressBar.visualPosition * parent.widthheight: parent.heightradius: 3color: "#21be2b"}}
}
在实际应用中,ProgressBar经常需要与其他控件联动。例如,与Slider结合实现进度调节:
Column {spacing: 20anchors.centerIn: parentProgressBar {width: 300value: slider.value}Slider {id: sliderwidth: 300from: 0to: 1value: 0.5}
}
对于不确定进度的场景(如网络请求),可以使用不确定模式:
ProgressBar {indeterminate: truewidth: 200
}
最佳实践建议:
- 对于长时间操作,始终提供进度反馈
- 确定进度尽量提供百分比数值
- 不确定进度使用动画让用户知道系统仍在工作
- 根据应用主题定制进度条样式以保持UI一致性
- 移动设备上考虑增加触摸交互区域
ProgressBar的性能优化主要涉及大数据量更新时的节流处理,避免过于频繁的界面重绘。在QML中,可以使用Qt.binding或定时批量更新来优化性能。
忙碌指示器:BusyIndicator
BusyIndicator是QML中用于表示应用程序正在处理任务的视觉组件,当应用执行耗时操作时,它通过旋转动画提示用户等待,防止用户误以为程序无响应。与ProgressBar不同,BusyIndicator不显示具体进度,而是通过持续的动画效果表明后台正在工作。
核心属性:
running
:布尔值,控制动画是否激活(默认true)visible
:控制组件是否显示padding
:内边距,影响指示器与边界的距离
基本使用方法非常简单:
import QtQuick 2.15
import QtQuick.Controls 2.15BusyIndicator {running: trueanchors.centerIn: parent
}
全屏覆盖实现是BusyIndicator的常见应用场景,模拟类似Android中ProgressDialog的效果:
ApplicationWindow {id: mainWindowvisible: truewidth: 400height: 300Rectangle {id: overlayanchors.fill: parentcolor: Qt.rgba(0, 0, 0, 0.5) // 半透明黑色背景visible: falseBusyIndicator {anchors.centerIn: parentrunning: parent.visible}MouseArea {anchors.fill: parentonClicked: {} // 阻止点击穿透}}Button {text: "开始任务"onClicked: overlay.visible = true}
}
自定义样式可以通过替换contentItem实现。默认的BusyIndicator使用系统主题样式,但我们可以完全自定义:
BusyIndicator {id: busyIndicatoranchors.centerIn: parentcontentItem: Item {implicitWidth: 64implicitHeight: 64Item {id: containeranchors.fill: parentopacity: busyIndicator.running ? 1 : 0RotationAnimator { //旋转的核心动画target: containerfrom: 0to: 360 //从0°到360°无限循环旋转duration: 1000loops: Animation.Infiniterunning: busyIndicator.visible && busyIndicator.running}Repeater { //重复器id: repeatermodel: 8 //生成8个圆点(model: 8),每个圆点通过Rectangle实现Rectangle {x: container.width / 2 - width / 2y: container.height / 2 - height / 2width: 10height: 10radius: 5color: "#21be2b"transform: [Translate {y: -container.height * 0.4 //将圆点沿Y轴向上移动容器高度的40%(y: -container.height * 0.4),形成环形布局},Rotation {angle: index * 45 //每个圆点按索引(index)旋转45°(angle: index * 45),使8个圆点均匀分布在圆周上origin.x: 5origin.y: 5 //元素会围绕(5,5)这个点旋转}]}}}}}
应用场景:
- 网络请求等待期间
- 文件读写操作时
- 复杂计算过程中
- 页面加载或初始化时
- 数据库查询执行时
性能注意事项:
- 避免同时显示多个BusyIndicator
- 不需要时及时设置为running: false以节省资源
- 动画复杂度影响CPU使用率,移动设备上需特别注意
- 考虑低端设备的性能,提供简化动画选项
BusyIndicator与ProgressBar的选择取决于场景:当可以计算进度百分比时使用ProgressBar,当无法确定完成时间时使用BusyIndicator。两者也可以结合使用,如主进度使用ProgressBar,子任务使用BusyIndicator。
组合选择控件:ComboBox
ComboBox是QML中常用的下拉选择控件,它结合了按钮和弹出列表的特点,为用户提供了一种节省空间的方式来从预定义选项中进行选择。ComboBox在表单填写、设置选项等场景中广泛应用,能够有效提升用户交互体验。
基本属性:
model
:数据模型,可以是ListModel、数组或整数currentIndex
:当前选中项的索引currentText
:当前选中项的文本editable
:是否允许编辑(可输入非列表项)displayText
:控件上显示的文本(可自定义格式)
基础示例:
import QtQuick
import QtQuick.Controls ComboBox {x: 20y: 20// editable: truemodel: ListModel {id: modelListElement { text: "Banana" }ListElement { text: "Apple" }ListElement { text: "Coconut" }}}
动态模型更新是实际开发中的常见需求:
ComboBox {id: dynamicCombowidth: 200model: []Component.onCompleted: {// 模拟异步加载Qt.callLater(function() {dynamicCombo.model = ["动态加载1", "动态加载2", "动态加载3"]})}Button {anchors.left: parent.righttext: "添加选项"onClicked: {if(dynamicCombo.model instanceof Array) {var newModel = dynamicCombo.model.slice()newModel.push("新选项" + (newModel.length + 1))dynamicCombo.model = newModel}}}
}
分组显示可以提升复杂选项的可浏览性:
ComboBox {width: 250height: 30model: [{ display: "水果", isGroup: true },{ display: "苹果", isGroup: false },{ display: "香蕉", isGroup: false },{ display: "蔬菜", isGroup: true },{ display: "胡萝卜", isGroup: false },{ display: "西红柿", isGroup: false }]displayText: currentIndex >= 0 ? model[currentIndex].display : "请选择"currentIndex: -1delegate: ItemDelegate {width: parent.widthheight: modelData.isGroup ? 30 : implicitHeightenabled: !modelData.isGrouphighlighted: comboBox.highlightedIndex === index && !modelData.isGroupcontentItem: Text {text: modelData.displayfont.bold: modelData.isGroupcolor: modelData.isGroup ? "#999999" : (highlighted ? "#ffffff" : "#333333")}}
}
自定义样式允许开发者完全控制ComboBox的外观:
ComboBox {id: controlmodel: ["经典风格", "现代风格", "简约风格"]// 下拉箭头indicator: Canvas {x: control.width - width - control.rightPaddingy: control.topPadding + (control.availableHeight - height) / 2width: 12height: 8contextType: "2d"Connections {target: controlfunction onPressedChanged() { indicator.requestPaint() }}onPaint: {var ctx = getContext("2d")ctx.reset()ctx.moveTo(0, 0)ctx.lineTo(width, 0)ctx.lineTo(width / 2, height)ctx.closePath()ctx.fillStyle = control.pressed ? "#17a81a" : "#21be2b"ctx.fill()}}// 背景background: Rectangle {implicitWidth: 120implicitHeight: 40border.color: control.pressed ? "#17a81a" : "#21be2b"border.width: control.visualFocus ? 2 : 1radius: 2}// 委托项delegate: ItemDelegate {width: control.widthhighlighted: control.highlightedIndex === indexcontentItem: Text {text: modelDatacolor: highlighted ? "#ffffff" : "#333333"font: control.fontelide: Text.ElideRightverticalAlignment: Text.AlignVCenter}background: Rectangle {color: highlighted ? "#21be2b" : "transparent"}}// 下拉弹出层popup: Popup {y: control.height - 1width: control.widthimplicitHeight: contentItem.implicitHeightpadding: 1contentItem: ListView {clip: trueimplicitHeight: contentHeightmodel: control.popup.visible ? control.delegateModel : nullcurrentIndex: control.highlightedIndexScrollIndicator.vertical: ScrollIndicator { }}background: Rectangle {border.color: "#21be2b"radius: 2}}
}
旋钮控件:Dial
Dial是QML中模拟物理旋钮的圆形交互控件,允许用户通过旋转手势调整数值。这种控件在音频处理、参数调整、仪表盘等场景中非常有用,能够提供直观的参数调节体验。
核心属性:
value
:当前旋钮的值(范围由from和to决定)from
/to
:最小值和最大值(默认0.0-1.0)stepSize
:步进值(调整时的最小单位)snapMode
:对齐模式(是否自动对齐步进值)wrap
:是否允许循环旋转inputMode
:输入模式(圆形或水平/垂直)angle
:当前旋钮的旋转角度(只读)
基础示例:
import QtQuick 2.15
import QtQuick.Controls 2.15Dial {id: dialwidth: 200height: 200from: 0to: 100value: 50stepSize: 1snapMode: Dial.SnapOnReleaseonValueChanged: console.log("当前值:", value.toFixed(1))Text {anchors.top: dial.bottomtext: "当前值: " + dial.value.toFixed(1)}
}
样式定制是Dial控件最具特色的方面,可以通过background和handle属性完全自定义外观:
Dial {id: controlwidth: 200height: 200anchors.centerIn: parentfrom: 0to: 100stepSize: 5snapMode: Dial.SnapAlwaysonValueChanged: {console.log("value: " + value)}background: Rectangle {x: control.width / 2 - width / 2y: control.height / 2 - height / 2implicitWidth: 140implicitHeight: 140width: Math.max(64, Math.min(control.width, control.height))height: widthcolor: "transparent"radius: width / 2border.color: control.pressed ? "#17a81a" : "#21be2b"opacity: control.enabled ? 1 : 0.3Label {anchors.centerIn: parenthorizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.pixelSize: 50font.bold: truecolor: "#21be2b"text: control.value}}handle: Rectangle { //手柄 即旋钮的那个小圆点id: handleItemx: control.background.x + control.background.width / 2 - width / 2y: control.background.y + control.background.height / 2 - height / 2width: 16height: 16color: control.pressed ? "#17a81a" : "#21be2b"radius: 8antialiasing: trueopacity: control.enabled ? 1 : 0.3transform: [Translate {y: -Math.min(control.background.width, control.background.height) * 0.4 + handleItem.height / 2},Rotation {angle: control.angleorigin.x: handleItem.width / 2origin.y: handleItem.height / 2}]}}
高级定制包括添加刻度标记和值显示,这需要使用Canvas绘制:
Dial {id: dialwidth: 200height: 200Canvas {anchors.fill: parentonPaint: {var ctx = getContext("2d")ctx.reset()// 绘制刻度ctx.strokeStyle = "#808080"ctx.lineWidth = 1var centerX = width / 2var centerY = height / 2var radius = Math.min(width, height) * 0.4// 主刻度for (var i = 0; i <= 10; i++) {var angle = (i * 30) * (Math.PI / 180)var x1 = centerX + radius * Math.sin(angle)var y1 = centerY - radius * Math.cos(angle)var x2 = centerX + (radius + 10) * Math.sin(angle)var y2 = centerY - (radius + 10) * Math.cos(angle)ctx.beginPath()ctx.moveTo(x1, y1)ctx.lineTo(x2, y2)ctx.stroke()// 刻度标签if (i % 2 == 0) {var labelX = centerX + (radius + 20) * Math.sin(angle)var labelY = centerY - (radius + 20) * Math.cos(angle)ctx.font = "12px Arial"ctx.textAlign = "center"ctx.textBaseline = "middle"ctx.fillText(i, labelX, labelY)}}}}Text {anchors.centerIn: parenttext: dial.value.toFixed(1)font.pixelSize: 20}
}
应用场景:
- 音频均衡器界面
- 仪表盘控制面板
- 图像处理参数调整
- 工业控制界面
- 游戏控制器设置
Dial控件的最佳实践包括提供足够的视觉反馈、确保触摸目标足够大、考虑添加辅助显示(如当前值)以及根据应用主题定制外观。通过合理的设计,Dial可以成为用户界面中最直观的参数调节方式之一。
滑动输入控件:Slider
Slider是QML中用于让用户通过拖动滑块选择数值的标准控件,适用于音量控制、亮度调节、进度指示等需要从连续或离散范围中选择值的场景。Qt Quick的Slider控件不仅功能丰富,还具有高度可定制性,能够满足各种应用场景的需求。
核心属性:
value
:当前滑块的值from
/to
:滑块的最小值和最大值(默认0-1)stepSize
:滑块的步进大小orientation
:滑块方向(水平或垂直)snapMode
:对齐模式(NoSnap/SnapOnRelease/SnapAlways)live
:拖动时是否实时更新valuepressed
:滑块是否正被交互visualPosition
:滑块的视觉位置(0.0-1.0)
基础示例:
import QtQuick 2.15
import QtQuick.Controls 2.15Slider {width: 200from: 0to: 100value: 50stepSize: 1onMoved: console.log("滑块值:", value.toFixed(1))
}
垂直滑块通过设置orientation属性实现:
Slider {orientation: Qt.Verticalheight: 200from: 0to: 100value: 50
}
样式定制是Slider最强大的特性之一,可以通过handle和background属性完全自定义外观:
Slider {id: sliderx: 100y: 50width: 300height: 20from: 0to: 100stepSize: 1onValueChanged: {console.log("value: " + value)// volumeLabel.text = value}background: Rectangle { //外层Rectangle定义了滑块的背景轨道x: slider.leftPaddingy: slider.topPadding + slider.availableHeight / 2 - height /2implicitWidth: 200implicitHeight: 12width: slider.availableWidthheight: implicitHeightradius: implicitHeight / 2color: "#bdbebf"Rectangle { //内层Rectangle作为滑块的填充指示器width: slider.visualPosition * parent.width height: parent.heightcolor: "#21be2b"radius: height / 2}}handle: Rectangle {x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)y: slider.topPadding + slider.availableHeight / 2 - height /2implicitWidth: 24implicitHeight: 24radius: implicitHeight / 2color: slider.pressed ? "#333333" : "#f0f0f0"border.color: "#bdbebf"}}Label {id: volumeLabelanchors.left: slider.rightanchors.leftMargin: 10anchors.verticalCenter: slider.verticalCenterfont.pixelSize: 32color: "#21be2b"text: slider.value}
添加刻度标记可以提升Slider的专业感和可用性:
Slider {id: sliderwidth: 300from: 0to: 100stepSize: 10snapMode: Slider.SnapAlwaysItem {width: parent.widthheight: 30Repeater {model: 11 // 0到100,每10一个刻度Rectangle {x: index * (slider.availableWidth / 10) - width/2y: 10width: 2height: 10color: "#808080"}Text {x: index * (slider.availableWidth / 10) - width/2y: 22text: (index * 10).toString()color: "#808080"font.pixelSize: 12}}}
}
应用场景:
- 音量控制面板
- 图像编辑参数调整
- 范围选择过滤器
- 播放进度控制
- 设置界面参数调节
Slider控件的最佳实践包括提供足够的视觉反馈、确保触摸目标足够大、考虑添加辅助显示(如当前值)以及根据应用主题定制外观。通过合理的设计,Slider可以成为用户界面中最直观的参数调节方式之一。
数值输入控件:SpinBox与Tumbler
SpinBox和Tumbler是QML中两种常用的数值输入控件,它们以不同的交互方式允许用户输入数值。SpinBox提供传统的增减按钮输入方式,而Tumbler则采用滚轮式选择界面,两者各有适用场景和优势。
SpinBox
SpinBox是用于数字输入的经典控件,支持整数和浮点数输入,适用于需要精确数值调整的场景。
核心属性:
value
:当前值from
/to
:取值范围stepSize
:步进大小editable
:是否允许直接编辑validator
:输入验证器up
/down
:增减按钮相关属性textFromValue
:自定义值显示格式的函数valueFromText
:从文本解析值的函数
基础示例:
import QtQuick 2.15
import QtQuick.Controls 2.15SpinBox {width: 100height: 30x: 50y: 50from: -20to: 20stepSize: 5onValueChanged: {console.log("value: " + value)}}
Tumbler
Tumbler是滚轮式选择器控件,提供直观的滚动选择体验,常用于日期、时间选择等场景。
核心属性:
model
:数据模型(数字、列表或自定义模型)currentIndex
:当前选中项的索引visibleItemCount
:可见项数量(奇数)wrap
:是否循环滚动delegate
:项的委托组件path
:自定义项的路径
基础示例:
import QtQuick 2.15
import QtQuick.Controls 2.15Tumbler {model: 10 // 显示0-9的数字width: 100height: 150
}
多列Tumbler组合(如时间选择器):
Row {spacing: 10Tumbler {id: hoursTumblermodel: 24width: 60currentIndex: 12}Tumbler {id: minutesTumblermodel: 60width: 60}Tumbler {id: amPmTumblermodel: ["AM", "PM"]width: 80}
}
应用场景对比:
特性 | SpinBox | Tumbler |
---|---|---|
交互方式 | 按钮增减/直接输入 | 滚轮选择 |
精度 | 支持小数 | 通常整数 |
空间占用 | 紧凑 | 需要更多空间 |
适用场景 | 精确数值输入 | 离散值/范围选择 |
移动友好性 | 一般 | 优秀 |
自定义难度 | 中等 | 较高 |
选择建议:
- 需要精确数值输入时选择SpinBox
- 需要快速范围选择时选择Tumbler
- 移动设备优先考虑Tumbler
- 紧凑界面考虑SpinBox
- 需要特殊视觉效果选择Tumbler
SpinBox和Tumbler都是强大的数值输入控件,选择取决于具体应用场景、目标平台和用户交互需求。通过合理的样式定制,两者都能完美融入应用的整体设计中。
文本编辑控件:TextField与TextArea
TextField和TextArea是QML中用于文本输入的核心控件,分别针对单行和多行文本输入场景设计。它们在表单填写、搜索框、文本编辑等交互中扮演着重要角色,是构建用户界面的基础组件。
TextField
TextField是单行文本输入控件,适用于用户名、密码、搜索词等简短文本输入场景。
核心属性:
text
:输入的文本内容placeholderText
:未输入时显示的提示文字echoMode
:文本显示模式(Normal/Password/NoEcho)validator
:输入验证器inputMask
:输入格式掩码maximumLength
:最大输入长度readOnly
:是否只读selectByMouse
:是否允许鼠标选择文本
基础示例:
import QtQuick 2.15
import QtQuick.Controls 2.15TextField {width: 200placeholderText: "请输入用户名..."onAccepted: console.log("输入完成:", text)
}
输入验证是TextField的重要功能,可以通过多种方式实现:
- 整数验证:
TextField {validator: IntValidator {bottom: 0top: 150}placeholderText: "请输入年龄(0-150)"
}
- 正则表达式验证:
TextField {validator: RegularExpressionValidator {regularExpression: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/}placeholderText: "请输入邮箱"
}
- 输入掩码:
TextField {inputMask: "9999-99-99"placeholderText: "请输入日期(YYYY-MM-DD)"
}
密码输入框通过echoMode实现:
TextField {echoMode: TextField.PasswordpasswordCharacter: "•"placeholderText: "请输入密码"
}
自定义样式:
TextField {width: 300height: 50anchors.centerIn: parentcolor: "black"font.pixelSize: 32verticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCenterleftPadding: 15rightPadding: 15placeholderText: "Enter" //预编辑文本echoMode: TextInput.PasswordonTextEdited: {console.log("#1 text: " + text)if(text === "123abc"){label.text = "Your password is right."}}onEditingFinished: {console.log("#2 text: " + text)}background: Rectangle {implicitWidth: parent.widthimplicitHeight: parent.heightradius: implicitHeight / 2color: "lightgray"border.width: 1border.color: "limegreen"}}
TextArea
TextArea是多行文本输入控件,适用于长文本输入、代码编辑等场景。
核心属性:
text
:文本内容placeholderText
:提示文字wrapMode
:换行模式(NoWrap/Wrap/WordWrap)textFormat
:文本格式(PlainText/RichText/AutoText)readOnly
:是否只读selectByMouse
:是否允许鼠标选择
基础示例:
import QtQuick
import QtQuick.ControlsWindow {width: 640height: 480visible: truetitle: qsTr("Hello World")ScrollView {width: 300height: 200anchors.centerIn: parentTextArea {width: parent.width - 10height: parent.height - 10anchors.centerIn: parentcolor: "blue"font.pixelSize: 32leftPadding: 10rightPadding: 10wrapMode: TextEdit.WrapselectionColor: "darkgreen"selectedTextColor: "red"placeholderText: "请输入你的消息"onTextChanged: {console.log("text: " + text)}background: Rectangle {implicitWidth: parent.widthimplicitHeight: parent.heightradius: 4color: "white"border.width: 1border.color: "limegreen"}}}}
应用场景对比:
特性 | TextField | TextArea |
---|---|---|
行数 | 单行 | 多行 |
滚动 | 自动水平滚动 | 需ScrollView支持 |
用途 | 简短输入 | 长文本编辑 |
性能 | 高效 | 大数据量需优化 |
验证 | 支持多种验证 | 基本验证 |
样式 | 较简单 | 复杂样式支持 |
性能优化技巧:
- 大数据集使用动态加载
- 考虑分页或虚拟滚动
- 避免频繁的文本分析操作
- 复杂语法高亮使用专业组件
TextField和TextArea是QML文本输入的基础,通过合理的使用和样式定制,可以构建出既美观又功能强大的文本输入界面,满足从简单表单到复杂文本编辑的各种需求。