【Qt】常用控件3——显示类控件
目录
一.Label
1.1.示例1——显⽰不同格式的⽂本
1.2.示例2——显示图片
1.3.示例3——⽂本对⻬,⾃动换⾏,缩进,边距
1.3.1.文本对齐
1.3.2.自动换行
1.3.3.缩进和边距
1.4.示例4——设置伙伴
二. LCD Number
2.1.示例1——基本示例
2.2.示例2——倒计时
2.2.1.问题一
2.2.2.问题二
三.ProgressBar
3.1.示例一:设置进度条按时间增长
3.2.前向声明和完整头文件
3.2.示例二:创建⼀个红⾊的进度条
四.Calendar Widget
4.1.示例一——获取选中的⽇期
一.Label
QLabel 可以⽤来显⽰⽂本和图⽚。
核⼼属性如下
1. text 属性:显示的内容
-
这是最根本的属性,就是你想在标签上显示的文字。
-
它可以是最简单的纯文本,也可以是包含 HTML 标签的富文本。
-
示例:
label->setText("你好,世界!"); // 纯文本 label->setText("<b>加粗的</b> 正常文本"); // 富文本(需要设置textFormat)
2. textFormat 属性:文本的解析方式
这个属性决定了 QLabel 如何理解和渲染 text
属性里的字符串。
-
Qt::PlainText
(默认值):将文本当作纯文本处理。即使你写了<b>粗体</b>
,它也会原封不动地显示这几个字符,而不会把“粗体”两个字加粗。 -
Qt::RichText
:支持一个 HTML 子集。你可以使用像<b>粗体</b>
、<i>斜体</i>
、<a href="https://www.qt.io">链接</a>
、<font color="red">红色文字</font>
这样的标签来格式化文本。 -
Qt::MarkdownText
:支持 Markdown 语法,比如用**粗体**
、*斜体*
、[链接](网址)
来排版。 -
Qt::AutoText
:Qt 会自己判断你的文本是纯文本还是富文本/ Markdown。注意:这个判断可能不总是准确的,所以为了保险起见,通常明确指定格式更好。
重要提示:一旦你改变了 textFormat
,QLabel 会立刻用新的格式重新解析当前的 text
。
3. pixmap 属性:显示图片
-
这个属性用于在 QLabel 里显示一张图片(QPixmap 对象)。
-
注意:如果你同时设置了
text
和pixmap
,图片会覆盖掉文本,你只会看到图片。所以一个 QLabel 通常只用于显示一种内容。 -
示例:
QPixmap photo("path/to/image.jpg"); label->setPixmap(photo);
4. scaledContents 属性:内容是否拉伸
-
这个问题专门针对当 QLabel 的尺寸和图片/文本的原始尺寸不一致时怎么办。
-
false
(默认值):内容保持原始比例和大小。如果 QLabel 比图片大,图片周围会有空白;如果 QLabel 比图片小,图片可能只会显示一部分。 -
true
:图片或文本会被强行拉伸或压缩,以填满整个 QLabel 的区域。这很容易导致图片失真(变扁或拉长),所以要谨慎使用。
5. alignment 属性:内容的对齐方式
-
当内容(文本或图片)的大小小于 QLabel 的大小时,这个属性决定了内容在 QLabel 区域内的位置。
-
它是一个组合值,可以同时指定水平和垂直方向的对齐。
-
常用值:
-
水平:
Qt::AlignLeft
(左对齐),Qt::AlignRight
(右对齐),Qt::AlignHCenter
(水平居中) -
垂直:
Qt::AlignTop
(顶部对齐),Qt::AlignBottom
(底部对齐),Qt::AlignVCenter
(垂直居中) -
组合:
Qt::AlignCenter
(等同于Qt::AlignHCenter | Qt::AlignVCenter
,即居中对齐)
-
6. wordWrap 属性:文本是否自动换行
-
当一行文本太长,超出 QLabel 的宽度时,这个属性决定如何处理。
-
false
(默认值):文本不换行,超出的部分会用 “...” 来表示被截断了。 -
true
:文本会自动折行到下一行显示。 -
注意:这个属性主要对
PlainText
和AutoText
格式有效。对于RichText
,HTML 本身有换行规则(如<br/>
标签);对于MarkdownText
,换行遵循 Markdown 语法。
7. indent 和 margin 属性:缩进与边距
这是两个容易混淆但作用不同的属性:
-
indent
(缩进):-
特指文本的缩进,单位是像素。
-
它的缩进方向取决于对齐方式
alignment
。比如,如果是左对齐,indent
就是向右缩进;如果是右对齐,就是向左缩进;如果是居中对齐,这个属性就无效。
-
-
margin
(边距):-
指的是整个内容(无论是文本还是图片) 与 QLabel 外部边框之间的内边距,就像给内容加了一个“衬垫”。
-
它在上、下、左、右四个方向同时生效,让内容看起来不那么紧贴边框。
-
8. openExternalLinks 属性:是否自动打开链接
-
当你的 QLabel 文本是
RichText
或MarkdownText
并且其中包含可点击的链接时,这个属性才有意义。 -
false
(默认值):点击链接不会打开浏览器,但会发射一个linkActivated(const QString &link)
信号。你可以连接这个信号,在自己的程序里做自定义处理(比如在嵌入式界面上跳转)。 -
true
:点击链接会直接用你电脑上默认的浏览器打开那个网址。
9. buddy 属性:设置伙伴控件
-
这是一个提升用户体验和可访问性的功能。
-
工作原理:
-
首先,你要在 QLabel 的
text
里用一个&
符号来指定一个快捷键,比如"&Username:"
会产生快捷键Alt+U
。 -
然后,你用
setBuddy()
方法把这个 QLabel 和另一个输入控件(比如 QLineEdit、QCheckBox)关联起来。 -
当用户按下快捷键(如
Alt+U
)时,焦点就会自动跳转到你设置的那个伙伴控件上。
-
-
典型应用:填写表单时,点击“姓名:”标签,光标就能直接跳到后面的输入框里。
1.1.示例1——显⽰不同格式的⽂本
这里运用了textFormat 属性。
textFormat 属性:文本的解析方式
这个属性决定了 QLabel 如何理解和渲染 text
属性里的字符串。
-
Qt::PlainText
(默认值):将文本当作纯文本处理。即使你写了<b>粗体</b>
,它也会原封不动地显示这几个字符,而不会把“粗体”两个字加粗。 -
Qt::RichText
:支持一个 HTML 子集。你可以使用像<b>粗体</b>
、<i>斜体</i>
、<a href="https://www.qt.io">链接</a>
、<font color="red">红色文字</font>
这样的标签来格式化文本。 -
Qt::MarkdownText
:支持 Markdown 语法,比如用**粗体**
、*斜体*
、[链接](网址)
来排版。 -
Qt::AutoText
:Qt 会自己判断你的文本是纯文本还是富文本/ Markdown。注意:这个判断可能不总是准确的,所以为了保险起见,通常明确指定格式更好。
我们创建一个新项目
拖3个出来
注意拉大一点
接下来来写代码
-
PlainText:显示原始代码,不解析
-
RichText:用HTML标签格式化
-
MarkdownText:用Markdown语法格式化
我们运行一下
可以看到,明显的不同。
这个时候,我们点击这个链接,却发现什么都没有发生
当然,下面这个链接也没有用
这其实跟Label的另外一个特性有关
openExternalLinks 属性:是否自动打开链接
-
当你的 QLabel 文本是
RichText
或MarkdownText
并且其中包含可点击的链接时,这个属性才有意义。 -
false
(默认值):点击链接不会打开浏览器,但会发射一个linkActivated(const QString &link)
信号。你可以连接这个信号,在自己的程序里做自定义处理(比如在嵌入式界面上跳转)。 -
true
:点击链接会直接用你电脑上默认的浏览器打开那个网址。
我们现在就来修改这个代码,让它可以打开链接,其实最关键的就是下面这两句
完整代码如下
我们运行一下,点击下面这个链接
点击之后,里面跳转到下面这里来了
回到我们的程序,点击下面这个
点击之后,直接跳转到下面这里来了
1.2.示例2——显示图片
虽然 QPushButton 也可以通过设置图标的⽅式设置图⽚,但是并⾮是⼀个好的选择。
更多的时候 还是希望通过 QLabel 来作为⼀个更单纯的显⽰图⽚的⽅式
我们创建一个新项目
我们在界⾯上创建⼀个QLabel, objectName 为 label
接下来我们来准备一下我们需要的图片文件
接下来创建一个qrc文件
注意我们需要提取将图片资源放到qrc所在目录下或者它的子目录下面
添加成功。
现在我们来编写一下代码
我们发现这个图片并没有填满这整个窗口,其实是我们的图片的尺寸达不到这个窗口的大小。
那我硬是要这个图片能达到和整个窗口一样大呢?
这就需要了解Label的另外一个属性
scaledContents 属性:内容是否拉伸
-
这个问题专门针对当 QLabel 的尺寸和图片/文本的原始尺寸不一致时怎么办。
-
false
(默认值):内容保持原始比例和大小。如果 QLabel 比图片大,图片周围会有空白;如果 QLabel 比图片小,图片可能只会显示一部分。 -
true
:图片或文本会被强行拉伸或压缩,以填满整个 QLabel 的区域。这很容易导致图片失真(变扁或拉长),所以要谨慎使用。
我们运行一下
此时,如果把我们的窗⼝变大或者变小,可以看到图⽚并不会随着窗⼝⼤⼩的改变⽽同步变化
这个其实是下面这个的问题
在上面代码中是在构造函数里,进行的这样的尺寸设置,这个设置相当于是"一次性的!
一旦程序运行起来之后,QLabel 的尺寸就固定下来了窗口发生改变,此时, QLabel 是不会变化的~~
那怎么办呢?
在 Qt 框架中,用户的交互行为通常对应两种处理机制:一种是信号(Signal),另一种是事件(Event)。这两者虽然都用于响应用户操作,但在触发方式和应用场景上有所区别。
QLabel 作为 QWidget 的子类,继承了其完整的事件处理体系。resizeEvent
属于连续型事件——当 QLabel 的尺寸从初始大小 A 变化到目标大小 B 的过程中,系统会连续触发多次 resizeEvent
,而不是仅触发一次。这意味着在整个尺寸变化过程中,resizeEvent
会被频繁调用,为实时响应尺寸变化提供了基础。
为了解决这个问题,可以在Widget中重写resizeEvent函数.
我们运行一下
我们把它放大
……
可以看到触发了很多次。这些打印出来的都是窗口的尺寸。
我们运行一下
把它放大
把它变小
怎么样?是不是就完美符合上了。这个图片的大小就会跟着窗口大小一直变化了。
此处的resizeEvent 函数我们没有⼿动调⽤,但是能在窗⼝⼤⼩变化时被⾃动调⽤.
这个过程就是依赖C++中的多态来实现的.Qt框架内部管理着QWidget对象表⽰咱们的窗
⼝.在窗⼝⼤⼩发⽣改变时,Qt就会⾃动调⽤resizeEvent 函数.
但是由于实际上这个表⽰窗⼝的并⾮是QWidget,⽽是QWidget的⼦类,也就是咱们⾃⼰写
的Widget.此时虽然是通过⽗类调⽤函数,但是实际上执⾏的是⼦类的函数(也就是我们重写
后的resizeEvent
此处属于是多态机制的⼀种经典⽤法.通过上述过程,就可以把⾃定义的代码,插⼊到框架内
部执⾏.相当于"注册回调函数".
1.3.示例3——⽂本对⻬,⾃动换⾏,缩进,边距
接着我们在右边属性栏里面设置一下下面这个属性
这个就是边框的意思,设置完后就和下面一样了。
我们复制3份
注意把里面的文字给我删了
1.3.1.文本对齐
alignment 属性:内容的对齐方式
-
当内容(文本或图片)的大小小于 QLabel 的大小时,这个属性决定了内容在 QLabel 区域内的位置。
-
它是一个组合值,可以同时指定水平和垂直方向的对齐。
-
常用值:
-
水平:
Qt::AlignLeft
(左对齐),Qt::AlignRight
(右对齐),Qt::AlignHCenter
(水平居中) -
垂直:
Qt::AlignTop
(顶部对齐),Qt::AlignBottom
(底部对齐),Qt::AlignVCenter
(垂直居中) -
组合:
Qt::AlignCenter
(等同于Qt::AlignHCenter | Qt::AlignVCenter
,即居中对齐)
-
我们也可以去看看源代码里面是怎么写的
我们先写这么一个函数,然后把鼠标移动到下面箭头所指的地方
然后我们ctrl+鼠标左键,直接跳转到下面这里
我给大家粗略的讲讲,这些都是啥意思
1. 水平对齐标志
下面这些标志控制水平方向的对齐。注意我只提取了最核心的3个
标志 | 值 | 描述 |
---|---|---|
|
| 左对齐。将内容对齐到可用空间的左侧。 |
|
| 右对齐。将内容对齐到可用空间的右侧。 |
|
| 水平居中。将内容在可用空间中水平居中对齐。 |
话不多说,我们直接看例子即可
怎么样?一目了然了吧!!
2. 垂直对齐标志
这些标志控制垂直方向的对齐。我只取最核心的3个
标志 | 值 | 描述 |
---|---|---|
|
| 顶部对齐。将内容对齐到可用空间的顶部。 |
|
| 底部对齐。将内容对齐到可用空间的底部。 |
|
| 垂直居中。将内容在可用空间中垂直居中对齐。 |
话不多说,我们直接多看几个例子就明白什么意思了。
怎么样?明白了吗?
3. 组合使用水平对齐标志和垂直对齐标志
由于这些是位标志,你可以使用 |
操作符来组合它们。
您可以通过 |
操作符来组合一个水平标志和一个垂直标志,以确定文本在二维空间中的位置。
当然,有一个选项——Qt::AlignCenter,其实是有点特殊的
ui->label->setAlignment(Qt::AlignCenter);
等价于
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
1.3.2.自动换行
我们看个例子
我们发现超过边框的文字都消失不见了,这可不是我们想要的结果,我们想要看到的是多余的文字会自动换行啊!!这个自动换行其实和下面这个属性有关
wordWrap 属性:文本是否自动换行
-
当一行文本太长,超出 QLabel 的宽度时,这个属性决定如何处理。
-
false
(默认值):文本不换行,超出的部分会用 “...” 来表示被截断了。 -
true
:文本会自动折行到下一行显示。 -
注意:这个属性主要对
PlainText
和AutoText
格式有效。对于RichText
,HTML 本身有换行规则(如<br/>
标签);对于MarkdownText
,换行遵循 Markdown 语法。
我们现在就来改写一下代码
1.3.3.缩进和边距
indent 和 margin 属性:缩进与边距
这是两个容易混淆但作用不同的属性:
-
indent
(缩进):-
特指文本的缩进,单位是像素。
-
它不只是首行缩进,他是所有行都缩进。
-
它的缩进方向取决于对齐方式
alignment
。比如,如果是左对齐,indent
就是向右缩进;如果是右对齐,就是向左缩进;如果是居中对齐,这个属性就无效。
-
-
margin
(边距):-
指的是整个内容(无论是文本还是图片) 与 QLabel 外部边框之间的内边距,就像给内容加了一个“衬垫”。
-
它在上、下、左、右四个方向同时生效,让内容看起来不那么紧贴边框。
-
为了更好的演示,我们去.ui文件里面修改一下Label的形状和数量
我们直接写代码
运行结果如下
现在我们明白了。假设我们的Label的区域如下
如果说我们在左对齐的情况下设置了setIndent(30),那么Label的内容展示区就只剩下下面黄色的这部分了,它不只是首行缩进
如果说我们设置了setMargin(30),那么Label的内容展示区就只剩下下面黄色的这部分了
外边框离内边框的距离是30个像素点。
1.4.示例4——设置伙伴
9. buddy 属性:设置伙伴控件
-
这是一个提升用户体验和可访问性的功能。
-
工作原理:
-
首先,你要在 QLabel 的
text
里用一个&
符号来指定一个快捷键,比如"&Username:"
会产生快捷键Alt+U
。 -
然后,你用
setBuddy()
方法把这个 QLabel 和另一个输入控件(比如 QLineEdit、QCheckBox)关联起来。 -
当用户按下快捷键(如
Alt+U
)时,焦点就会自动跳转到你设置的那个伙伴控件上。
-
-
典型应用:填写表单时,点击“姓名:”标签,光标就能直接跳到后面的输入框里。
我们创建一个新项目
我们拖出2个来
然后我们再拖出2个Label来
它们的objectname是
在 QLabel 的文本中使用 & 符号后跟一个字母来定义快捷键:
- &A → 对应快捷键 Alt+A
- &B → 对应快捷键 Alt+B
- &OK → 对应快捷键 Alt+O(取第一个字母)
- 此处把label中的⽂本设置为"快捷键&A"这样的形式
- 其中&后⾯跟着的字符,就是快捷键.
- 可以通过alt+A的⽅式来触发该快捷键
但是注意,这⾥的快捷键和 QPushButton 的不同,这里需要搭配alt和单个字⺟的⽅式才能触发
我们运行一下,我们发现&字符不见了。
我们按下快捷键Alt+a,就会发现选中了选项一
我们按下快捷键Alt+b,就会发现选中了选项二
二. LCD Number
QLCDNumer 是⼀个专⻔⽤来显⽰数字的控件.类似于"⽼式计算器"的效果.
1. 设置数值的方法:
display
-
方法名不是
setValue
或setIntValue
,而是display
。 -
display
是一个重载函数,它可以接受多种参数类型:-
display(int num)
-
display(double num)
-
display(const QString &str)
-
为什么这样设计?
display
这个名字更直观地反映了这个控件的作用——显示一个值。你命令它“显示”某个数字,而不是抽象地“设置”一个属性。
示例:
lcd->display(15); // 显示整数 15
lcd->display(12.75); // 显示浮点数 12.75
lcd->display("Error"); // 显示字符串(通常用于错误状态)
2. 数值属性:
intValue
和value
(及其联动关系)
这是 QLCDNumber
最核心的属性。
-
value
(double 类型):控件实际存储的数值。 -
intValue
(int 类型):控件显示值的四舍五入后的整数。
关键点:联动关系与四舍五入
这两个属性是完全联动的,但理解其联动规则至关重要。
例如给 value
设为 1.5
,intValue
的值就是 2
。
规则是: 当你设置 value
时,intValue
会自动被设置为对 value
进行四舍五入后的整数值。反之,当你设置 intValue
时,value
会被设置为该整数值的浮点数形式(例如,设置 intValue
为 5
,value
会变成 5.0
)。
示例代码:
2.1.示例1——基本示例
我们创建一个项目,
放置4个LCD Number
它们的objectname是
接下来我们来写一下代码
显示模式:
mode
这个属性决定了数字以何种进制格式显示。
它是一个枚举类型 QLCDNumber::Mode
。
模式 | 枚举值 | 说明 |
---|---|---|
十进制 |
| 显示常规的十进制数字。只有在此模式下才能显示小数。 |
十六进制 |
| 以十六进制格式显示数字(0-9, A-F)。 |
二进制 |
| 以二进制格式显示数字(0 和 1)。 |
八进制 |
| 以八进制格式显示数字(0-7)。 |
我们运行一下
我们发现这个二进制的怎么显示是0呢?
这个涉及一个新属性
显示位数:
digitCount
-
控制 LCD 数字控件最多显示多少位数字。
-
如果设置的数值位数超过
digitCount
,就会出现错误。
显示为0的原因:
-
位数不足:100的二进制是
1100100
(7位),如果你的digitCount
设置太小(比如只有2-3位),就无法显示完整的二进制数 -
默认位数可能太小:如果没有显式设置
digitCount
,默认位数可能不足以显示100的二进制形式
我们来显示设置一下这个默认显示位数,
显示小数的情况
注意:只有⼗进制的时候才能显⽰⼩数点后的内容。
这是 mode
属性一个极其重要的特性。如果你在十六进制、二进制或八进制模式下设置了一个带小数的 value
,控件会将小数部分完全忽略。
实际显示结果是:
-
十进制:
10.75
-
十六进制:
A
(10的十六进制) -
二进制:
1010
(10的二进制) -
八进制:
12
(10的八进制)
很符合我们的预期。
但是我们发现一个问题
这个小数点太占位置了吧。我们能不能不要让它独占一个格子,这涉及一个新属性
小型小数点:
smallDecimalPoint
这是一个布尔属性(true/false)。
-
当设置为
false
(默认值)时:小数点会占据一个完整的数字位。例如,显示12.3
需要 4 个数字位(两位整数、一位小数点、一位小数)。 -
当设置为
true
时:小数点会变得更小,并与其前面的数字共享一个数字位。显示12.3
只需要 3 个数字位(两位整数+共享的小数点、一位小数)。
这样做的好处是:
可以更有效地利用显示空间。尤其是在显示位数有限的情况下,可以多显示一位整数或小数。
怎么样?明白了没!!
2.2.示例2——倒计时
我们先创建一个新项目
在界⾯上创建⼀个 QLCDNumber
我们在右边属性栏里面设置一下它的初始值为10
修改这个value即可。
接下来我们修改代码
关于这个QTimer,我想讲讲
您可以把 QTimer
想象成一个 “闹钟” 或者 “定时器”。
核心概念:它是什么?
QTimer
就是一个用来在程序中安排“在未来某个时间点做某件事”的工具。它不是让整个程序停下来等待(那会卡住界面),而是以一种非常高效、不阻塞程序运行的方式来实现定时功能。
它是如何工作的?(基于您提供的例子)
我们根据这个“倒计时”程序来理解它的工作流程:
-
创建闹钟:首先,您在程序中创建了一个
QTimer
对象,这就好比您从抽屉里拿出了一个闹钟。 -
设置闹铃间隔:您调用
timer->start(1000)
,这相当于把闹钟的响铃间隔设置为 1000 毫秒(也就是 1 秒)。并且按下了“开始”按钮。 -
闹钟自动运行:从这一刻起,这个闹钟就会在后台默默地工作。每过 1 秒钟,它就会“叮”地响一声。在 Qt 的世界里,这“叮”的一声,就是一个叫做
timeout()
的信号被发射了。 -
连接信号与动作:您通过
connect
函数,把这个“叮”的信号timeout()
和另外一个名为updateTime()
的槽函数 连接了起来。这就像您告诉程序:“听着,每次闹钟一响,你就去执行updateTime()
这个函数里写的指令。” -
执行预定任务:所以,每隔一秒钟:
-
闹钟(
QTimer
)响(发射timeout
信号)。 -
程序听到铃声,自动去执行
updateTime()
函数。 -
在
updateTime()
函数里,您让屏幕上显示的数字减 1。
-
-
关闭闹钟:当数字减到 0 时,您在
updateTime()
函数里调用了timer->stop()
。这相当于把正在响的闹钟给关掉了,它就不再周期性地发射timeout
信号了,updateTime()
函数也就不会再被自动调用。
关键特性与优势
-
非阻塞:这是最重要的特点。程序的主线程(比如负责更新界面的线程)不会被定时等待卡住。界面依然可以流畅地响应用户的操作(比如点击按钮)。
QTimer
是利用 Qt 的事件循环 机制来实现这一点的,它悄悄地等待,时间到了就发个通知,而不是傻傻地原地等待。 -
精确性:它的定时是“近似精确”的。它保证间隔至少是您设定的时间,但可能会因为系统繁忙而有微小的延迟。对于需要高精度定时(如音频、视频同步)的场景,Qt 提供了其他更高级的定时器。
-
单次与多次:
-
多次定时:就像我们的例子,每隔固定时间重复触发,直到调用
stop()
。 -
单次定时:您可以设置一个定时器,只在指定的时间过后触发一次,然后就自动停止。这就像设置一个“10分钟后提醒我烤箱里的蛋糕好了”的单一闹钟。
-
想象一下你在用烤箱烤蛋糕:
-
没有 QTimer 的做法(阻塞):你搬个凳子坐在烤箱前,眼睛死死盯着计时器,什么也不干,直到时间到。这非常低效。
-
使用 QTimer 的做法(非阻塞):你设置好烤箱的定时器,然后就可以放心地去打扫房间、看电视。当烤箱“叮”的一声响(
timeout
信号),你听到后(事件循环收到信号),就走过去把蛋糕拿出来(执行槽函数)。
我们运行一下,就会发现这个就会从10倒计时到0,然后结束
……
……
……
我们看这个槽函数被触发了11次,这个是在我们的预期之中的。
针对上述代码,存在两个问题:
2.2.1.问题一
问题一:如果直接在Widget构造函数中,通过⼀个循环+sleep的⽅式是否可以呢?
我们点击启动,却发现是下面这个情况
这个为什么报错呢?
别忘了我们这个代码可是在Widget的构造函数里面的
w.show()才是显示
-
由于
while(true)
无限循环,Widget类的构造函数永远无法执行完毕 -
在Qt中,界面组件需要构造函数完成后才能进入正常的工作状态
所以就会报错啊。
2.2.2.问题二
问题二:如果是在Widget构造函数中,另起⼀个线程,在新线程中完成循环+sleep是否可以呢?
我们运行一下,发现又报错了。
这是为啥呢?我们来看看
在 Qt 框架中,图形用户界面(GUI)有一个核心的设计原则:所有对界面组件的创建、修改和操作都必须在主线程(也称为 GUI 线程)中执行。这个主线程就是执行 main()
函数并启动 QApplication
(或 QGuiApplication
)事件循环的那一个线程。
一、 为什么会有这个限制?
这主要是出于线程安全的考虑。
-
GUI 的内部状态:Qt 的界面组件(如
QWidget
、QLabel
、QPushButton
等)并非线程安全的对象。它们内部维护着复杂的状态信息,例如几何尺寸、是否可见、样式表、以及底层的窗口系统资源等。 -
竞态条件风险:如果允许多个线程同时修改同一个界面组件,就会产生“竞态条件”。例如,一个线程正在重绘按钮,而另一个线程同时改变了按钮的文本,这极易导致界面渲染错误、数据损坏,甚至程序崩溃。
-
底层窗口系统的要求:许多操作系统本身的窗口系统 API 就不是线程安全的,它们期望对窗口的调用来自创建它的线程。Qt 作为跨平台的抽象层,必须遵守这一底层约束。
因此,Qt 采取了最直接有效的策略:强制规定 GUI 操作只能在主线程进行,从根本上杜绝了多线程操作 UI 带来的不确定性。
二、 错误示例与现象
如果你在子线程中直接调用如下代码:
// 在某个子线程中执行
ui->lcdNumber->display(value); // 危险!违反规则!
程序在运行时可能会立即崩溃,并抛出类似 “Cannot send events to objects owned by a different thread"
的错误。更常见的是,在 Debug 模式下可能看似正常,但在 Release 模式下或高负载时出现随机崩溃,错误信息可能就是您提到的 “terminate called without an active exception”
或纯粹的段错误。这是一种非常难以调试的错误。
三、 正确的解决方案:信号与槽机制
默认情况下,槽函数都是由主线程调用的,所以在槽函数中修改界面是没有任何问题的!!
Qt 推荐并提供了完美的解决方案——信号与槽机制,来实现跨线程的安全通信。这是 Qt 的核心机制之一。
工作原理:
当一个信号被发射(emit)时,Qt 会检查信号与槽之间的连接方式(由 Qt::ConnectionType
指定)。如果连接的双方对象(sender
和 receiver
)存在于不同的线程,并且采用默认的 Qt::AutoConnection
方式,Qt 会自动将信号转换为一个事件,并将该事件放入主线程的事件队列中。随后,主线程的事件循环会从队列中取出这个事件,并调用与之相连的槽函数。
总结下来一句话:
如果要修改界面,必须在主线程里面或者槽函数里面进行修改,绝对不能在子线程进行修改
Qt中规定,任何对于GUI上内容的操作,必须在主线程中完成。
像Widget构造 函数,以及connect连接的slot函数,都是在主线程中调⽤的。⽽我们⾃⼰创建的线程则不是
三.ProgressBar
使⽤ QProgressBar 表⽰⼀个进度条
相关属性介绍
minimum(最小值)
-
定义进度条数值范围的下限。
-
该属性决定了进度条能够表示的最小数值,是进度计算的起点。
-
当进度条的当前值等于最小值时,进度条显示为完全空的状态,表示任务尚未开始或处于初始状态。
-
该值必须小于最大值,且通常设置为非负整数,但理论上可以设置为任何整数值。
maximum(最大值)
-
定义进度条数值范围的上限。
-
该属性与最小值共同确定了进度条的完整量程。
-
当进度条的当前值等于最大值时,进度条显示为完全填满的状态,表示任务已完成。
-
最大值必须大于最小值,且决定了进度条能够表示的最高进度水平。
value(当前值)
-
表示进度条的当前进度状态(进度条当前值)
-
该数值必须位于最小值和最大值之间,直接反映了任务的完成程度。
-
进度条的可视化填充程度是根据当前值在最小值和最大值之间的相对位置来计算的。
-
具体来说,填充百分比的计算公式为:(当前值 - 最小值) ÷ (最大值 - 最小值) × 100%。
-
该属性是动态变化的,随着任务的执行而不断更新。
alignment(文本对齐方式)
该属性用于控制进度条内部文本(如进度数值或百分比)在其显示区域内的对齐方式。通过设置不同的对齐标志,可以精确控制文本在水平与垂直方向上的位置。
水平对齐选项:
-
Qt::AlignLeft:将文本对齐到进度条区域的左侧边缘。
-
Qt::AlignRight:将文本对齐到进度条区域的右侧边缘。
-
Qt::AlignHCenter:将文本在进度条区域内水平居中对齐。
-
Qt::AlignJustify:使文本在可用宽度内两端对齐。对于进度条中的单行短文本,其视觉效果通常与水平居中对齐相似。
垂直对齐选项:
-
Qt::AlignTop:将文本对齐到进度条区域的顶部边缘。
-
Qt::AlignBottom:将文本对齐到进度条区域的底部边缘。
-
Qt::AlignVCenter:将文本在进度条区域内垂直居中对齐。
这些标志可以通过按位或(|
)运算符组合使用,例如 Qt::AlignRight | Qt::AlignVCenter
可实现文本在区域内的右下角显示。
textVisible(文本可见性)
-
控制是否在进度条上方显示表示进度的文本信息。
-
当设置为可见时,进度条会显示当前进度值或格式化后的文本;
-
当设置为不可见时,进度条仅显示图形化的填充效果,不显示任何文字。
orientation(方向)
-
说明:定义进度条的延伸方向,即进度填充的轴向。
-
水平方向:进度条从左到右(或从右到左,取决于其他设置)填充,这是最常见的进度条形式,符合大多数用户的阅读习惯。
-
垂直方向:进度条从下到上(或从上到下)填充,适用于高度有限但宽度充足的界面布局,或在需要与垂直排列的其他元素保持一致的场景。
-
invertAppearance(反转外观)
-
控制进度条填充方向的逻辑反转。
-
当启用反转外观时,进度条的填充方向将与常规方向相反。
-
对于水平进度条,填充将从右向左进行;
-
对于垂直进度条,填充将从上向下进行。
textDirection(文本方向)
-
说明:专门针对垂直进度条设置文本的阅读方向。该属性决定了文本在垂直进度条中的字符排列方向。
-
自上而下:文本字符从上向下排列,每个字符保持正常方向。
-
自下而上:文本字符从下向上排列,这种显示方式较为少见,适用于特殊设计需求。
-
format(文本格式)
-
说明:定义进度条上显示的文本内容的格式模板。通过使用特定的占位符,可以自定义显示进度信息的方式。
-
%p:显示当前进度的百分比数值,范围从0%到100%,这是最直观的进度表示方式。
-
%v:显示当前进度的实际数值,基于最小值和最大值设定的范围。
-
%m:显示总范围值,即最大值与最小值之差,表示任务的总量。
-
%t :表⽰总时间(以毫秒为单位)
-
此外,还可以在格式字符串中添加自定义文字,如"已完成: %p%"会显示为"已完成: 75%"。
-
3.1.示例一:设置进度条按时间增长
我们创建一个新项目
我们拖一个出来
我们设置一下最小值是0,最大值是100,当前值设置为0
它就变成下面这样子了
现在我们就来写一下代码
我们运行一下,然后就会发现这个进度条会从0一直涨到100,然后就停止了
3.2.前向声明和完整头文件
其实上面那个例子,我们不止可以使用Lambda表达式,我们还可以用最普通的直接定义槽函数即可,大家看看下面的代码
这个的运行效果和上面的是一模一样的。
那么问题来了
大家仔细看看这个头文件<QTimer>的包含.
这个头文件的包含,本应放在 widget.h
中。
但在这个例子中,我们却将头文件包含放在了 .cpp
文件中。
观察以下代码,虽然在 widget.h
中使用了 QTimer
类型,却没有在头文件中包含 <QTimer>
,为什么这样的代码编译不会出错?为什么没有出现“未找到定义”之类的错误?
实际上,这是通过 Qt 内部提供的一项特殊机制实现的。
在 Qt 中,存在一个专门的头文件,其中包含了所有 Qt 类的前置声明,例如:
class QWidget;
class QPushButton;
class QTimer;
...
这个头文件一般不会直接出现在我们的代码中,但当我们包含其他 Qt 头文件(例如 <QWidget>
)时,会间接地引入该文件。
就比如说
当你写下:
#include <QWidget> // 包含了 QWidget 的完整定义,以及其他一些东西
这个 QWidget
头文件内部,又会去包含那个包含了大量前置声明的内部头文件。
所以,你的 widget.h
文件通过 #include <QWidget>
间接地获得了很多类(包括 QTimer
)的前置声明。
所以说,我们下面这里的声明是不会报错的
不过我们就有一个问题:
有了这么一个全能的头文件,我为什么还需要去#include具体的头文件呢?
这里大家需要注意的是:在 Qt 中,存在一个专门的头文件,其中包含了所有 Qt 类的前置声明
这里我们需要搞清楚什么是类的完整定义(头文件),什么是类的完整定义(头文件)
让我们用一个比喻来理解:
-
类的完整定义(头文件):就像一个人的完整身份证信息,包括姓名、身份证号、家庭住址、照片等。你要请他帮你办具体的事(比如调用他的成员函数),就必须知道他的详细地址。
-
类的前置声明:就像只知道一个人的名字。你虽然不知道他具体住哪,但你可以向别人提起他(比如声明一个指向他的指针),告诉大家“我认识一个叫张三的人”。
核心规则:C++ 的“前置声明”
C++ 编译器有一个重要规则:当你只是声明一个类的指针(pointer) 或引用(reference) 时,你只需要告诉编译器“这个类存在”即可,而不需要知道这个类的完整细节(比如它有哪些成员变量和函数)。
这就是“前置声明”(Forward Declaration)。它的写法很简单:
class QTimer; // 这就是一个前置声明,告诉编译器:QTimer 是一个类。
只要有了这行声明,你就可以这样写:
class Widget {
private:QTimer *timer; // 没问题!编译器知道 QTimer 是个类,指针的大小是固定的。
// QTimer &timerRef; // 声明引用也一样可以。
};
但是,如果你要真正使用这个对象,比如创建它(new QTimer)或者调用它的方法(timer->start()),编译器就必须知道这个类的全部细节(它的构造函数、成员函数等)。这时候,就必须包含该类的头文件 #include <QTimer>。
不信的话,我们可以看看例子
我们一编译运行,就报错了
这个错误是C++编译错误,意思是使用了不完整的类型 'QTimer'。
所以大家一定要注意:当你真正要使用这个类(new
, 调用函数等)时,必须包含该类的头文件。
前向声明只能保证你声明一个对象没有错,但是你不能构造它。
为什么要使用前置声明?
这确实主要为了解决编译速度的问题,特别是在大型项目中,这个优化带来的收益是巨大的。
C/C++ 编译的瓶颈:#include
机制
C/C++ 的编译速度在编程语言中确实是出了名的慢,这与其独特的编译模型直接相关:
-
文本包含机制:
#include
本质上是将头文件的全部内容原封不动地插入到源文件中。 -
重复编译:如果头文件 A 被 100 个源文件包含,那么头文件 A 的内容就会被编译 100 次。
-
依赖传染:头文件本身还会包含其他头文件,形成复杂的依赖网。修改一个基础头文件(例如包含了常用声明的头文件)可能导致整个项目需要重新编译。
有的时候,添加一行 printf
就需要编译一小时——这正是大型 C++ 项目的典型痛点。编译时间成了开发效率的主要瓶颈。
前置声明如何“破局”?
1. 切断编译依赖链
假设 Widget.h
直接包含 QTimer.h
,而 QTimer.h
又包含 QObject.h
,那么依赖关系是:
main.cpp
→ Widget.h
→ QTimer.h
→ QObject.h
当 QObject.h
发生改变时,所有直接或间接包含它的文件(包括 main.cpp
)都需要重新编译。
如果使用前置声明,依赖关系变为:
-
Widget.h
:仅包含QWidget.h
(其中已前置声明了QTimer
),不直接依赖QTimer.h
。 -
Widget.cpp
:包含Widget.h
和QTimer.h
。
这样,当 QTimer.h
的实现细节发生变化(但只要其公共接口不变),只有 Widget.cpp
需要重新编译,而其他包含了 Widget.h
的文件则不受影响。这在大型项目中能显著减少增量编译的范围。
2. 减少编译器处理的内容
头文件包含会显著增加编译器需要处理的代码量。一个简单的类,其头文件可能因为包含层层依赖而膨胀到数千行。使用前置声明,头文件会保持非常精简,编译器解析起来自然更快。
现代C++的进化:模块(Modules)
C++20 的模块,这正是为了解决 #include
的历史包袱而引入的终极方案。
-
#include
:文本替换,每次编译都需要重复处理。 -
模块:编译一次后,生成二进制接口文件,后续编译直接使用这个预处理结果,极大提升效率。
虽然模块是未来,但在当前的实际开发中,我们仍然大量依赖头文件机制。
实践建议:权衡之道
“与其过度追求技巧,不如改善硬件和工具链”。
-
对于普通项目:确实不必过分纠结。清晰的代码结构比微小的编译时间优化更重要。该包含时就包含,保持代码可读性。
-
对于大型项目:遵循“在头文件中使用前置声明,在实现文件中包含头文件”的原则是非常有价值的。这正是 Qt 自身所践行的最佳实践。同时,利用分布式编译工具(如 Incredibuild、distcc)和高性能硬件(多核CPU、SSD)能更直接地提升编译效率。
3.2.示例二:创建⼀个红⾊的进度条
上述的进度条使⽤绿⾊表⽰的,但是考虑到有些⼈可能不喜欢绿⾊,因此我们改成⼀个红⾊的进度条。
注意,这次我们是使用.ui文件进行操作的
我们创建一个新项目
我们拖一个出来
我们设置一下最小值是0,最大值是100,当前值设置为0
修改完之后它就变下面这样子了
不要忘了, QProgressBar 同样也是 QWidget 的⼦类,因此我们可以使⽤ styleSheet 通过样 式来修改进度条的颜⾊.
在QtDesigner右侧的属性编辑器中,找到QWidget的 styleSheet 属性
点击之后弹出下面这个弹窗
当然,如果我们按照下面这个方式也是可以打开这个弹窗的
我们把下面这个代码复制进去
QProgressBar::chunk {background-color: #FF0000;}
各部分含义
1. QProgressBar
- 指定要样式化的控件类型,这里是进度条控件
2. ::chunk
-
伪状态选择器,特指进度条中已完成的进度部分
-
进度条由两部分组成:
-
chunk:已完成的部分(前景)
-
进度条背景:未完成的部分(背景)
-
3. background-color: #FF0000;
- 设置chunk(已完成部分)的背景颜色为红色
- #FF0000 是红色的十六进制颜色代码
设置完之后,我们发现这个进度条居然变成下面这样子了
我们发现这个数字居然跑到了左上角,这个其实是Qt的bug!
我们修改一下下面这个配置来使得它变的更好看一点
alignment(文本对齐方式)
该属性用于控制进度条内部文本(如进度数值或百分比)在其显示区域内的对齐方式。通过设置不同的对齐标志,可以精确控制文本在水平与垂直方向上的位置。
水平对齐选项:
-
Qt::AlignLeft:将文本对齐到进度条区域的左侧边缘。
-
Qt::AlignRight:将文本对齐到进度条区域的右侧边缘。
-
Qt::AlignHCenter:将文本在进度条区域内水平居中对齐。
-
Qt::AlignJustify:使文本在可用宽度内两端对齐。对于进度条中的单行短文本,其视觉效果通常与水平居中对齐相似。
垂直对齐选项:
-
Qt::AlignTop:将文本对齐到进度条区域的顶部边缘。
-
Qt::AlignBottom:将文本对齐到进度条区域的底部边缘。
-
Qt::AlignVCenter:将文本在进度条区域内垂直居中对齐。
这些标志可以通过按位或(|
)运算符组合使用,例如 Qt::AlignRight | Qt::AlignVCenter
可实现文本在区域内的右下角显示。
我们把 QProcessBar 的 alignment 属性设置为垂直⽔平居中.
设置完之后,我们发现
比刚才好看多了吧
我们现在来编写代码,其实这个代码和示例1是一模一样的
我们现在运行一下
通过上述⽅式,也可以修改⽂字的颜⾊,字体⼤⼩等样式。
现在我们就来写一下代码
我们运行一下,然后就会发现这个进度条会从0一直涨到100,然后就停止了
注意
进度条的具体进度数值并非随意设定,而是需要根据后台任务的实际完成情况来动态计算和更新。其核心原理是:将任务的当前完成量与其总量进行比较,从而得出一个准确的百分比。
设置步骤与典型示例
一个常见的场景是读取大文件时的进度显示,其设置流程如下:
-
确定总量:首先,获取整个任务的总工作量。例如,在文件读取任务中,通过系统调用获取文件的总体大小(总字节数)。这相当于设定了进度条的“最大值”(Maximum)。
-
计算当前进度:任务开始后,在任务执行的关键节点(如每次成功读取一段数据块后),累加已读取的数据量(当前字节数)。这个值即为“当前值”(Value)。
-
更新进度条:根据公式
当前进度 = (当前值 / 最大值) * 100%
计算出完成的百分比,并调用进度条控件的setValue
方法更新其显示。例如,一个500MB的文件,当读取了250MB时,进度条应更新至50%。
四.Calendar Widget
QCalendarWidget 表⽰⼀个"⽇历",形如
下面这些属性控制着日历组件的外观、行为和可用范围。
日期范围与选择控制
-
selectedDate
(当前选中日期)
这是日历中最核心的属性,类型为QDate
。它表示用户当前选定的日期。您可以通过读取这个属性来获取用户的选择,也可以通过设置这个属性来高亮显示一个特定的日期。 -
minimumDate
与maximumDate
(最小与最大日期)
这两个属性共同定义了用户可以选择的有效日期范围。任何早于minimumDate
或晚于maximumDate
的日期都会在日历上变为灰色不可用状态,用户无法选择。这常用于限制选择范围,如生日选择(不能晚于今天)或酒店预订(不能早于今天)。 -
selectionMode
(选择模式)
此属性控制用户是否可以选择日期。它通常有两种模式:-
SingleSelection
(默认值):允许用户选择单个日期。 -
NoSelection
:禁止选择任何日期。此时日历仅用于查看,用户无法通过点击改变selectedDate
。
-
显示与外观控制
-
firstDayOfWeek
(每周的第一天)
此属性决定日历视图的第一列显示星期几。例如,设置为Qt.Monday
,则日历的第一列将是星期一,最后一列是星期日。这可以根据不同地区的习惯进行设置(例如,北美通常从周日开始,而欧洲和中国通常从周一开始)。 -
gridVisible
(网格可见性)
一个布尔值,控制是否在日期数字之间显示表格网格线。开启后(设为true
),会显示细线,使日历看起来更像一个表格,提升可读性。 -
navigationBarVisible
(导航栏可见性)
导航栏是日历顶部的区域,包含月份/年份标题以及用于前后翻页的箭头按钮。如果将此属性设置为false
,将隐藏这个导航栏,用户将无法通过界面切换月份和年份。 -
horizontalHeaderFormat
(水平表头格式)
此属性控制日历顶部“星期几”表头的显示格式。常见选项有:-
ShortNames
:显示短名称(如“一”、“二”、“三”)。 -
LongNames
:显示长名称(如“星期一”、“星期二”)。 -
NoHorizontalHeader
:完全不显示星期几表头。
-
-
verticalHeaderFormat
(垂直表头格式)
此属性控制日历最左侧一列显示的内容。最常见的选项是:-
NoVerticalHeader
(默认):左侧为空,不显示任何内容。 -
ISOWeekNumbers
:显示 ISO 8601 标准的周编号(1 到 53)。这在需要查看一年中第几周的商业或项目管理场景中非常有用。
-
交互控制
-
dateEditEnabled
(日期编辑启用)
一个布尔值。当设置为true
时,用户可以直接点击日历顶部的月份或年份标题,此时会出现一个下拉选择器,允许用户快速跳转到任意年月,这对于长距离日期跳转非常方便。如果设置为false
,则只能使用导航箭头逐月浏览。
重要信号详解
信号是 Qt 框架中“事件”的体现。当用户与日历交互或日历状态改变时,会发出这些信号,您可以连接这些信号到自定义的槽函数以执行相应操作。
-
selectionChanged()
当用户选择的日期发生改变时发出。这是最常用的信号。无论是通过鼠标点击、键盘方向键选择,还是通过代码设置selectedDate
属性,只要选中的日期变了,这个信号就会被触发。通常用于实时响应日期选择,例如更新界面上的其他信息。 -
activated(const QDate &date)
当用户“激活”一个日期时发出。激活操作通常意味着用户双击一个有效的日期,或者当日期被高亮选中时按下了键盘上的回车键。形参date
即为被激活的日期。这个信号通常用于确认选择并执行下一步操作,例如在日期被激活后关闭对话框或打开一个详细视图。 -
currentPageChanged(int year, int month)
当日历当前显示的年份或月份改变时发出。即用户通过导航箭头或编辑标题切换了视图(例如从 2024 年 5 月切换到 2024 年 6 月)。形参year
和month
表示切换后的新年份和新月份(注意:month
的范围是 1-12)。这个信号常用于加载与当前显示月份相关的数据。
4.1.示例一——获取选中的⽇期
我们创建一个新项目
点进去
1) 在界⾯上创建⼀个 QCalendarWidget 和⼀个label
它们的objectname是
我们给QCalendarWidget设置槽函数
selectedDate
(当前选中日期)
这是日历中最核心的属性,类型为 QDate
。它表示用户当前选定的日期。您可以通过读取这个属性来获取用户的选择,也可以通过设置这个属性来高亮显示一个特定的日期。
我们运行程序,观察效果
我们随便选中一个日期
再随便选中一个日期