文章目录
- [1. Label](#1. Label)
- [2. LCD Number](#2. LCD Number)
- [3. ProgressBar](#3. ProgressBar)
- [4. Calendar Widget](#4. Calendar Widget)
1. Label
QLabel 可以用来显是文本和图片.
核心属性如下
| 属性 | 说明 |
|---|---|
| text | QLabel 中的文本 |
| textFormat | 文本的格式. + Qt::PlainText 纯文本 + Qt::RichText 富文本(支持 html 标签) + Qt::MarkdownText markdown 格式 + Qt::AutoText 根据文本内容自动决定文本格式. |
| pixmap | QLabel 内部包含的图片. |
| scaledContents | 设为 true 表是内容自动拉伸填充 QLabel 设为 false 则不会自动拉伸 |
| alignment | 对齐方式. 可以设置水平和垂直方向如何对齐. |
| wordWrap | 设为 true 内部的文本会自动换行. 设为 false 则内部文本不会自动换行. |
| indent | 设置文本缩进. 水平和垂直方向都生效. |
| margin | 内部文本和边框之间的边距. 不同于 indent, 但是是上下左右四个方向都同时有效. 而indent 最多只是两个方向有效(具体哪两个方向有效取决于 alignment ) |
| openExternalLinks | 是否允许打开一个外部的链接. (当 QLabel 文本内容包含 url 的时候涉及到) |
| buddy | 给 QLabel 关联一个 "伙伴" , 这样点击 QLabel 时就能激活对应的伙伴. 例如伙伴如果是一个 QCheckBox, 那么该 QCheckBox 就会被选中. |
代码示例:显示不同格式的文本
在界面上创建三个 QLabel
尺寸放大一些. objectName 分别为 label, label_2, label_3

修改 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("这是一段纯文本");
ui->label_2->setTextFormat(Qt::RichText);
ui->label_2->setText("<b>这是一段富文本</b>");
ui->label_3->setTextFormat(Qt::MarkdownText);
ui->label_3->setText("# 这是一段markdown文本");
}
Widget::~Widget()
{
delete ui;
}
注意:在富文本里使用标签,表示文本加粗,而markdown里,#则是一级标题.
运行结果:

代码示例:显示图片
虽然 QPushButton 也可以通过设置图标的方式设置图片, 但是并非是一个好的选择. 更多的时候还是希望通过 QLabel 来作为一个更单纯的显示图片的方式.
在界面上创建一个 QLabel, objectName 为 label

创建 resource.qrc 文件, 并把图片导入到 qrc 中

修改 widget.cpp, 给 QLabel 设置图片
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 先把 QLabel 设置成和窗口一样大, 并且把这个 QLabel 左上角设置到窗口的左上角这里.
// 让整个 QLabel 铺满整个窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/huaji.jpg");
ui->label->setPixmap(pixmap);
// 启动自动拉伸. 此时图片就能够填充满整个窗口了.
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
运行结果:

此时, 如果拖动窗口大小, 可以看到图片并不会随着窗口大小的改变而同步变化.

用户的操作,会对应一些信号~~
Qt 中,表示用户的操作,有两类概念,一个是信号,另一个是事件~~
当用户拖拽修改窗口大小的时候,就会触发 resize 事件(resizeEvent),像 resize 这样的事件,是连续变化的,把窗口尺寸从 A 拖到 B 这个过程中,会触发出一系列的 resizeEvent.
此时就可以借助 resizeEvent 来完成上述的功能.
为了解决这个问题,可以让 Widget 窗口类, 重写父类 (QWidget) 的 resizeEvent 虚函数
注意:在鼠标拖动窗口尺寸的过程中,这个函数就会被反复调用执行~~每次触发一个 resizeEvent 事件都会调用一次对应的虚函数~~由于此处进行了函数重写,调用父类的虚函数就会实际调用到子类的对应的函数(多态)
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QResizeEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 先把 QLabel 设置成和窗口一样大, 并且把这个 QLabel 左上角设置到窗口的左上角这里.
// 让整个 QLabel 铺满整个窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/huaji.jpg");
ui->label->setPixmap(pixmap);
// 启动自动拉伸. 此时图片就能够填充满整个窗口了.
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
// 此处的形参 event 是非常有用的, 这里就包含了触发这个 resize 事件这一时刻, 窗口的尺寸的数值.
void Widget::resizeEvent(QResizeEvent *event)
{
qDebug() << event->size();
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
}
运行结果:

代码示例:文本对齐, 自动换行, 缩进, 边距
创建四个 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(25);
ui->label_4->setText("这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本");
}
Widget::~Widget()
{
delete ui;
}
运行结果,可以看到如下效果
- 第一个 label 垂直水平居中
- 第二个 label 设置了 wordWrap, 能够自动换行
- 第三个 label 设置了 Indent, 左侧和上方和边框有间距. 右侧则没有.
- 第四个 label 设置了 margin, 四个方向均有间距

代码示例:设置伙伴
创建两个 label 和 两个 radioButton,objectName 分别为 label , label_2 , radioButton , radioButton_2

此处把 label 中的文本设置为 "快捷键 &A" 这样的形式.
其中 & 后面跟着的字符, 就是快捷键,可以通过 alt + A 的方式来触发该快捷键.
但是注意, 这里的快捷键和 QPushButton 的不同. 需要搭配 alt 和 单个字母的方式才能触发.
编写 widget.cpp, 设置 buddy 属性
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
Widget::~Widget()
{
delete ui;
}
运行程序, 可以看到, 按下快捷键 alt + a 或者 alt + b, 即可选中对应的选项

2. LCD Number
QLCDNumer 是一个专门用来显示数字的控件. 类似于 "老式计算器" 的效果

核心属性
| 属性 | 说明 |
|---|---|
| intValue | QLCDNumber 显示的数字值(int). |
| value | QLCDNumber 显示的数字值(double). 和 intValue 是联动的. 例如给 value 设为 1.5, intValue 的值就是 2. 另外, 设置 value 和 intValue 的方法名字为 display , 而不是 setValue 或 者 setIntValue . |
| digitCount | 显示几位数字. |
| mode | 数字显示形式. 1. QLCDNumber::Dec :十进制模式,显示常规的十进制数字。 2. QLCDNumber::Hex :十六进制模式,以十六进制格式显示数字。 3. QLCDNumber::Bin :二进制模式,以二进制格式显示数字。 4. QLCDNumber::Oct :八进制模式,以八进制格式显示数字。 只有十进制的时候才能显十小数点后的内容. |
| segmentStyle | 设置显示风格. 1. QLCDNumber::Flat :平面的显示风格,数字呈现在一个平坦的表面上。 2. QLCDNumber::Outline :轮廓显示风格,数字具有清晰的轮廓和阴影效果。 3. QLCDNumber::Filled :填充显示风格,数字被填充颜色并与背景区分开。 |
| smallDecimalPoint | 设置比较小的小数点. |
代码示例:倒计时
在界面上创建一个 QLCDNumber , 初始值设为 5,objectName 为 lcdNumber

修改 widget.h 代码, 创建一个 QTimer 成员, 和一个 updateTime 函数
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void updateTime();
private:
Ui::Widget *ui;
QTimer* timer;
};
#endif // WIDGET_H
此处关键要点是要实现"每秒钟-1"这个效果
周期性的执行某个逻辑~~"定时器"
C++ 标准库中,没有提供定时器的实现.但Boost 里面提供了对应的功能.
Qt 中也封装了对应的定时器~~ (结合了信号槽机制的) QTimer
通过这个类创建出来的对象,就会产生一个 timeout 这样的信号~~
可以通过start方法来开启定时器,并且参数中设定触发timeout信号的周期
修改 widget.cpp
使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来, 意味着每次触发 QTimer::timeout 都会执行 Widget::updateTime
实现 updateTime
- 通过 intValue 获取到 QLCDNumber 内部的数值.
- 如果 value 的值归 0 了, 就停止 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);
// 创建 QTimer 实例
timer = new QTimer(this);
// 把 QTimer 的 timeout 信号和咱们自己的槽函数进行连接
connect(timer, &QTimer::timeout, this, &Widget::updateTime);
// 启动定时器, 参数是触发 timeout 的周期. 单位是 ms
timer->start(1000);
}
Widget::~Widget()
{
delete ui;
}
void Widget::updateTime()
{
int value = ui->lcdNumber->intValue();
if(value <= 0)
{
timer->stop();
return;
}
ui->lcdNumber->display(value-1);
}
运行结果:

