🔥 本文专栏:Qt
🌸作者主页:努力努力再努力wz



💪 今日博客励志语录 :
别怕慢,怕的是你一边走一边怀疑这条路,结果连慢都没攒下来。
思维导图

引入
在此前的学习中,我们已经了解了 Qt 中的一些常用控件,包括按钮类控件和标签类控件。通过这些内容,我们初步认识了 Qt 控件的基本使用方式,以及控件在界面显示、用户交互和信号槽机制中的作用。
本文将继续围绕 Qt 常用控件展开,进一步学习其他典型控件的使用方式,并结合实际代码分析其常见属性、信号槽连接方式以及在界面开发中的应用场景。
QLCDNumber 数字显示控件:从基础显示控制到倒计时案例
QLCDNumber:LCD 数字显示控件的基本使用与显示控制
首先要介绍的显示类控件是 QLCDNumber即 LCD 数字显示控件 。从功能定位上来看,QLCDNumber 和前面学习过的 QLabel 有一定相似之处,它们都可以在界面中展示内容。不过二者的侧重点并不相同:QLabel 更偏向通用文本或图片展示,可以显示普通字符串、富文本以及图片;而 QLCDNumber 则更侧重于数值展示,它会以类似电子表、计算器或数码管的 LCD 风格显示数字。
在实际开发中,QLCDNumber 的外观属性既可以在 Qt Designer 中通过可视化方式进行设置,也可以通过 C++ 代码进行控制。例如,可以设置它的数字位数、显示模式、小数点样式等属性。
对于 QLCDNumber 来说,设置当前显示内容的核心接口是 display()。该接口提供了多个重载版本,既可以接收整数,也可以接收浮点数,还可以接收字符串形式的内容,并将其以 LCD 数字风格显示到界面中。
cpp
ui->lcdNumber->display(10); // 显示整数
ui->lcdNumber->display(3.14); // 显示浮点数
ui->lcdNumber->display("12:30"); // 显示部分 LCD 风格支持的字符串
需要注意的是,虽然 display() 可以接收字符串参数,但 QLCDNumber 并不是通用字符串显示控件。由于它采用 LCD 数码管风格进行绘制,因此只能显示数字以及少量适合数码管表示的字符,例如负号、小数点、冒号、部分英文字母等。如果传入了不支持的字符,这些字符会被替换为空格。
因此,如果只是普通文本展示,更适合使用 QLabel;如果是数字、时间、计数器、倒计时等内容展示,则更适合使用 QLCDNumber。
除了设置显示值之外,QLCDNumber 还可以控制数值的显示进制。它支持十进制、十六进制、八进制和二进制显示模式:
cpp
ui->lcdNumber->setMode(QLCDNumber::Dec); // 十进制
ui->lcdNumber->setMode(QLCDNumber::Hex); // 十六进制
ui->lcdNumber->setMode(QLCDNumber::Oct); // 八进制
ui->lcdNumber->setMode(QLCDNumber::Bin); // 二进制
也可以使用对应的槽函数来切换显示模式:
cpp
ui->lcdNumber->setDecMode();
ui->lcdNumber->setHexMode();
ui->lcdNumber->setOctMode();
ui->lcdNumber->setBinMode();
需要注意的是,QLCDNumber 虽然可以通过 display(double) 接收浮点数,但只有在十进制模式下才会正常显示小数部分。当控件处于十六进制、八进制或二进制模式时,传入浮点数本身不会导致接口调用错误,但控件会按照该数值对应的整数形式进行进制显示,因此不会展示小数部分。
对于显示位数,QLCDNumber 提供了 setDigitCount() 接口。该接口用于设置控件当前能够显示的数字位数,例如:
cpp
ui->lcdNumber->setDigitCount(5);
这里的 5 表示控件最多显示 5 个数字位,而不是表示小数点后保留 5 位。如果传入 display() 的内容超出了当前 digitCount 所能容纳的范围,控件就无法完整显示该内容,并可能触发 overflow() 信号。
例如:
cpp
ui->lcdNumber->setDigitCount(5);
ui->lcdNumber->display(12345); // 可以完整显示
ui->lcdNumber->display(123456); // 超出 5 位,可能触发 overflow()
如果显示的是小数,还需要结合 setSmallDecimalPoint() 来理解。该接口用于控制小数点的显示方式:
cpp
ui->lcdNumber->setSmallDecimalPoint(true);
当参数为 true 时,小数点会以较小的形式显示在数字之间,此时小数点不单独占用一个数字位;当参数为 false 时,小数点会按照正常大小显示,并且会占用一个数字位。
例如,当显示 12.34 时,如果小数点占用一个显示位,那么整体显示结构可以理解为:
text
1 2 . 3 4
此时一共需要 5 个显示位置。
cpp
ui->lcdNumber->setDigitCount(5);
ui->lcdNumber->setSmallDecimalPoint(false);
ui->lcdNumber->display(12.34);
如果将 digitCount 设置为 4,并且小数点仍然占用一个显示位:
cpp
ui->lcdNumber->setDigitCount(4);
ui->lcdNumber->setSmallDecimalPoint(false);
ui->lcdNumber->display(12.34);
此时显示容量就不够了,因为 12.34 需要 1、2、.、3、4 共 5 个显示位置。
而如果启用较小的小数点显示模式:
cpp
ui->lcdNumber->setSmallDecimalPoint(true);
小数点就不会单独占用 digitCount 中的一个位置,主要占用显示位的是 1、2、3、4 这几个数字。
因此,QLCDNumber 中几个常用接口之间的关系可以这样理解:display() 用于设置当前显示的值;setMode() 用于控制数值按照什么进制显示;setDigitCount() 用于控制控件整体能够显示多少个数字位;setSmallDecimalPoint() 则用于控制小数点是否单独占用一个显示位。
除了通过 display() 设置当前显示内容之外,QLCDNumber 也提供了用于获取当前显示值的接口。常用的读取接口主要有两个:intValue() 和 value()。
其中,intValue() 用于获取当前显示值对应的整数形式,返回值类型为 int;而 value() 用于获取当前显示值对应的浮点形式,返回值类型为 double。
cpp
ui->lcdNumber->display(3.14);
int n = ui->lcdNumber->intValue(); // 获取整数形式的显示值
double d = ui->lcdNumber->value(); // 获取浮点形式的显示值
需要注意的是,虽然 display() 可以接收浮点数,但如果后续通过 intValue() 获取当前显示值,那么得到的是整数形式,并不会保留小数部分。因此,如果当前控件用于显示计数器、倒计时等整数场景,可以使用 intValue() 获取当前值;如果控件用于显示小数,并且希望保留小数部分,则应该使用 value() 接口。
因此,display() 可以理解为负责"写入当前显示值",而 intValue() 和 value() 则负责"读取当前显示值"。其中,intValue() 侧重整数读取,value() 侧重浮点数读取。
QLCDNumber 实战:结合 QTimer 实现倒计时效果
在认识了 QLCDNumber 的相关属性和常用接口之后,接下来我们通过一个常见的应用场景来进一步理解它的使用方式,这个应用场景就是倒计时。
倒计时的核心逻辑并不复杂:首先在界面中显示一个初始数值,然后每隔一段固定时间对该数值进行更新,直到数值减小到 0 为止。因此,这里除了需要使用 QLCDNumber 显示当前倒计时数值之外,还需要借助 QTimer 类来完成周期性触发任务。
QTimer 本质上是 Qt 提供的定时器类,它可以按照指定的时间间隔周期性地发出 timeout() 信号。我们只需要将该信号和自定义槽函数进行连接,就可以在槽函数中完成周期性任务,例如更新 QLCDNumber 当前显示的数值。
需要注意的是,QTimer 并不是显示类控件,它不会直接显示在界面中。前面学习 QLabel、QPushButton 等控件时,给控件传递父指针通常有两层含义:一方面是应用 Qt 的父子对象机制,让子对象的生命周期交给父对象管理;另一方面是确定该控件显示在哪个父窗口区域中。
但是对于 QTimer 来说,它并不继承 QWidget,而是继承自 QObject,因此它不具备界面显示能力。创建 QTimer 对象时传入 this,主要是为了应用 Qt 的对象树机制:
cpp
timer = new QTimer(this);
这样一来,timer 就会成为当前 Widget 对象的子对象。当 Widget 被销毁时,作为子对象的 QTimer 也会被自动销毁,从而避免我们手动释放定时器对象。
创建定时器对象之后,可以通过 start() 设置定时器的触发间隔。start() 接收的参数单位是毫秒,例如:
cpp
timer->start(1000);
表示定时器每隔 1000ms,也就是每隔 1 秒发出一次 timeout() 信号。接着通过 connect() 将 timeout() 信号和槽函数绑定起来:
cpp
connect(timer, &QTimer::timeout, this, &Widget::handler);
这样每当定时器触发时,都会自动调用 handler() 槽函数。在槽函数中,我们可以先通过 intValue() 获取 QLCDNumber 当前显示的整数值,然后判断倒计时是否已经结束。如果当前值已经小于等于 0,就调用 stop() 停止定时器;否则将当前值减 1,并再次通过 display() 更新到界面中。
完整代码如下:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 LCD 初始显示值
ui->lcdNumber->display(10);
// 创建定时器对象,并将当前窗口作为父对象
timer = new QTimer(this);
// 绑定 timeout 信号和自定义槽函数
connect(timer, &QTimer::timeout, this, &Widget::handler);
// 每隔 1000ms 触发一次 timeout 信号
timer->start(1000);
// 设置 LCD 控件样式
ui->lcdNumber->setStyleSheet(
"QLCDNumber {"
"color: green;"
"background-color: black;"
"border: 2px solid gray;"
"}"
);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handler()
{
int value = ui->lcdNumber->intValue();
if (value <= 0)
{
timer->stop();
return;
}
value--;
ui->lcdNumber->display(value);
}

