Qt 显示类控件详解:从 QLabel 到 QCalendarWidget 的实战指南

📑 目录

  • [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

  1. 在界⾯上创建三个 QLabel
    尺⼨放⼤⼀些. objectName 分别为 label, label_2, label_3

📚 笔记:在代码中直接写死某个数值,这样的做法称之为"硬编码",举例如下:

ui->label->setGeometry(0,0,windowRect.width(),windowRect.height());

在上面的代码中实在构造函数里,进行的这样的尺寸设置,整个设置相当于"一次性"的

一旦程序运行起来之后,Qlabel的尺寸就固定下来了,窗口发生改变,此时,QLabel是不会变化的。

  1. 修改 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;
}
  1. 运⾏程序, 观察效果

    代码⽰例: 显⽰图⽚

🐳说明 :虽然 QPushButton 也可以通过设置图标的⽅式设置图⽚, 但是并⾮是⼀个好的选择. 更多的时候

还是希望通过 QLabel 来作为⼀个更单纯的显⽰图⽚的⽅式.

  1. 在界⾯上创建⼀个 QLabel, objectName 为 label
  2. 创建 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事件都会调用对应的虚函数。

  1. 修改代码, 设置 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;
}

再次运⾏, 观察效果, 可以看到图⽚已经被拉伸, 可以把窗⼝填满了.

  1. 此时, 如果拖动窗⼝⼤⼩, 可以看到图⽚并不会随着窗⼝⼤⼩的改变⽽同步变化

为了解决这个问题, 可以在 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).

此处属于是 多态 机制的⼀种经典⽤法. 通过上述过程, 就可以把⾃定义的代码, 插⼊到框架内部执⾏. 相当于 "注册回调函数" .

代码⽰例: ⽂本对⻬, ⾃动换⾏, 缩进, 边距

  1. 创建四个 label, objectName 分别是 label 到 label_4
    并且在QFrame中设置 frameShape 为 Box(设置边框之后看起来会更清晰⼀些)

    QFrame 是QLabel的⽗类. 其中 frameShape 属性⽤来设置边框性质.
  • QFrame::Box :矩形边框
  • QFrame::Panel :带有可点击区域的⾯板边框
  • QFrame::WinPanel :Windows⻛格的边框
  • QFrame::HLine :⽔平线边框
  • QFrame::VLine :垂直线边框
  • QFrame::StyledPanel :带有可点击区域的⾯板边框,但样式取决于窗⼝主题
  1. 编写 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;
}
  1. 运⾏程序, 可以看到如下效果

第⼀个label垂直⽔平居中

第⼆个label设置了 wordWrap, 能够⾃动换⾏

第三个label设置了 Indent, 左侧和上⽅和边框有间距. 右侧则没有.

第四个label设置了 margin, 四个⽅向均有间距(图上仅体现出三个⽅向, 下⽅看不出来.

代码⽰例: 设置伙伴

  1. 创建两个label和 两个 radioButton.
    objectName 分别问 label , label_2 , radioButton , radioButton_2

🐳 笔记:

Qt中,QLabel中写的文本,是可以指定"快捷键",此处的快捷键的规则功能要比QPushButton弱很多

是在文本中使用 & 跟上一个字符来表示快捷键

比如

&A :通过键盘上的alt + a来触发这个快捷键

&B :通过键盘上的alt + b来触发这个快捷键

绑定了伙伴关系 后,通过了快捷键就可以选中对应的单选按钮和复选按钮。

但是注意, 这⾥的快捷键和 QPushButton 的不同. 需要搭配 alt 和 单个字⺟的⽅式才能触发.

  1. 编写 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;
}
  1. 运⾏程序, 可以看到, 按下快捷键 alt + a 或者 alt + b, 即可选中对应的选项.

🔢 1.2 LCD Num

⌚️说明:LCD Number⽰例: 倒计时**

  1. 在界⾯上创建⼀个 QLCDNumber , 初始值设为 10.
    objectName 为 lcdNu### 🔢 1.2 LCD Number中 数字了
  1. 修改 widget.h 代码, 创建⼀个 QTimer 成员, 和⼀个 updateTime 函数
cpp 复制代码
QTimer* timer;
void updateTime();
  1. 修改 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. 执⾏程序, 可以看到每隔⼀秒钟, 显⽰的数字就减少 1.

✏️笔记:

QT里面,界面有一个专门的线程去负责维护更新的(主线程)

对于GUI来说,内部包含了很多的隐藏状态,QT为了保证修改界面的过程中,线程安全不会是受到影响,

QT禁止了其他线程直接修改界面

因此QT为了保证线程安全,直接要求所有的对界面的修改操作,必须在主线程中完成

对于Qt的槽函数来说,默认情况下,槽函数都是由主线程调用的,在槽函数中修改界面没有任何问题

但是此处会有一个问题:

针对上述代码, 存在两个问题:

  1. 上述代码如果直接在 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 的构造函数⽆法执⾏完毕, 此时界⾯是不能正确构造和显⽰的.

  1. 上述代码如果是在 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⽰⼀个进度条.

代码⽰例: 设置进度条按时间增⻓

  1. 在界⾯上创建进度条, objectName 为 progressBar
    其中最⼩值设为0, 最⼤值设为 100. 当前值设为 50.
  2. 修改 widget.h, 创建QTimer和 updateProgressBar 函数.
