目录
一,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);
}
效果如下: