《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——3. QML入门:像搭积木一样构建UI
目录
- 一、概述
- 1.1 背景介绍:从后端逻辑到前端界面
- 1.2 学习目标
- 二、QML基础入门
- 2.1 QML语法与核心组件
- 2.2 响应用户输入:`MouseArea`与`Button`
- 三、项目UI骨架搭建
- 四、总结与展望
一、概述
1.1 背景介绍:从后端逻辑到前端界面
在上一篇文章中,我们深入了C++的“后厨房”,掌握了构建程序“大脑”和“骨骼”的核心技能,包括类、对象以及Qt独有的信号与槽通信机制。一个功能强大的后端逻辑已经蓄势待发。然而,对于最终用户而言,他们直接与之交互的是软件的“面孔”——用户界面(User Interface, UI)。
本篇文章的核心任务,就是为我们强大的C++后端,打造一个现代化、美观且响应迅速的UI。我们将正式从ScrewDetector
项目入手,学习Qt Quick技术的核心——QML语言。QML是一种声明式的语言,它允许开发者像“搭积木”一样快速、直观地构建界面,并将界面的外观与程序的逻辑彻底分离。
1.2 学习目标
通过本篇的学习,读者将能够:
- 理解QML的基本语法和核心概念。
- 熟练使用最常用的QML组件来构建静态界面。
- 掌握QML中的布局技巧,实现响应式的界面设计。
- 最终完成
ScrewDetector
项目主界面的静态布局搭建。
二、QML基础入门
从本章开始,我们将回到在第1篇文章中创建的ScrewDetector
项目,并主要在Main.qml
文件中进行工作。
2.1 QML语法与核心组件
【核心概念:声明式的UI描述】
与C++这种命令式语言(告诉计算机“如何做”)不同,QML是一种声明式语言,它更侧重于描述“是什么”。在QML中,通过层层嵌套的**组件(Item)来描述界面的结构,并用属性(Property)**来定义每个组件的外观和行为。
【例3-1】 基础组件与属性。
我们将从最基础的组件开始,熟悉QML的语法。
1. 打开项目
- 打开第一章创建的
ScrewDetector
项目。
2. 编写代码 (Main.qml)
- 用以下代码替换
Main.qml
的全部内容:
import QtQuick// Window是所有桌面应用的根组件,代表一个窗口
Window {id: rootWindow // 为根组件指定一个id,方便内部引用width: 640height: 480visible: truetitle: "QML基础组件演示"color: "#2c3e50" // 窗口背景色// Rectangle是一个矩形组件,是构建UI的基本形状Rectangle {id: blueRectwidth: 200 // 宽度height: 100 // 高度color: "#3498db" // 矩形颜色// anchors是一种强大的布局方式,这里让矩形在父组件(窗口)中居中anchors.centerIn: parent// Text组件用于显示文本Text {text: "这是一个蓝色矩形" // 显示的文本内容color: "white" // 文本颜色font.bold: true // 字体加粗font.pixelSize: 16 // 字体大小(像素)// 让文本在父组件(蓝色矩形)中居中anchors.centerIn: parent}}
}
3. 运行结果
单击运行按钮,将看到一个深色背景的窗口,中央有一个带有文字的蓝色矩形。
关键代码分析:
(1) import QtQuick
: 类似于C++的#include
,用于导入包含基础QML组件的模块。
(2) 组件层级: QML代码通过大括号{}
形成层级结构。Text
组件被定义在Rectangle
组件内部,意味着Text
是Rectangle
的子组件。
(3) id
属性: id
是一个特殊的属性,它为组件提供一个在当前QML文件内唯一的名称,方便其他组件引用它。例如,anchors.centerIn: parent
中的parent
就隐式地引用了其父组件的id
。
(4) 属性绑定: width: 200
这种 属性名: 值
的语法称为属性赋值。QML更强大的地方在于属性绑定,如果一个属性的值依赖于另一个属性,当后者改变时,前者会自动更新。我们将在后续章节深入了解。
(5) anchors
布局: anchors
(锚)是一种相对布局系统。anchors.centerIn: parent
表示将当前组件的中心点与父组件的中心点对齐。
2.2 响应用户输入:MouseArea
与Button
一个静态的界面是不够的,UI需要能够响应用户的操作。
【例3-2】 添加交互功能。
1. 编写代码 (Main.qml)
- 在上一个示例的基础上,为蓝色矩形添加一个
MouseArea
,并额外添加一个Button
组件。
import QtQuick
import QtQuick.Controls // Button组件需要导入此模块Window {id: rootWindow// ... (属性保持不变)Rectangle {id: blueRect// ... (属性保持不变)Text {id: infoText // 给Text组件一个idtext: "点击我!"// ... (其他属性保持不变)}// MouseArea是一个不可见的组件,专门用于处理鼠标事件MouseArea {anchors.fill: parent // 让MouseArea完全覆盖父组件(蓝色矩形)// onClicked是一个信号处理器,当鼠标点击时,这里的代码会被执行onClicked: {// 修改蓝色矩形的颜色blueRect.color = (blueRect.color == "#3498db" ? "#e74c3c" : "#3498db");// 修改文本内容infoText.text = "颜色改变了!";}}}// Button是Qt Quick Controls提供的预制按钮组件Button {id: resetButtontext: "重置"width: 100// 将按钮锚定在窗口底部,水平居中anchors.bottom: parent.bottomanchors.horizontalCenter: parent.horizontalCenteranchors.bottomMargin: 20// 按钮的点击事件处理器onClicked: {blueRect.color = "#3498db";infoText.text = "点击我!";}}
}
2. 运行结果
现在,窗口下方出现了一个“重置”按钮。
- 单击蓝色矩形,它的颜色会在蓝色和红色之间切换,并且文本会改变。
- 单击“重置”按钮,矩形会恢复到初始的蓝色和文本。
关键代码分析:
(1)import QtQuick.Controls
: 像Button
,Slider
,Frame
等更高级的、带样式的控件,都位于此模块中,需要单独导入。
(2)MouseArea
: 这是一个非常核心的组件,用于为任何非交互式组件(如Rectangle
,Image
)添加鼠标响应能力。它本身是透明不可见的。
(3) 信号处理器:onClicked
就是一个信号处理器。它的命名规则是on
+信号名
(首字母大写)。当MouseArea
或Button
发出clicked
信号时,对应的onClicked
块内的JavaScript代码就会被执行。
(4) JavaScript代码: 在信号处理器中,可以编写简单的JavaScript代码来改变其他组件的属性,实现动态交互。这里的(条件 ? 值1 : 值2)
是一个三元运算符,是if-else
的简洁写法。
三、项目UI骨架搭建
现在,我们具备了搭建静态界面的基础知识。是时候开始构建ScrewDetector
项目的主界面了。
根据项目需求,主界面可以划分为三个区域:
- 图像显示区:占据大部分空间,用于显示一个实时视频画面。
- 结果展示区:位于视频下方,用于显示检测结果的列表。
- 控制区:位于最下方,包含“开始检测”、“停止”等操作按钮。
我们将使用ColumnLayout
(垂直布局)和RowLayout
(水平布局)来组织这些区域。布局组件可以自动管理其子组件的位置和大小,非常适合创建响应式界面。
【例3-3】 ScrewDetector
主界面静态布局。
1. 编写代码 (Main.qml)
- 用以下代码再次替换
Main.qml
的全部内容。
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts // 布局组件需要导入此模块Window {id: rootWindowwidth: 960height: 720visible: truetitle: qsTr("AI螺丝瑕疵检测系统 V1.0")color: "#1e2a38"// 使用一个ColumnLayout作为根布局,使其内部组件垂直排列ColumnLayout {anchors.fill: parent // 填充整个窗口anchors.margins: 10 // 设置边距spacing: 10 // 设置子组件之间的间距// --- 1. 图像显示区 ---// 使用Frame组件来创建一个带边框和背景的容器Frame {id: videoFrameLayout.fillWidth: true // 宽度填充父布局Layout.fillHeight: true // 高度填充父布局,权重为1(默认)padding: 0background: Rectangle {color: "#101a24" // 一个更深的背景色}// 这里暂时用一个文本来占位,后续将替换为真实的视频画面Text {text: "实时视频显示区"color: "#555"font.pixelSize: 24anchors.centerIn: parent}}// --- 2. 结果展示区 ---Frame {id: resultFrameLayout.fillWidth: trueLayout.preferredHeight: 150 // 固定高度background: Rectangle {color: "#2c3e50"}Text {text: "检测结果列表区"color: "#7f8c8d"anchors.centerIn: parent}}// --- 3. 控制区 ---// 使用RowLayout使内部按钮水平排列RowLayout {id: controlBarLayout.fillWidth: trueLayout.preferredHeight: 50Layout.alignment: Qt.AlignHCenter // 整体居中对齐spacing: 20Button {id: startButtontext: "开始检测"Layout.preferredWidth: 120Layout.preferredHeight: 40}Button {id: stopButtontext: "停止"Layout.preferredWidth: 120Layout.preferredHeight: 40}}}
}
2. 运行结果
一个结构清晰、布局合理的界面框架就完成了。无论如何拖动改变窗口大小,各个区域都会按比例自动调整,保持美观。
关键代码分析:
(1) Layout
附加属性: 当一个组件被放置在布局(如ColumnLayout
)内部时,可以使用Layout.
前缀的附加属性来控制其在布局中的行为。Layout.fillWidth: true
表示让组件的宽度自动填满布局的可用宽度。Layout.preferredHeight
则指定了一个期望的高度。
(2) Frame
: 这是一个带背景和可选边框的容器,非常适合用于对UI元素进行分组和区域划分。
(3) 布局的组合: 通过ColumnLayout
和RowLayout
的嵌套组合,可以构建出几乎任何复杂的界面布局,并且代码结构非常清晰。
四、总结与展望
在本篇文章中,我们正式踏入了QML的世界。通过学习基础组件、交互处理和布局系统,我们成功地为ScrewDetector
项目搭建了一个专业、响应迅速的UI骨架。
至此,我们已经分别掌握了C++后端逻辑和QML前端界面的基础。但目前,它们仍然是两个独立的世界。如何将它们连接起来,让QML的按钮能够触发C++的复杂操作,让C++的计算结果能够显示在QML的界面上?这将是我们下一篇文章的核心主题。
在下一篇文章**【《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——4. 前后端联动:打通QML与C++的任督二脉】**中,我们将探索MVVM架构思想,并实践连接前后端的关键技术。