cpp 复制代码
QTimer* timer;
void updateProgressBar ();
  1. 修改 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;
}
  1. 运⾏程序, 可以看到进度条中的进度在快速增⻓

✏️ 笔记:

虽然在widget用到了QTimer,但是却没在.h文件中包含QTimer头文件,为啥这个代码编译不会报错?

上述问题其实是通过Qt内部提供的一个特殊技巧来实现的,在Qt中,有一个专门的头文件,这个头文件包含里Qt中所有类的声明,这个头文件,一般不会直接接触到,但是包含了其他Qt的头文件,都会间接的包含到这个头文件中。

尽可能的减少include头文件的个数,就可以有效的减少编译时间,Qt中就是用class前置声明的方式,来尽量减少头文件的包含。

通过前置声明的方式,Qt中的头文件,每个头文件包含的其他头文件的数量就能得到一定的降低。

但是实际开发中,还是要该包含就包含,与其通过特殊技巧来缩短编译时间,不如引入更好的硬件资源,来更加高效的编译。
🏝️一小点说明

在实际开发中, 进度条的取值, 往往是根据当前任务的实际进度来进⾏设置的.

⽐如需要读取⼀个很⼤的⽂件, 就可以获取⽂件的总的⼤⼩, 和当前读取完毕的⼤⼩, 来设置进度条的⽐例.

由于上⾯我们介绍了 Qt 禁⽌在其他线程修改界⾯, 因此进度条的更新往往也是需要搭配定时器来完成的.

通过定时器周期触发信号, 主线程调⽤对应的slot函数. 再在 slot 函数中对当前的任务进度进⾏计算, 并更新进度条的界⾯效果.

1.4 📅 Calendar Widget

🌲说明:QCalendarWidget 表⽰⼀个 "⽇历" , 形如

核心属性

重要信号:

代码⽰例: 获取选中的⽇期

  1. 在界⾯上创建⼀个 QCalendarWidget 和 ⼀个 label,objectName 为 calendarWidget , label
  2. 给 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());
}
  1. 执⾏程序, 可以看到当选择不同的⽇期时, label 中的内容就会随之改变
    !在这里插入图片描述(https://i-blog.csdnimg.cn/direct/e0afa8ca2c42410a8ab38ab95d7f10b8.png

1.5 📚小结

本章介绍了 Qt 中常用的显示类控件,主要包括:

  • QLabel :最基础的显示控件,可显示文本(纯文本、富文本、Markdown)和图片,支持对齐、自动换行、缩进、边距等属性,还可通过 setBuddy 设置伙伴关系实现快捷键操作。
  • QLCDNumber :用于显示数字的控件,类似老式计算器效果,常配合 QTimer 实现倒计时等周期性更新。
  • QProgressBar :进度条控件,用于展示任务执行进度,常与 QTimer 搭配实现动态更新。
  • QCalendarWidget:日历控件,可获取用户选中的日期,并通过信号槽机制实时响应。

核心要点回顾:

  1. 信号与事件 :Qt 中用户操作分为信号和事件两类,如窗口大小变化会触发 resizeEvent 事件,可通过重写虚函数实现自定义逻辑。
  2. 多态机制 :重写 resizeEvent 等虚函数时,Qt 框架通过多态自动调用子类实现,相当于"注册回调函数"。
  3. 定时器(QTimer):用于周期性执行任务,是 Qt 中实现界面动态更新的推荐方案。
  4. 线程安全:Qt 规定所有对 GUI 内容的操作必须在主线程中完成,禁止在其他线程直接修改界面,否则程序可能崩溃。
  5. 硬编码:在代码中直接写死数值的做法称为"硬编码",应尽量避免,推荐使用动态计算或布局管理器。

1.6 经典面试题

Q1:QLabel 和 QPushButton 都可以显示图片,实际开发中如何选择?

A:虽然 QPushButton 可以通过设置图标的方式显示图片,但 QLabel 更适合作为单纯的图片显示控件。QLabel 提供了 setPixmap、setScaledContents 等更丰富的图片显示控制能力,且语义更清晰。QPushButton 更适合需要点击交互的场景。

Q2:如何实现 QLabel 中的图片随窗口大小变化而同步缩放?

A:需要两步:

  1. 设置 setScaledContents(true) 允许内容伸缩;
  2. 重写 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 信号在槽函数中实时响应日期变化,例如将日期显示到)

相关推荐
.道阻且长.1 小时前
C++11 :右值引用的作用,引用折叠和完美转发
java·开发语言·c++
code2cat1 小时前
Java进阶篇之StampedLock:先读取,再校验,必要时退回读锁
java·开发语言
阿里嘎多学长2 小时前
2026-09-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管
孙启超3 小时前
【AI开发之Rust】第 22 课:一键多平台与工程收尾 —— CI、发布检查与结课
开发语言·后端·rust
kaixin_learn_qt_ing10 小时前
Qt插件机制demo示例
qt
落魄实习生11 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
茉莉玫瑰花茶11 小时前
GO [ 方法 ]
开发语言·后端·golang
狗凯之家源码网12 小时前
微博图床 PHP 系统搭建与实战应用指南
android·开发语言·php
Quz12 小时前
QML Flickable:内容滚动与边界行为
qt