📑 目录
- [1.1 Label](#1.1 Label)
- 显示不同格式的文本
- 显示图片
- 文本对齐、自动换行、缩进、边距
- 设置伙伴
- [1.2 LCD Number](#1.2 LCD Number)
- 倒计时
- [1.3 ProgressBar](#1.3 ProgressBar)
- 设置进度条按时间增长
- [1.4 Calendar Widget](#1.4 Calendar Widget)
- 获取选中的日期
- [1.5 小结](#1.5 小结)
- [1.6 经典面试题](#1.6 经典面试题)
🏷️ 1.1 Label
- 在界⾯上创建三个 QLabel
尺⼨放⼤⼀些.objectName分别为label, label_2, label_3
📚 笔记:在代码中直接写死某个数值,这样的做法称之为"硬编码",举例如下:
ui->label->setGeometry(0,0,windowRect.width(),windowRect.height());在上面的代码中实在构造函数里,进行的这样的尺寸设置,整个设置相当于"一次性"的
一旦程序运行起来之后,
Qlabel的尺寸就固定下来了,窗口发生改变,此时,QLabel是不会变化的。

- 修改 widget.cpp, 设置三个 label 的属性
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setTextFormat(Qt::PlainText);//纯文本
ui->label->setText("演示Label的基本用法!");
ui->label_2->setTextFormat(Qt::RichText);//富文本
ui->label_2->setText("<b>这是一段富文本</b>");
ui->label_3->setTextFormat(Qt::MarkdownText);//MarkDown格式的标签
ui->label_3->setText("## 这是一段Markdown文本");
}
Widget::~Widget()
{
delete ui;
}
- 运⾏程序, 观察效果

代码⽰例: 显⽰图⽚
🐳说明 :虽然 QPushButton 也可以通过设置图标的⽅式设置图⽚, 但是并⾮是⼀个好的选择. 更多的时候
还是希望通过 QLabel 来作为⼀个更单纯的显⽰图⽚的⽅式.
- 在界⾯上创建⼀个
QLabel,objectName为label

- 创建
resource.qrc⽂件, 并把图⽚导⼊到qrc中.
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label大小和窗口一样大
ui->label->setGeometry(0,0,800,600);
QPixmap pixmap(":/long.png");
ui->label->setPixmap(pixmap);
}
Widget::~Widget()
{
delete ui;
}
🐳 笔记 :
Qt中,表示用户的操作有两类概念,一个是信号,一个是事件。当用户拖拽窗口大小的时候,就会触发resize事件(resizeEvent)。像resize这样的事件,是连续变化的,把窗口从A拖到B这个过程中,会触发一系列的resizeEvent,此时就可以借助resizeEvent来完成上述功能,可以让Widget窗口类,重写父类(QWidget)的resizeEvent函数。在鼠标拖动窗口尺寸时,过程中,这个函数就会被反复调用执行。每次一个
resizeEvent事件都会调用对应的虚函数。
- 修改代码, 设置
scaledContents属性
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label大小和窗口一样大
ui->label->setGeometry(0,0,800,600);
QPixmap pixmap(":/long.png");
ui->label->setPixmap(pixmap);
//设置内容伸缩
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
再次运⾏, 观察效果, 可以看到图⽚已经被拉伸, 可以把窗⼝填满了.

- 此时, 如果拖动窗⼝⼤⼩, 可以看到图⽚并不会随着窗⼝⼤⼩的改变⽽同步变化
为了解决这个问题, 可以在 Widget 中重写 resizeEvent 函数
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPixmap>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label大小和窗口一样大
ui->label->setGeometry(0,0,800,600);
QPixmap pixmap(":/long.png");
ui->label->setPixmap(pixmap);
//设置内容伸缩
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::resizeEvent(QResizeEvent* event)
{
//可以直接通过this->width()和this->height()设置label的新的尺寸,也可以通过event参数按到新的尺寸
//方式1:
//ui->label->setGeometry(0,0,this->width(),this->height());
//方式2:
QWidget::resizeEvent(event); // 调用父类实现(良好习惯)
ui->label->setGeometry(0,0,event->size().width(),event->size().height());
qDebug()<<event->size();
}
执⾏程序, 此时改变窗⼝⼤⼩, 图⽚也会随之变化.
🏀 注意:
此处的
resizeEvent函数我们没有⼿动调⽤, 但是能在窗⼝⼤⼩变化时被⾃动调⽤.这个过程就是依赖
C++中的多态来实现的.Qt框架内部管理着QWidget对象表⽰咱们的窗⼝. 在窗⼝⼤⼩发⽣改变时,Qt就会⾃动调⽤ resizeEvent 函数.但是由于实际上这个表⽰窗⼝的并⾮是
QWidget, ⽽是QWidget的⼦类, 也就是咱们⾃⼰写的
Widget. 此时虽然是通过⽗类调⽤函数, 但是实际上执⾏的是⼦类的函数(也就是我们重写后的resizeEvent).此处属于是 多态 机制的⼀种经典⽤法. 通过上述过程, 就可以把⾃定义的代码, 插⼊到框架内部执⾏. 相当于 "注册回调函数" .
代码⽰例: ⽂本对⻬, ⾃动换⾏, 缩进, 边距
- 创建四个
label,objectName分别是label到label_4
并且在QFrame中设置frameShape为Box(设置边框之后看起来会更清晰⼀些)

QFrame是QLabel的⽗类. 其中frameShape属性⽤来设置边框性质.
QFrame::Box:矩形边框QFrame::Panel:带有可点击区域的⾯板边框QFrame::WinPanel:Windows⻛格的边框QFrame::HLine:⽔平线边框QFrame::VLine:垂直线边框QFrame::StyledPanel:带有可点击区域的⾯板边框,但样式取决于窗⼝主题
- 编写
widget.cpp, 给这四个label设置属性
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置文字居中对齐
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->label->setText("垂直水平居中的文本");
//设置自动换行
ui->label_2->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_2->setWordWrap(true);
ui->label_2->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本");
//设置首行缩进
ui->label_3->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_3->setIndent(20);
ui->label_3->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本");
//设置边框
ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_4->setMargin(20);
ui->label_4->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本");
}
Widget::~Widget()
{
delete ui;
}
- 运⾏程序, 可以看到如下效果
第⼀个
label垂直⽔平居中第⼆个
label设置了wordWrap, 能够⾃动换⾏第三个
label设置了Indent, 左侧和上⽅和边框有间距. 右侧则没有.第四个
label设置了margin, 四个⽅向均有间距(图上仅体现出三个⽅向, 下⽅看不出来.

代码⽰例: 设置伙伴
- 创建两个
label和 两个radioButton.
objectName分别问label,label_2,radioButton,radioButton_2

🐳 笔记:
Qt中,QLabel中写的文本,是可以指定"快捷键",此处的快捷键的规则功能要比QPushButton弱很多
是在文本中使用 & 跟上一个字符来表示快捷键
比如
&A :通过键盘上的alt + a来触发这个快捷键
&B :通过键盘上的alt + b来触发这个快捷键
绑定了伙伴关系 后,通过了快捷键就可以选中对应的单选按钮和复选按钮。
但是注意, 这⾥的快捷键和 QPushButton 的不同. 需要搭配 alt 和 单个字⺟的⽅式才能触发.
- 编写 widget.cpp, 设置 buddy 属性
当然这⾥也可以使⽤ Qt Designer 直接设置.
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label的伙伴widget
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
Widget::~Widget()
{
delete ui;
}
- 运⾏程序, 可以看到, 按下快捷键
alt + a或者alt + b, 即可选中对应的选项.

🔢 1.2 LCD Num
⌚️说明:LCD Number⽰例: 倒计时**
- 在界⾯上创建⼀个
QLCDNumber, 初始值设为10.
objectName为lcdNu### 🔢 1.2 LCD Number中 数字了

- 修改
widget.h代码, 创建⼀个QTimer成员, 和⼀个updateTime函数
cpp
QTimer* timer;
void updateTime();
- 修改
widget.cpp, 在构造函数中初始化QTimer,并实现updateTime
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个timer实例
timer = new QTimer(this);
//连接信号槽,QTimer会每隔一定的时间触发一个timeout的信号,现在把timeout
//信号和updateTime()函数连接起来
connect(timer,&QTimer::timeout,this,&Widget::updateTime);
//启动QTimer,并且规定每隔1000ms触发一次timeout信号
timer->start(1000);//参数单位为毫秒
}
Widget::~Widget()
{
delete ui;
}
void Widget::updateTime()
{
qDebug()<<"updateTime()";
int value = ui->lcdNumber->intValue();
if(value <= 0)
{
//如果时间到,停止计时器
timer->stop();
return;
}
//否则,每隔一秒减一
ui->lcdNumber->display(value-1);//此处注意是使用display函数来给lcd设置数字
}
🐚 解释:
QTimer表⽰定时器. 通过start⽅法启动定时器之后, 就会每隔⼀定周期, 触发⼀次QTimer::timeout信号.使⽤
connect把QTimer::timeout信号和Widget::updateTime连接起来, 意味着每次触发QTimer::timeout都会执⾏Widget::updateTime通过
intValue获取到QLCDNumber内部的数值.如果
value的值归0了, 就停⽌QTimer. 接下来QTimer也就不会触发timeout信号了.
- 执⾏程序, 可以看到每隔⼀秒钟, 显⽰的数字就减少 1.

✏️笔记:
QT里面,界面有一个专门的线程去负责维护更新的(主线程)对于
GUI来说,内部包含了很多的隐藏状态,QT为了保证修改界面的过程中,线程安全不会是受到影响,
QT禁止了其他线程直接修改界面因此
QT为了保证线程安全,直接要求所有的对界面的修改操作,必须在主线程中完成对于
Qt的槽函数来说,默认情况下,槽函数都是由主线程调用的,在槽函数中修改界面没有任何问题
但是此处会有一个问题:
针对上述代码, 存在两个问题:
- 上述代码如果直接在 Widget 构造函数中, 通过⼀个循环 + sleep 的⽅式是否可以呢?
代码形如
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
#include<QDebug>
#include<thread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
int value= ui->lcdNumber->intValue();
while(1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if(value <= 0)
{
break;
}
ui->lcdNumber->display(value-1);
}
}
Widget::~Widget()
{
delete ui;
}
显然, 这个代码是不⾏的. 循环会使 Widget 的构造函数⽆法执⾏完毕, 此时界⾯是不能正确构造和显⽰的.
- 上述代码如果是在 Widget 构造函数中, 另起⼀个线程, 在新线程中完成 循环 + sleep 是否可以呢?
代码形如
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
#include<QDebug>
#include<thread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
std::thread t([this](){
int val = ui->lcdNumber->intValue();
while(true)
{
if(val <= 0)
{
break;
}
this->ui->lcdNumber->display(val-1);
}
});
}
Widget::~Widget()
{
delete ui;
}
这个代码同样是不⾏的.
Qt中规定, 任何对于GUI上内容的操作, 必须在 主线程 中完成. 像Widget构造函数, 以及connect连接的slot函数, 都是在主线程中调⽤的. ⽽我们⾃⼰创建的线程则不是.当我们⾃⼰的线程中尝试对界⾯元素进⾏修改时,Qt程序往往会直接崩溃.
✏️这样的约定主要是因为GUI中的状态往往是牵⼀发动全⾝的, 修改⼀个地⽅, 就需要同步的对其他内容进⾏调整.⽐如调整了某个元素的尺⼨, 就可能影响到内部的⽂字位置, 或者其他元素的位置. 这⾥⼀连串的修改, 都是需要按照⼀定的顺序来完成的.由于多线程执⾏的顺序⽆法保障, 因此
Qt从根本上禁⽌了其他线程修改GUI状态, 避免后续的
🔥 1.3 ProgressBar
🐳 说明:Progressbar⽰⼀个进度条.


代码⽰例: 设置进度条按时间增⻓
- 在界⾯上创建进度条,
objectName为progressBar
其中最⼩值设为0, 最⼤值设为100. 当前值设为50.

- 修改
widget.h, 创建QTimer和updateProgressBar函数.
cpp
QTimer* timer;
void updateProgressBar ();
- 修改
widget.cpp, 初始化QTimer
• 此处设置100ms触发⼀次timeout信号. 也就是⼀秒钟触发10次
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
timer = new QTimer(this);
//每隔100ms,进度就+1,即进度条+1
connect(timer,&QTimer::timeout,this,[this]()->void{
int val = ui->progressBar->value();
if(val >= 100)
{
timer->stop();
return;
}
ui->progressBar->setValue(val+1);
});
timer->start(100);//每隔100ms触发一次timeout信号
}
Widget::~Widget()
{
delete ui;
}
- 运⾏程序, 可以看到进度条中的进度在快速增⻓

