QT跨平台应用程序开发框架(6)—— 常用显示类控件

发布于:2025-07-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一,Label

1.1 主要属性

1.2 文本格式

1.3 设置图片

1.4 其它常用属性

1.5 设置伙伴

二,LCD Number

2.1 主要属性

2.2 实现倒计时

​2.3 两个问题

三,ProgressBar

3.1 主要属性

3.2 进度条按时间增长

3.3 改变样式

3.4 一个问题

四,Calendar Widget

4.1 主要属性

4.2 获取选中的日期 

一,Label

1.1 主要属性

QLabel 可以用来显示文本或图片,主要属性如下:

属性 说明
text QLabel 中的文本
textFormat

文本的格式:

  • Qt::PlainText:纯文本
  • Qt::RichText:富文本(⽀持html标签)
  • Qt::MarkdownText:markdown格式
  • Qt::AutoText:根据文本内容自动决定文本格式
pixmap Qlabel 内部包含的图片
scaledContents 设为 true表示图片自动拉伸填充满 QLabel,false就不会
alignment 对齐方式,可设置水平和垂直方向如何对齐
wordWrap

设为 true 内部文本会自动换行,false 则不会

QLabel 不会提供滚动条,别的控件才有,比如 QtextEdit(多行编辑框)

indent 设置文本缩进,水平和垂直方向都生效
margin 内部文本和边框之间的边距
openExternalLinks 是否允许打开一个外部的链接(QLabel 文本内容包含 URL 时涉及)
buddy

给 QLabel 关联⼀个"伙伴",这样点击QLabel时就能激活对应的伙伴

例如伙伴如果是⼀个QCheckBox,那么该QCheckBox就会被选中

 下面我们来搞几个具体的例子,演示下上表格的部分属性

1.2 文本格式

先创建三个label用于展示不同的格式,然后可以在构造函数里设置不同文本格式:

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>"); //b标签表示加粗
    
    ui->label_3->setTextFormat(Qt::MarkdownText);
    ui->label_3->setText("## 这是一段 markdown ⽂本"); // ##表示二级标题
}

 

1.3 设置图片

首先用 qrc 准备一张图片,然后就可以对 Label 设置图片:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //先把整个 Label 铺满窗口,然后让 Label 的左上角设置到窗口的左上角
    ui->label->setGeometry(0, 0, this->width(), this->height());

    QPixmap pixmap(":/deepseek.png");
    ui->label->setPixmap(pixmap);
    ui->label->setScaledContents(true); //有时候图片尺寸会小于窗口,那么这个就可以让图片拉伸
}

但是上面我们对 Label 尺寸的设置是“一次性”的,就上面的程序而言,只要我们扩大或缩小窗口大小,里面的 Label控件大小是不会变的,所以下面我们让 Label 的大小随着窗口大小实时发生改变

原理

  • 用户的绝大部分操作,会对应一些事件
  • 但 Qt 中,除了信号,还有一个“事件”也用来表示用户的操作
  • 鼠标拖拽窗口大小时,会让 Qt 触发一个 resize 事件(resizeEvent),而且我们改变窗口尺寸的过程中,会触发一系列的 resize事件
  • 此时就可以借助 resizeEvent 来完成功能,重写父类 QWidget 的 resizeEvent虚函数,而且在鼠标拖动窗口尺寸过程中,会持续调用这个虚函数进而调用子类的函数

先在头文件里声明函数:

然后实现该函数即可:

//此处的形参 event 包含了触发 resize 事件时,窗口尺寸的数值
void Widget::resizeEvent(QResizeEvent *event)
{
    //qDebug() << event->size();
    ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
}

 效果如下:

1.4 其它常用属性