在这个案例中,QLCDNumber 负责显示当前倒计时数值,而 QTimer 负责按照固定时间间隔触发更新逻辑。二者结合之后,就可以实现一个简单的倒计时效果。
QProgressBar 进度条控件:范围设置、进度更新与定时器模拟
根据上文,我们已经认识了 QLCDNumber 这种 LCD 数字显示控件。接下来要学习的显示类控件是 QProgressBar,也就是进度条控件。
QProgressBar 主要用于在界面中展示某个任务的完成进度。它会根据当前值在指定区间中的位置,计算出当前进度比例,并以进度条长度的形式展示出来。简单来说,进度条的显示效果取决于三个核心值:最小值、最大值以及当前值。
其中,最小值和最大值用于确定整个进度范围,当前值用于表示任务当前执行到了哪个位置。进度比例可以简单理解为:
text
当前进度比例 = (当前值 - 最小值) / (最大值 - 最小值)
因此,当我们将进度条范围设置为 0 ~ 100 时,当前值就可以直接理解为百分比。例如,当前值为 30,就表示任务完成了 30%;当前值为 100,就表示任务已经完成。
在代码中,可以通过 setMinimum() 和 setMaximum() 分别设置进度条的最小值和最大值:
cpp
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(100);
也可以使用 setRange() 一次性设置进度条的范围:
cpp
ui->progressBar->setRange(0, 100);
设置好范围之后,可以通过 setValue() 设置当前进度值:
cpp
ui->progressBar->setValue(0);
而如果需要获取当前进度条的值,则可以调用 value() 接口:
cpp
int value = ui->progressBar->value();
需要注意的是,QProgressBar 的当前值是整数类型,因此它通常适合用来表示整数进度。例如,在 0 ~ 100 的范围中,可以将当前值直接理解为百分比;而在文件读取、文件下载、文件传输等场景中,也可以将进度条范围设置为 0 ~ 文件总大小,再根据当前已经读取或传输的字节数更新进度条。
例如,在文件读取场景中,可以先获取文件总大小,然后将进度条的最大值设置为文件总字节数。随着文件不断被读取,再将已经读取的字节数设置为当前值,这样进度条就可以根据真实读取进度进行变化。
为了演示 QProgressBar 的基本使用方式,我们可以先实现一个简单的模拟进度条案例。这里仍然需要借助 QTimer 定时器类。QTimer 会按照指定时间间隔周期性地发出 timeout() 信号,我们将该信号和槽函数绑定起来,然后在槽函数中不断修改进度条的当前值,就可以模拟一个自动增长的进度条效果。
完整代码如下:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置进度条范围
ui->progressBar->setRange(0, 100);
// 设置进度条初始值
ui->progressBar->setValue(0);
// 创建定时器对象,并将当前窗口作为父对象
qtimer = new QTimer(this);
// 绑定 timeout 信号和自定义槽函数
connect(qtimer, &QTimer::timeout, this, &Widget::handler);
// 每隔 10ms 触发一次 timeout 信号
qtimer->start(10);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handler()
{
int value = ui->progressBar->value();
if (value >= 100)
{
qtimer->stop();
return;
}
value++;
ui->progressBar->setValue(value);
}
在这段代码中,首先将进度条的范围设置为 0 ~ 100,并将初始值设置为 0。随后创建一个 QTimer 定时器对象,并将它的 timeout() 信号连接到 handler() 槽函数。
当定时器启动后,它会每隔 10ms 触发一次 timeout() 信号,从而调用一次 handler()。在槽函数中,我们先通过 value() 获取进度条当前的值,如果当前值已经大于等于 100,说明进度条已经到达最大值,此时调用 stop() 停止定时器;否则将当前值加 1,再通过 setValue() 更新进度条显示。
通过这个案例可以看到,QProgressBar 负责展示当前进度,而 QTimer 负责周期性触发更新逻辑。二者结合之后,就可以实现一个自动增长的进度条效果。
QCalendarWidget 日历控件:显示属性、日期范围与选择事件
认识了进度条控件之后,接下来我们继续学习另一个常用的显示类控件:QCalendarWidget,也就是日历控件。
和前面学习的 QLCDNumber、QProgressBar 相比,QCalendarWidget 展示的内容结构要更加复杂。QLCDNumber 主要用于展示数字,QProgressBar 主要用于展示进度比例,而 QCalendarWidget 则会在界面中绘制出一个接近我们日常使用习惯的日历,用于展示日期信息,并支持用户选择具体日期。
从界面结构上来看,QCalendarWidget 通常由上方的导航区域和下方的日期表格组成。上方导航区域用于显示当前年份和月份,并提供月份切换功能;下方日期表格则按照周和日期进行组织,每一个单元格都对应一个具体日期。用户可以通过鼠标点击的方式在日历中选择日期。
对于 QCalendarWidget 来说,Qt 也提供了一系列接口用于控制日历的显示效果。首先,可以通过 setGridVisible() 设置日历表格是否显示网格线:
cpp
ui->calendarWidget->setGridVisible(true);
当参数为 true 时,日历中的日期单元格之间会显示网格线;当参数为 false 时,则不会显示网格线。
不显示网格线:

显示网格线:

其次,可以通过 setHorizontalHeaderFormat() 设置日历控件横向表头的显示格式。这里的横向表头,指的是日历日期区域上方用于显示星期名称的一行。例如,在日历表格中,日期区域上方通常会显示"星期一、星期二、星期三......"或者对应的缩写形式,这一行就是横向表头。

cpp
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames);
QCalendarWidget::ShortDayNames 表示使用较短的星期名称进行显示。例如在英文环境下,完整星期名称可能是 Monday、Tuesday、Wednesday,而较短名称则可能显示为 Mon、Tue、Wed。也就是说,ShortDayNames 会使用星期名称的缩写形式,避免表头内容过长。
除了 ShortDayNames 之外,QCalendarWidget 还提供了其他几种横向表头显示方式:
cpp
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::LongDayNames);
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames);
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::SingleLetterDayNames);
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::NoHorizontalHeader);
其中,LongDayNames 表示显示完整的星期名称,例如 Monday;ShortDayNames 表示显示较短的星期缩写,例如 Mon;SingleLetterDayNames 表示只显示单字母形式,例如 M;而 NoHorizontalHeader 则表示隐藏横向星期表头。
如果处于中文环境下,实际显示内容可能会受到系统语言环境和 Qt 本地化配置的影响。例如,完整名称可能显示为"星期一",较短名称可能显示为"周一"或者"一"。因此,在理解这个接口时,不需要死记具体显示成什么文字,只需要抓住它的核心作用:setHorizontalHeaderFormat() 用于控制日历上方"星期表头"的显示形式。
除了横向表头之外,QCalendarWidget 还可以通过 setVerticalHeaderFormat() 设置纵向表头的显示格式。
这里需要注意,纵向表头并不是日历顶部显示年份和月份的区域。顶部显示当前年份、月份,并且可以切换上一个月或下一个月的区域,更准确地说应该叫导航栏或导航区域。而纵向表头指的是日历日期表格最左侧的一列。
横向表头通常位于日期表格上方,用于显示一周中的星期名称,例如"周一、周二、周三......";而纵向表头则位于日期表格左侧,通常用于显示当前这一行日期属于一年中的第几周。
例如,当设置为:
cpp
ui->calendarWidget->setVerticalHeaderFormat(QCalendarWidget::ISOWeekNumbers);
此时日历表格最左侧会显示一列数字,例如 22、23、24 等。这些数字表示对应这一行日期属于当前年份中的第几周,也就是周编号。
可以简单理解为:
text
周一 周二 周三 周四 周五 周六 周日
22 1 2 3 4 5 6 7
23 8 9 10 11 12 13 14
24 15 16 17 18 19 20 21
其中,左侧的 22 表示这一行日期所在的这一周是当前年份中的第 22 周;下一行的 23 表示下一周是当前年份中的第 23 周。
如果不希望显示这一列周编号,也可以将纵向表头设置为隐藏:
cpp
ui->calendarWidget->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
因此,QCalendarWidget 中几个区域可以这样区分:顶部显示年份和月份的区域是导航栏;日期表格上方显示星期名称的一行是横向表头;日期表格左侧显示周编号的一列是纵向表头。
因此,QCalendarWidget 的基础显示属性可以先从三个方面理解:setGridVisible() 用于控制是否显示日期单元格之间的网格线;setHorizontalHeaderFormat() 用于控制横向星期表头的显示格式;setVerticalHeaderFormat() 用于控制纵向周编号表头的显示格式。
通过这些接口,我们可以对日历控件的基础显示效果进行定制,使其更加符合实际界面需求。
认识了 QCalendarWidget 的显示属性之后,我们还可以继续设置日历控件的可选日期范围。这里所谓的范围并不是普通数值范围,而是日期范围。可以通过 setMinimumDate() 设置最小可选日期,通过 setMaximumDate() 设置最大可选日期:
cpp
ui->calendarWidget->setMinimumDate(QDate(2026, 1, 1));
ui->calendarWidget->setMaximumDate(QDate(2026, 12, 31));
也可以使用 setDateRange() 一次性设置最小日期和最大日期:
cpp
ui->calendarWidget->setDateRange(QDate(2026, 1, 1), QDate(2026, 12, 31));
需要注意的是,最小日期和最大日期限制的是"用户可以选中的日期范围",而不是简单地控制哪些日期会被绘制出来。由于 QCalendarWidget 通常以完整月份的形式进行展示,为了保证日历表格结构完整,超出范围的日期仍然可能会显示在界面中。
不过,这些超出范围的日期通常会以灰色或禁用状态显示,表示它们不可选中。用户点击这些日期时,日历控件不会将其设置为当前选中日期,因此也不会触发正常的日期选择逻辑。也就是说,日期范围限制影响的是日期的"可选性",而不是一定影响日期是否出现在界面中。
除了设置可选日期范围之外,QCalendarWidget 还提供了日期选择相关的交互机制。当用户在日历中选中的日期发生变化时,控件会发出 selectionChanged() 信号。我们可以将该信号连接到自定义槽函数,然后在槽函数中获取当前选中的日期,并将其显示到标签控件中。
cpp
connect(ui->calendarWidget, &QCalendarWidget::selectionChanged,
this, &Widget::handler);
在槽函数中,可以通过 selectedDate() 获取当前选中的日期。该接口返回的是一个 QDate 类型的对象:
cpp
QDate date = ui->calendarWidget->selectedDate();
如果希望将这个日期显示到 QLabel 中,就需要先将 QDate 转换成字符串。这里可以调用 QDate 的 toString() 接口,并通过格式化字符串控制日期的显示格式:
cpp
ui->label->setText(date.toString("yyyy年MM月dd日"));
其中,yyyy 表示四位年份,MM 表示两位月份,dd 表示两位日期。需要注意的是,日期格式化字符串对大小写和重复次数有要求。例如,月份应该使用大写的 MM,日期应该使用小写的 dd。如果格式写错,就可能无法得到预期的显示结果。
当然,toString() 也可以不传递格式化字符串。此时 Qt 会按照默认格式将日期转换成字符串。不过在实际界面开发中,为了让显示效果更加明确,通常会主动指定日期格式。
完整代码如下:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDate>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置日历基础显示属性
ui->calendarWidget->setGridVisible(true);
ui->calendarWidget->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames);
ui->calendarWidget->setVerticalHeaderFormat(QCalendarWidget::ISOWeekNumbers);
// 设置日期可选范围
ui->calendarWidget->setMinimumDate(QDate(2026, 1, 1));
ui->calendarWidget->setMaximumDate(QDate(2026, 12, 31));
// 初始化标签内容,默认显示当前系统日期
ui->label->setText(QDate::currentDate().toString("yyyy年MM月dd日"));
// 当选中日期发生变化时,更新标签内容
connect(ui->calendarWidget, &QCalendarWidget::selectionChanged,
this, &Widget::handler);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handler()
{
QDate date = ui->calendarWidget->selectedDate();
ui->label->setText(date.toString("yyyy年MM月dd日"));
}

在这段代码中,QDate::currentDate() 用于获取系统当前日期,并在程序启动时初始化标签内容;而 selectedDate() 用于获取用户在日历控件中当前选中的日期。每当用户选择的日期发生变化时,selectionChanged() 信号都会触发槽函数,从而更新标签中显示的日期信息。

结语
那么这就是本篇文章的全部内容,我会持续更新,希望你能够多多关注,如果本文有帮助到你的话,还请三连加关注,你的支持就是我创作的最大动力!