✏️ 笔记:
虽然在
widget用到了QTimer,但是却没在.h文件中包含QTimer头文件,为啥这个代码编译不会报错?上述问题其实是通过
Qt内部提供的一个特殊技巧来实现的,在Qt中,有一个专门的头文件,这个头文件包含里Qt中所有类的声明,这个头文件,一般不会直接接触到,但是包含了其他Qt的头文件,都会间接的包含到这个头文件中。尽可能的减少
include头文件的个数,就可以有效的减少编译时间,Qt中就是用class前置声明的方式,来尽量减少头文件的包含。通过前置声明的方式,
Qt中的头文件,每个头文件包含的其他头文件的数量就能得到一定的降低。但是实际开发中,还是要该包含就包含,与其通过特殊技巧来缩短编译时间,不如引入更好的硬件资源,来更加高效的编译。
🏝️一小点说明在实际开发中, 进度条的取值, 往往是根据当前任务的实际进度来进⾏设置的.
⽐如需要读取⼀个很⼤的⽂件, 就可以获取⽂件的总的⼤⼩, 和当前读取完毕的⼤⼩, 来设置进度条的⽐例.
由于上⾯我们介绍了
Qt禁⽌在其他线程修改界⾯, 因此进度条的更新往往也是需要搭配定时器来完成的.通过定时器周期触发信号, 主线程调⽤对应的
slot函数. 再在slot函数中对当前的任务进度进⾏计算, 并更新进度条的界⾯效果.
1.4 📅 Calendar Widget
🌲说明:
QCalendarWidget表⽰⼀个 "⽇历" , 形如