先创建几个带边框的 Label,如下图:

 然后在构造函数中,给这几个 label 设置不同的属性:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    //①设置对齐方式
    ui->setupUi(this);
    ui->label->setText("这是一段文本");
    ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //设置文本对齐方式,参数也是枚举,这两个表示垂直和水平居中,就是显示在框框中间
    //水平方向有靠左、中、右三种,垂直方向有靠上、中、下三种,一般用两个组合使用

    //②设置自动换行
    ui->label_2->setText("Tcp全称“传输控制协议(Transmission Control Protocol)”,是当今互联网使用最广泛的传输层协议,因为它基于通信时保证可靠性,并且对于高效传输也有一定策略,是目前应用层底层使用的非常常见的网络协议");
    ui->label_2->setWordWrap(true); //表示开启自动换行

    //③设置缩进
    ui->label_3->setText("这是另一段文本");
    ui->label_3->setIndent(50); //表示在显示在程序上时给字体前面加上多少像素的空白
    //如果是label_2那样的长文本的话,会给所有的行都添加缩进

    //④设置边距
    ui->label_4->setText("Tcp的三次握手是验证双方通信信道的最小次数,能够快速建立连接;并且奇数次握手,可以确保一般情况下握手失败的连接成本是嫁接在客户端的,能保证服务器本身的稳定性");
    ui->label_4->setWordWrap(true); //开启自动换行
    ui->label_4->setMargin(50); //设置边距,表示文本内容的上下左右四个方向都要留出部分像素的空白
}

效果如下:

1.5 设置伙伴

这个和 HTML 前端中一样的,有时候按钮旁边会有一些字,但是为了方便用户点击,一般用户直接点击按钮的旁边的文字也可以选中按钮,所以这个设置伙伴就是将文本和按钮“绑定”

先创建两个单选框和两个文本框:

关于 QLabel 快捷键:

  • Qt 中 Label 的快捷键是在文本中使用符号 ‘ & ’ 跟上一个字符来表示快捷键
  • 比如 &A ,然后需要通过键盘上的 Alt + a 才能触发快捷键
  • 绑定了伙伴关系之后,就可以通过快捷键选中对应的单选或复选按钮了
  • 但是这个快捷键没有我们前面QPushButton的功能那么强大,所以了解即可

然后就是通过代码将下面的文本框添加“伙伴”,如下代码:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //设置 label 和 radioButton 的伙伴关系
    ui->label->setBuddy(ui->radioButton);
    ui->label_2->setBuddy(ui->radioButton_2);
}

然后我们就可以通过 Alt + A 或 Alt + B 的快捷键方式来选中按钮了:

二,LCD Number

2.1 主要属性

QLCDNumber 是一个专门用来显示数字的控件,类似于“老式计算器”的效果,主要属性如下:

属性 说明
intValue 显示的数字值(int)
value

显示的数字值(double)

  • 和 intValue 是联动的,例如 value 设为1.5,那么intValue 的值就是2
  • 另外,设置 value 和 intValue 的方法名字为 display,而不是 setValue 或 setIntValue
digitCount 显示几位数字
mode

数字显示形式

  • QLCDNumber::Dec:十进制模式,显示常规的十进制数字
  • QLCDNumber::Hex:十六进制模式,以十六进制格式显示数字
  • QLCDNumber::Bin:二进制模式,以二进制格式显示数字
  • QLCDNumber::Oct:八进制模式,以八进制格式显示数字

只有十进制才能显示小数点后的内容

segmentStyle

设置显示风格

  • QLCDNumber::Flat:平面的显示风格,数字呈现在一个平坦的表面上
  • QLCDNumber::Outline:轮廓显示风格,数字具有清晰的轮廓哦和阴影效果
  • QLCDNumber::Filled:填充显示风格,数字被填充颜色并与背景区分开
smallDecimalPoint 设置比正常大小还小的小数点

下面通过例子来演示上述效果:

2.2 实现倒计时

我们先使用 QLCDNumber 显示一个初始的数值,比如4,然后程序启动后,每过一秒数字就 -1,直到 0 就结束

先创建一个LCD,如下:

然后我们现在的关键点就是要实现“每秒钟 -1” 这个效果,也就是周期性的执行某个逻辑