针对上述代码, 存在两个问题:
- 上述代码如果直接在 Widget 构造函数中, 通过一个循环 + sleep 的方式是否可以呢?
代码形如
cpp
#include "widget.h"
#include "ui_widget.h"
#include <thread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
int value = ui->lcdNumber->intValue();
while(true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if(value <= 0)
{
break;
}
value -= 1;
ui->lcdNumber->display(value);
}
}
Widget::~Widget()
{
delete ui;
}
显然, 这个代码是不行的. 循环会使 Widget 的构造函数无法执行完毕, 此时界面是不能正确构造和显示的.
- 上述代码如果是在 Widget 构造函数中, 另起一个线程, 在新线程中完成 循环 + sleep 是否可以呢?
代码形如
cpp
#include "widget.h"
#include "ui_widget.h"
#include <thread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
std::thread t([this](){
int value = ui->lcdNumber->intValue();
while(true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if(value <= 0)
{
break;
}
value -= 1;
ui->lcdNumber->display(value);
}
});
}
Widget::~Widget()
{
delete ui;
}
这个代码同样是不行的. Qt 中规定, 任何对于 GUI 上内容的操作, 必须在 主线程 中完成. 像 Widget 构造 函数, 以及 connect 连接的 slot 函数, 都是在主线程中调用的. 而我们自己创建的线程则不是.
当我们自己的线程中尝试对界面元素进行修改时, Qt 程序往往会直接崩溃
这样的约定主要是因为 GUI 中的状态往往是牵一发动全身的, 修改一个地方, 就需要同步的对其他内容进>行调整.
比如调整了某个元素的尺寸, 就可能影响到内部的文字位置, 或者其他元素的位置. 这里一连串的修改, 都是需要按照一定的顺序来完成的.
由于多线程执行的顺序无法保障, 因此 Qt 从根本上禁止了其他线程修改 GUI 状态, 避免后续的一系列问题
综上所述, 使用定时器, 是实现上述功能的最合理方案.
后续如果我们也有类似的需要 "周期性修改界面状态" 的需求, 也需要优先考虑使用定时器
3. ProgressBar
使用 QProgressBar 表示一个进度条.
注意, 不要把 ProgressBar 拼写成 ProcessBar !
核心属性
| 属性 | 说明 |
|---|---|
| minimum | 进度条最小值 |
| maximum | 进度条最大值 |
| value | 进度条当前值 |
| alignment | 文本在进度条中的对齐方式. + Qt::AlignLeft : 左对齐 + Qt::AlignRight : 右对齐 + Qt::AlignCenter : 居中对齐 + Qt::AlignJustify : 两端对齐 |
| textVisible | 进度条的数字是否可见. |
| orientation | 进度条的方向是水平还是垂直 |
| invertAppearance | 是否是朝反方向增长进度 |
| textDirection | 文本的朝向. |
| format | 展示的数字格式. 常用格式如下: + %p :表示进度的百分比(0-100) + %v :表示进度的数值(0-100) + %m :表示剩余时间(以毫秒为单位) + %t :表示总时间(以毫秒为单位) |
代码示例:设置进度条按时间增长
在界面上创建进度条, objectName 为 progressBar

