目录
一、概述
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: 640
height: 480
visible: true
title: "QML基础组件演示"
color: "#2c3e50" // 窗口背景色
// Rectangle是一个矩形组件,是构建UI的基本形状
Rectangle {
id: blueRect
width: 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组件一个id
text: "点击我!"
// ... (其他属性保持不变)
}
// MouseArea是一个不可见的组件,专门用于处理鼠标事件
MouseArea {
anchors.fill: parent // 让MouseArea完全覆盖父组件(蓝色矩形)
// onClicked是一个信号处理器,当鼠标点击时,这里的代码会被执行
onClicked: {
// 修改蓝色矩形的颜色
blueRect.color = (blueRect.color == "#3498db" ? "#e74c3c" : "#3498db");
// 修改文本内容
infoText.text = "颜色改变了!";
}
}
}
// Button是Qt Quick Controls提供的预制按钮组件
Button {
id: resetButton
text: "重置"
width: 100
// 将按钮锚定在窗口底部,水平居中
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.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: rootWindow
width: 960
height: 720
visible: true
title: qsTr("AI螺丝瑕疵检测系统 V1.0")
color: "#1e2a38"
// 使用一个ColumnLayout作为根布局,使其内部组件垂直排列
ColumnLayout {
anchors.fill: parent // 填充整个窗口
anchors.margins: 10 // 设置边距
spacing: 10 // 设置子组件之间的间距
// --- 1. 图像显示区 ---
// 使用Frame组件来创建一个带边框和背景的容器
Frame {
id: videoFrame
Layout.fillWidth: true // 宽度填充父布局
Layout.fillHeight: true // 高度填充父布局,权重为1(默认)
padding: 0
background: Rectangle {
color: "#101a24" // 一个更深的背景色
}
// 这里暂时用一个文本来占位,后续将替换为真实的视频画面
Text {
text: "实时视频显示区"
color: "#555"
font.pixelSize: 24
anchors.centerIn: parent
}
}
// --- 2. 结果展示区 ---
Frame {
id: resultFrame
Layout.fillWidth: true
Layout.preferredHeight: 150 // 固定高度
background: Rectangle {
color: "#2c3e50"
}
Text {
text: "检测结果列表区"
color: "#7f8c8d"
anchors.centerIn: parent
}
}
// --- 3. 控制区 ---
// 使用RowLayout使内部按钮水平排列
RowLayout {
id: controlBar
Layout.fillWidth: true
Layout.preferredHeight: 50
Layout.alignment: Qt.AlignHCenter // 整体居中对齐
spacing: 20
Button {
id: startButton
text: "开始检测"
Layout.preferredWidth: 120
Layout.preferredHeight: 40
}
Button {
id: stopButton
text: "停止"
Layout.preferredWidth: 120
Layout.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架构思想,并实践连接前后端的关键技术。