关于“定时器”功能

  • C++ 标准库里没有提供定时器的相关接口(Boost库里面有)
  • 但是Qt 中是封装了对应的定时器的,涉及到的类叫做 QTimer,创建出来的对象会产生 timeout 这样的信号
  • 我们可以通过 start 方法来开启定时器,并在参数中设定触发 timeout 信号的周期 
  • 然后结合 connect ,把这个 timeout 信号绑定到需要的槽函数中,就可以执行逻辑,修改 LCDNumber 中的数字了

我们先在头文件添加 定时器对象和槽函数的声明:

然后就是倒计时的逻辑了:

#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->lcdNumber->display(4); //设置一个初始值
    timer = new  QTimer(this);
    connect(timer, &QTimer::timeout, this, &Widget::handle);
    //将 QTimer 的 timeout 信号和我们自己创建的槽函数进行关联
    timer->start(1000); //启动定时器,单位ms,1000表示每隔一秒触发一次信号
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handle()
{
    int value = ui->lcdNumber->intValue();
    if(value <= 0)
    {
        timer->stop();
        return;
    }
    ui->lcdNumber->display(value - 1);
}

效果如下:

2.3 两个问题

问题一:如果我不用计时器,直接在 Widget 的构造函数里,通过 “循环 + sleep(1)” 的方式是否可以呢

例如下列代码:

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;
        ui->lcdNumber->display(value - 1);
    }
}

这个方法直接叉掉,在构造函数里搞循环,会导致构造函数无法退出,此时界面就无法显示任何控件

问题二:那么我是否可以另外创建一个线程,在新线程里完成 循环 + sleep(1) 可以吗?

如下代码:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建线程t,并添加lamdba线程函数
    std::thread t([this]() {
        int value = this->ui->lcdNumber->intValue();
        while (true) 
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            if (value <= 0) break;
            this->ui->lcdNumber->display(value - 1);
        }
    });
}

这个也是不行的,原因如下:

  • Qt 的界面只能由主线程去负责维护和更新的,并且 Qt 为了保证修改页面过程中不受影响,禁止了其他线程直接修改页面
  • 因为这种情况如果不加锁,不维护好线程安全,就可能有多个线程同时在修改页面,很容易导致页面显示混乱,这是无法容忍的
  • 所以Qt 为了主界面线程安全,直接要求所有对界面的修改操作,必须在主线程中完成
  • 槽函数就是由主线程调用的,所以可以修改

简单来说,界面就相当于共享资源,每个线程访问签都必须先加锁,但是 Qt 规定只能由主线程一个线程去修改 

解决办法也有,就是创建线程后,线程只负责每秒发 timeout 信号给槽函数再去修改,但这属于多此一举, 所以暂时不考虑 

三,ProgressBar

3.1 主要属性

QProgressBar 控件表示一个进度条,就是我们安装程序时界面显示的那个条,主要属性如下:

属性 说明
minimum 进度条最小值
maximum 进度条最大值
value 京都条当前值
alignment

文本在进度条中的对齐方式

  • Qt::AlignLeft:左对齐
  • Qt::AlignRight:右对齐
  • Qt::AlignCenter:居中对齐
  • Qt::AlignJustify:两端对齐
textVisible 进度条数字是否可见
orientation 进度条的方向是水平还是垂直
invertAppearance 是否朝反方向增长进度
textDirection 文本的朝向
format

展示的数字格式

  • %p:表示进度的百分比(0 - 100)
  • %v:表示进度的数值(0-100)
  • %m:表示剩余时间(单位ms)
  • %t:表示总时间(单位ms)

3.2 进度条按时间增长

我们之前是写过一个命令行版本的进度条的,可以参考:Linux实现简单进度条-CSDN博客

先创建一个进度条,如下图:

我们的期望是每隔100毫秒进度条就增加1,所以我们仍然可以通过定时器来实现周期行为

代码如下:

#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);
    connect(timer, &QTimer::timeout, this, &Widget::handle);
    timer->start(20); //启动计时器
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handle()
{
    int value = ui->progressBar->value(); //获取进度条当前数值
    if(value >= 100)
    {
        timer->stop();
        return;
    }
    ui->progressBar->setValue(value + 1);
}

步骤和前面的倒计时很像,都是计时器发信号然后触发槽函数然后修改,效果如下:

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

3.3 改变样式

QProGressBar 也是继承自 QWidget 的,所以也可以使用 styleSheet等方式来改变进度条的颜色等外表

假如我们要把上面的进度条颜色改为“红色”,如下:

 可以添加下列样式:

QProgressBar::chunk { background-color: red; }

上面两个冒号是“选择器”,对于什么是选择器,可以参考:前端学习(2)—— CSS详解与使用-CSDN博客 

 效果如下:

但是我们发现,我们的 100% 的数字跑到了左上角,这个忙猜是 Qt 的bug,毕竟我们没有改变其他的任何地方,所以我们只能先使用 alignment 来手动调整下,如下图:

效果如下:

3.4 一个问题

问题:我们上面的 Widget.h 头文件,添加QTimer* timer; 声明时,明明没有包含 对应头文件,为什么不会提示“QTimer 找不到定义”之类的呢?

如下图:

原因如下:

  • 在 Qt 中,有一个特殊的头文件,这个头文件里包含了 Qt 中所有类的“前置声明”,这个头文件一般不会直接接触到,但是包含其他的 Qt 的头文件,都会间接包含到这个文件
  • 比如上面的 Widget 类的前面已经提供了 QTimer 类的声明的话,此时就可以直接在 Widget 中使用 QTimer 的指针/引用类型的成员(这个是 C++ 中的特殊技巧,Qt 就把它充分发挥了)

追加问题:Qt 为什么要使用上面的技巧呢?

解答

  • C/C++ 的代码,编译速度在其他语言的横向对比中,其实是非常慢的,这个 #include 头文件有很大关系
  • 因为C/C++代码在编译期间,是直接把头文件展开,然后替换掉原来包含头文件的位置,相当于复制拷贝,而一个头文件会间接包含其他头文件,然后其他头文件也会包含其他头文件,这样一下来,就会造成很多不必要的头文件展开
  • 因此,尽可能减少 include 头文件的个数,就可以有效减少编译事件,Qt 就使用 class 前置声明的方式,尽量减少头文件的包含
  • 但是实际开发中,该包含就该包含,也可以引入更好的硬件资源来更高效的编译,一些互联网大肠,都有专门的“编译集群”(分布式编译),专门用来编译

四,Calendar Widget

4.1 主要属性

QCalendarWidget 表示一个“日历”,主要属性如下:

属性 说明
selectDate 当前选中的日期
minimumDate 最小日期
maximumDate 最大日期
firstDayOfWeek 每周的第一天是周几(日历的第一列)
gridVisible 是否显示表格的边框
selectionMode 是否允许选择日期
navigationBarVisible 日历上方标题是否显示
horizontalHeaderFormat 日历上方标题显示的日期格式
verticalHeaderFormat 日历第一列显示的内容格式
dateEditEnabled 是否允许日期被编辑

也提供了一些信号,如下:

信号 说明
selectionChanged(const QDate&) 当选中的日期发生改变时发出
activated(const QDate&) 当双击一个有效的日期或按下回车键时发出,形参是一个QDate类型,保存了选中的日期
currentPageChanged(int, int) 当年份月份改变时发出,形参表示改变后的新年份个月份

4.2 获取选中的日期

先创建一个 label 和一个日历,然后右键转到槽,选择 selectionChange():

编写如下槽函数:

void Widget::on_calendarWidget_selectionChanged()
{
    QDate date = ui->calendarWidget->selectedDate();
    QString ret = "选择的日期是:";
    ret += date.toString();
    ui->label->setText(ret);
}

效果如下:


网站公告

今日签到

点亮在社区的每一天
去签到