其中最小值设为 0, 最大值设为 100. 当前值设为 0.
修改 widget.h, 创建 QTimer 和 handle 函数.
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handle();
private:
Ui::Widget *ui;
QTimer* timer;
};
#endif // WIDGET_H
修改 widget.cpp, 初始化 QTimer
- 此处设置 100ms 触发一次 timeout 信号. 也就是一秒钟触发 10 次.
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Widget::handle);
timer->start(100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
int value = ui->progressBar->value();
if(value >= 100)
{
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
运行结果:

在实际开发中, 进度条的取值, 往往是根据当前任务的实际进度来进行设置的.
比如需要读取一个很大的文件, 就可以获取文件的总的大小, 和当前读取完毕的大小, 来设置进度条的比例.
由于上面我们介绍了 Qt 禁止在其他线程修改界面, 因此进度条的更新往往也是需要搭配定时器来完成的.
通过定时器周期触发信号, 主线程调用对应的 slot 函数. 再在 slot 函数中对当前的任务进度进行计算, 并更新进度条的界面效果.
代码示例:创建一个红色的进度条
不要忘了, QProgressBar 同样也是 QWidget 的子类, 因此我们可以使用 styleSheet 通过样式来修改进度条的颜色
在界面上创建一个进度条.
然后在 Qt Designer 右侧的属性编辑器中, 找到 QWidget 的 styleSheet 属性.
编辑如下内容:
- 其中的 chunk 是选中进度条中的每个 "块" . 使用 QProgressBar::text 则可以选中文本.

同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.

此处如果不设置 alignment , 进度条中的数字会跑到左上角. 暂时只能先使用 alignment 来手动调整下
运行结果:

通过上述方式, 也可以修改文字的颜色, 字体大小等样式.
4. Calendar Widget
QCalendarWidget 表示一个 "日历"
核心属性
| 属性 | 说明 |
|---|---|
| selectDate | 当前选中的日期 |
| minimumDate | 最小日期 |
| maximumDate | 最大日期 |
| firstDayOfWeek | 每周的第一天(也就是日历的第一列) 是周几. |
| gridVisible | 是否显示表格的边框 |
| selectionMode | 是否允许选择日期 |
| navigationBarVisible | 日历上方标题是否显示 |
| horizontalHeaderFormat | 日历上方标题显示的日期格式 |
| verticalHeaderFormat | 日历第一列显示的内容格式 |
| dateEditEnabled | 是否允许日期被编辑 |
重要信号
| 信号 | 说明 |
|---|---|
| selectionChanged(const QDate&) | 当选中的日期发生改变时发出 |
| activated(const QDate&) | 当双击一个有效的日期或者按下回车键时发出,形参是一个QDate类型,保存了选中的日期 |
| currentPageChanged(int, int) | 当年份月份改变时发出,形参表示改变后的新年份和月份 |
代码示例:获取选中的日期
在界面上创建一个 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());
}
运行结果:
