QT跨平台应用程序开发框架(6)—— 常用显示类控件
目录
一,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 | 文本的格式:
|
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)
|
digitCount | 显示几位数字 |
mode | 数字显示形式
只有十进制才能显示小数点后的内容 |
segmentStyle | 设置显示风格
|
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 | 文本在进度条中的对齐方式
|
textVisible | 进度条数字是否可见 |
orientation | 进度条的方向是水平还是垂直 |
invertAppearance | 是否朝反方向增长进度 |
textDirection | 文本的朝向 |
format | 展示的数字格式
|
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);
}
效果如下: