Qt常用控件之显示类控件
目录
QLabel
文本格式
设置图片
文本对齐/自动换行/边距/缩进
设置伙伴
QLCDNumber
倒计时功能
QProgressBar
进度条
QCalendarWidget
QLabel
QLabel 同样是 QWidget 的子类,所以前面博客中 QWidget 中的属性方法也是适用的
QLabel可以用来显示文本和图片
核心属性如下:
属性 | 说明 |
---|---|
text | QLabel中的文本 |
textFormat | 文本的格式 Qt::PlainText 纯文本 (使用最多) Qt::RichText 富文本(支持 html 标签) Ot::MarkdownText markdown 格式 Qt::AutoText 根据文本内容自动决定文本格式 |
pixmap | QLabel 内部包含的图片 |
scaledContents | 设为 true 表示内容自动拉伸填充 QLabel 设为 false 则不会自动拉伸 |
alignment | 对齐方式,可以设置水平和垂直方向如何对齐 |
wordWrap | 设为 true 内部的文本会自动换行 设为 false 则内部文本不会自动换行 (QLabel不提供滚动条,QTestEdit会提供) |
indent | 设置文本缩进,水平和垂直方向都生效 |
margin | 内部文本和边框之间的边距 不同于于 indent,但是是上下左右四个方向都同时有效 而 indent 最多只是两个方向有效(具体哪两个方向有效取决于 alignment) |
openExternalLinks | 是否允许打开一个外部的链接 (当 OLabel文本内容包含 url 的时候涉及到) |
buddy | 给 OLabel关联一个 "伙伴",这样点击 OLabel时就能激活对应的伙伴 例如伙伴如果是一个 QCheckBox,那么该 QCheckBox 就会被选中 |
文本格式
下面演示 QLabel 的 textFormat 属性,先创建三个 Label 标签:
再将这三个文本分别设置为纯文本、富文本、markdown文本:
可以看到,给这三个不同 textFormat 属性的文本的 Test,分别加入 html 标签、markdown格式,运行程序,结果如下:
所以得出结论,纯文本不会识别 html 标签或是 markdown格式,只会将他们当做文本打印出来
设置图片
想要设置图片,当然要使用 qrc机制,这里就不重复步骤了:
接着图形化界面的方式,拖动一个 Label,并编写构造函数:
此时运行程序:
我们发现图片并没有按照我们代码所编写的那样,让整个 Label 都是这个图片,是因为我们在 qrc 中传入的图片,可能原本的尺寸就是比较小的,像上述所插入的 dog.png 原本尺寸就是 200 * 200 的,而 Widget 窗口是 800* 600 的,所以不能够显示整个屏幕
而我们想要让整个 Widget 窗口都显示图片,就需要将 scaledContents 属性设置为 true,自动拉伸图片:
此时运行程序,dog.png 就能够填满整个屏幕了:
在上面代码中是在构造函数里,进行的这样的尺寸设置这个设置相当于是一次性的
一旦程序运行起来之后,QLabel 的尺寸就固定下来了,如果窗口发生改变,QLabel 是不会变化的
如果想要 QLabel 随着窗口大小实时发送改变,就需要了解 事件
在前面的学习中,我们了解了用户的操作,会对应一些信号
在 Qt 中,表示用户的操作,有两类概念,一个是信号,另一个是事件
当用户拖拽修改窗口大小的时候,就会触发 resize 事件(resizeEvent)
像 resize 这样的事件,是连续变化的,把窗口尺寸从 A 拖到 B 这个过程中,会触发出一系列的 resizeEvent
此时就可以借助 resizeEvent 来完成上述的功能
可以让 Widget 窗囗类,重写父类(QWidget)的 resizeEvent 虚函数,在鼠标拖动窗口尺寸的过程中,这个函数就会被反复调用执行,每次触发一个 resizeEvent 事件,都会调用一次对应的虚函数
由于此处进行了函数重写,调用父类的虚函数就会实际调用到子类的对应的函数(多态)
下面先在 widget.h 中,声明需要重写的 resizeEvent 函数:
这里的形参 event 是非常有用的,这里就包含了触发这个 resize 事件这一时刻,窗口的尺寸的数值
再在 widget.cpp 中编写 resizeEvent 函数,观察拖动窗口时发送的情况:
运行程序:
此时拖动窗口,发现 Qt 会自动调用 resizeEvent 函数,所以我们这里重写虚函数,是指定了一个回调函数的逻辑:
在实际编程中,指定回调函数其实有很多种写法:
- 设置函数指针
- 设置仿函数(函数对象)
- 设置 lambda
- 通过重写父类虚函数(框架中拿着父类的指针调用这个函数,如果你创建了子类重写了这个函数此时在多态机制下,实际执行的就是子类的函数了)
- Qt 的信号槽
上面只是单纯的打印出尺寸,下面完善代码,使得能够通过实时窗口大小的结果,来实时修改 QLabel 的尺寸:
运行程序,随意拖动窗口的大小,图片也会实时改变,满足了我们的需求:
文本对齐/自动换行/边距/缩进
文本对齐
我们创建一个 Label,并在右侧找到 QFrame,设置 Label 的边框,这样能够更加清楚的观察文本对齐的方式
我们将 第一个属性 frameShape 改为 box,此时就有边框了:
下面给第一个 Label 设置属性,将其设置为 水平居中(AlignHCenter)和垂直居中(AlignVCenter)
之所以中间使用 按位或 连接,因为这里的 AlignHCenter 和 AlignVCenter 都是枚举类型:
类似于位图一样的效果,想要某个效果,就将某个比特位置为1即可
此时运行程序:
如果水平和垂直方向不居中了,想让它靠右上角对齐,将 AlignHCenter 改为 AlignRight,AlignVCenter 改为 AlignTop 即可:
自动换行
给第二个 Label 设置一段很长的文本:
运行程序观察:
发现这段文本无法完全显示出来,所以给它添加上自动换行的属性:
缩进
缩进使用的属性是 indent:
此处设置的缩进即使文本换行了,后续的行也会产生缩进不仅仅是首行缩进:
边距
边距是会影响上下左右四个方向的,用到了 margin 属性:
可以看到设置完边距50,这段长文本有一部分被覆盖掉了,显示不太清晰,超出部分也不再显示了,这就是设置边距的效果
设置伙伴
可以将 QLabel 和 单选框/复选框 绑定一个伙伴关系,此时就能够通过 QLabel 触发 单选框/复选框 的选择操作
下面先创建两个 RadioButton,分别在这两个 RadioButton 后面跟上一个 Label:
此时在构造函数中,将 Label 与对应的 RadioButton 建立伙伴关系:
运行程序,不需要鼠标点击,使用 alt + a / alt + b 就能选中对应的 单选按钮/复选按钮:
Qt 中,QLabel 中写的文本,是可以指定快捷键的,此处快捷键的规则功能上要比 QPushButton 弱很多
是在文本中使用 & 跟上一个字符来表示快捷键
比如 &A => 通过键盘上的 alt +a来触发这个快捷键
&B =>通过键盘上的 alt +b 来触发
绑定了伙伴关系之后,通过快捷键就可以选中对应的 单选按钮/复选按钮
QLCDNumber
QLCDNumer 是一个专门用来显示数字的控件,类似于"老式计算器"的效果
核心属性:
属性 | 说明 |
---|---|
intValue | QLCDNumber 显示的数字值(int) |
value | QLCDNumber 显示的数字值(double) 和 intValue 是联动的 例如给 value 设为 1.5,intValue 的值就是 2 另外,设置 value和 intValue 的方法名字为 display,而不是 setValue 或者 setIntValue |
digitCount | 显示几位数字 |
mode | 数字显示形式 QLCDNumber::Dec:十进制模式,显示常规的十进制数字 QLCDNumber::Hex:十六进制模式,以十六进制格式显示数字 QLCDNumber::Bin:二进制模式,以二进制格式显示数字 QLCDNumber::0ct:八进制模式,以八进制格式显示数字 只有十进制的时候才能显示小数点后的内容 |
segmentStyle | 设置显示风格 QLCDNumber::Flat:平面的显示风格,数字呈现在一个平坦的表面上QLCDNumber::0utline:轮廓显示风格,数字具有清晰的轮和阴影效果 5QLCDNumber::Filled:填充显示风格,数字被填充颜色并与背景区分开 |
smallDecimalPoint | 设置比较小的小数点 |
倒计时功能
使用 QLCDNumber 显示一个初始的数值,比如 10
每隔一秒钟,数字就 -1,一直到 0,就停止了
先使用 图形化界面 的方式拖动一个 LCDNumber:
此处关键要点是要实现"每秒钟 -1"这个效果
周期性的执行某个逻辑,也就是"定时器”
C++标准库中,没有提供定时器的实现,Boost 里面提供了对应的功能
Qt 中也封装了对应的定时器(结合了信号槽机制的)
Qt 中是使用 QTimer类,通过这个类创建出来的对象,就会产生一个 timeout 这样的信号
可以通过 start 方法来开启定时器,并且参数中设定触发 timeout 信号的周期
再结合 connect,把这个 timeout 信号绑定到需要的槽函数中,就可以执行逻辑,修改 LCDNumber 中的数字了
先在 widget.h 中添加成员 timer 和 槽函数 handle:
再在 widget.cpp 中编写代码:
此时运行程序,完成了倒计时功能:
上面实现的 倒计时功能 是利用的 QTimer类, 那么如果不使用这个类,直接写一个循环,每个一秒减一,能够实现倒计时的功能吗,看下面的示例
我们直接在 ui界面 中,在右侧修改初始值:
之前学习 C++ 时使用 sleep函数 是 Windows 的 API,需要在 VS 中包含 Windows.h 的头文件才能使用,而 Qt 是无法直接使用 Windows.h 这个头文件的
所以下面采用 thread库 中的 sleep_for 来完成休眠一秒的操作
具体代码如下:
此时运行程序,发现10秒内并没有显示窗口,而是10秒后,才会显示下面的界面:
这是因为我们是在构造函数中编写的上述代码,而 main.cc 在执行的时候,构造函数执行完,才会执行 show函数,所以运行程序后,无法看到窗口,直到在构造函数中10秒执行完,才显示0秒的界面
下面我们就可以思考一下,另一个情况能够实现吗
假设在构造函数中,另外创建一个线程,在新的线程中,执行上述循环+更新操作,此时并不影响主线程的构造函数的执行
所以将代码改为:
此时运行程序,发现控制台上日志显示,程序终止,出现异常:
而之所以出现异常,原因是:
Qt 里,界面有一个专门的线程去负责维护更新的(主线程,也就是 main 函数所在的线程)
对于 GUI 来说,内部包含了很多的隐藏状态,Qt 为了保证修改界面的过程中,线程安全是不会受到影响的,所以 Qt 禁止了其他线程直接修改界面
而我们上面创建一个线程,在最后一行的 ui->lcdNumber->display(value); 就是在修改界面,所以会出现异常
因此 Qt 为了确保线程安全,直接要求所有的对界面的修改操作,必须在主线程中完成
对于 Qt 的槽函数来说,默认情况下,槽函数都是由主线程调用的,所以在槽函数中修改界面是没有任何问题的
QProgressBar
使用 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:表示剩余时间 (以毫秒为单位) %t:表示总时间 (以毫秒为单位) |
进度条
创建一个进度条,让这个进度条的进度跟随时间增长
(可以假设每隔 100ms,让进度条数值+1)
先拖拽一个 进度条:
通过右边的属性栏,将最小值/最大值设置为0/100,将当前值设置为0
下面就需要上面设计倒计时功能时,所用到的 QTimer类
同样在 widget.h 中新增成员 timer,新增槽函数 handle:
之所以需要定义一个成员 timer,而不是在构造函数中定义局部变量,是因为下面的槽函数 handle 还需要使用 timer
widget.cpp:
运行程序,进度条以 100ms 的速度每次 +1
这里有一个细节问题,我们所使用的 QTimer类,是需要包含头文件的,我们之前在写 C++代码 时一般是包含在 .h文件 中的,而在 Qt 中,既可以在 .h 中包含,也可以在 .cpp 中包含
之所以可以不在 .h 中包含,却能够在 .h 中声明 QTimer类,是因为:
在 Qt 中,有一个专门的头文件,这个头文件中包含了 Qt 中所有类的"前置声明",这个头文件一般不会直接接触到,但是包含其他的 Qt 的头文件,例如 #include<QWidget> 都会间接的包含到这个头文件
Widget 类的前面已经提供了 QTimer 类的声名的话,此时就可以在 Widget.h 中声明 QTimer 的指针/引用类型的成员
后续如果要真正使用 QTimer(包括创建实例,使用里面的成员),仍然需要包含 QTimer 的头文件(包含了 QTimer 的详细的类的定义)
Qt 使用上述的技巧,主要解决的是编译速度的问题
C++编译速度慢,和 #include 头文件有直接关系的
由于 include 关系错综复杂,因此尽可能减少 include 头文件的个数,就可以有效的减少编译时间
Qt 中就使用 class 前置声明的方式,来尽量减少头文件的包含,通过前置声明的方式,Qt 中的每个头文件包含的其他头文件数量都能得到一定的降低
但是咱们实际开发中,还是要该包含就包含,与其通过特殊技巧来缩短编译时间,不如说引入更好的硬件资源,来更高效的编译
例如:一些互联网大厂,都有专门的"编译集群”(分布式编译)
正是因为 #include 编译速度慢的原因,所以在 C++ 20 标准开始,就引入了"模块" module来替代 #include
因为 QProgressBar 也是 QWidget 的子类,所以 QWidget 的 styleSheet 属性也就可以使用,所以我们可以将 进度条 的颜色改为红色:
先拖动一个进度条:
右键,点击改变样式表:
输入以下内容:
此时进度条颜色就变为红色了,但是我们发现 24% 原来是在右边,现在却在左上角了:
这种情况有可能是 Qt 的 bug,并不知道原因是什么,但是也没关系,我们也可以设置样式:
此时就变为 垂直/水平方向 都 居中对齐了:
进度条具体的进度如何设置,一般都是根据实际的任务类型来灵活设置的
例如:要读取一个很大的文件,就可以先获取到文件的总大小,每读取一部分数据(可以计算出读了多少数据的),更新一次进度条的数值
设置进度条的过程中,往往是要搭配 定时器 的
QCalendarWidget
QCalendarwidget 表示一个"日历",形如
核心属性:
属性 | 说明 |
---|---|
selectDate | 当前选中的日期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第一天(也就是日历的第一列) 是周几 |
gridVisible | 是否显示表格的边框 |
selectionMode | 是否允许选择日期 |
navigationBarVisible | 日历上方标题是否显示 |
horizontalHeaderFormat | 日历上方标题显示的日期格式 |
verticalHeaderFormat | 日历第一列显示的内容格式 |
dateEditEnabled | 是否允许日期被编辑 |
重要信号
信号 | 说明 |
---|---|
selectionChanged(const QDate&) | 当选中的日期发生改变时发出 |
activated(const QDate&) | 当双击⼀个有效的日期或者按下回车键时发出,形参是⼀个QDate类型,保存了选中的日期 |
currentPageChanged(int, int) | 当年份月份改变时发出,形参表示改变后的新年份和月份 |
首先使用 图形化界面 的方式,拖动一个 QCalendarWidget 和一个 Label,用于显示我们选中的日期:
接着右键日历,选择 转到槽 的 selectionChanged:
下面编写 槽函数 :
运行程序,可以看到控制台会打印我们所点击的日期,上面的 Label 中也会显示所点击的日期:
Qt常用控件之显示类控件到此结束啦