核心属性
重要信号:

代码⽰例: 获取选中的⽇期
- 在界⾯上创建⼀个
QCalendarWidget和 ⼀个label,objectName为calendarWidget,label

- 给
QCalendarWidget添加slot函数
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
qDebug()<< date;
ui->label->setText(date.toString());
}
- 执⾏程序, 可以看到当选择不同的⽇期时,
label中的内容就会随之改变
!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/e0afa8ca2c42410a8ab38ab95d7f10b8.png
1.5 📚小结
本章介绍了 Qt 中常用的显示类控件,主要包括:
- QLabel :最基础的显示控件,可显示文本(纯文本、富文本、Markdown)和图片,支持对齐、自动换行、缩进、边距等属性,还可通过
setBuddy设置伙伴关系实现快捷键操作。 - QLCDNumber :用于显示数字的控件,类似老式计算器效果,常配合
QTimer实现倒计时等周期性更新。 - QProgressBar :进度条控件,用于展示任务执行进度,常与
QTimer搭配实现动态更新。 - QCalendarWidget:日历控件,可获取用户选中的日期,并通过信号槽机制实时响应。
核心要点回顾:
- 信号与事件 :Qt 中用户操作分为信号和事件两类,如窗口大小变化会触发
resizeEvent事件,可通过重写虚函数实现自定义逻辑。 - 多态机制 :重写
resizeEvent等虚函数时,Qt 框架通过多态自动调用子类实现,相当于"注册回调函数"。 - 定时器(QTimer):用于周期性执行任务,是 Qt 中实现界面动态更新的推荐方案。
- 线程安全:Qt 规定所有对 GUI 内容的操作必须在主线程中完成,禁止在其他线程直接修改界面,否则程序可能崩溃。
- 硬编码:在代码中直接写死数值的做法称为"硬编码",应尽量避免,推荐使用动态计算或布局管理器。
1.6 经典面试题
Q1:QLabel 和 QPushButton 都可以显示图片,实际开发中如何选择?
A:虽然 QPushButton 可以通过设置图标的方式显示图片,但 QLabel 更适合作为单纯的图片显示控件。QLabel 提供了 setPixmap、setScaledContents 等更丰富的图片显示控制能力,且语义更清晰。QPushButton 更适合需要点击交互的场景。
Q2:如何实现 QLabel 中的图片随窗口大小变化而同步缩放?
A:需要两步:
- 设置
setScaledContents(true)允许内容伸缩; - 重写
Widget::resizeEvent函数,在窗口大小变化时更新 QLabel 的尺寸,例如:
cpp
void Widget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
}
Q3:为什么在 Widget 构造函数中用循环 + sleep 实现倒计时是不可行的?
A:因为循环会使构造函数无法执行完毕,界面无法正确构造和显示。构造函数需要快速返回,让事件循环得以启动,界面才能正常渲染。
Q4:为什么不能在子线程中直接修改 GUI 控件?
A:Qt 规定任何对 GUI 内容的操作必须在主线程中完成。因为 GUI 内部状态往往"牵一发而动全身",修改一个元素可能需要同步调整其他内容,且多线程执行顺序无法保障,Qt 从根本上禁止其他线程修改 GUI 状态,以避免崩溃和状态不一致问题。
Q5:QTimer 的 timeout 信号触发周期如何设置?
A:通过 start 方法设置,参数单位为毫秒。例如 timer->start(1000) 表示每隔 1000 毫秒(1 秒)触发一次 timeout 信号。触发周期越短,界面更新越频繁。
Q6:QLabel 的伙伴关系(buddy)有什么作用?
A:通过 setBuddy 将 QLabel 与某个输入控件(如 QRadioButton、QCheckBox)绑定后,用户按下 QLabel 文本中指定的快捷键(如 &A 对应 Alt+A)即可选中对应的控件,提升操作效率和无障碍体验。
Q7:QProgressBar 的进度值在实际开发中如何确定?
A:应根据当前任务的实际进度来设置。例如读取大文件时,可获取文件总大小和当前已读取大小,计算比例后设置进度条的值。由于 Qt 禁止在其他线程修改界面,进度条更新通常搭配 QTimer 在槽函数中完成。
Q8:QCalendarWidget 如何获取用户选中的日期?
A:通过 selectedDate() 方法获取选中的 QDate 对象,并结合 selectionChanged 信号在槽函数中实时响应日期变化,例如将日期显示到)