回顾常用控件文章
Qt常用控件-> 1、概述、QWidget核心属性-CSDN博客
一、按钮类控件
1、Push Button
使用QPushButton表示一个按钮,这也是当前我们最熟悉的一个控件了。QPushButton继承自QABstractButton,这个类是一个抽象类,是其他按钮的父类
在Qt Designer中也能够看到这里的继承关系:
QAbstractButton中,和QPushButton相关性较大的属性:
1、QAbstractButton作为QWidget的子类,当然也继承了QWidget的属性。上面介绍的QWIdget里的各种属性用法,对于QAbstractButton同样适用,因表格仅列出QAbstractButton独有属性
2、Qt的api设计风格是非常清晰的,此处列出的属性都是可以获取和设置的。例如,使用text()获取按钮文本,使用setText()设置文本
事实上,QPushButton的核心功能都是QAbstractButton提供的,自身提供的属性都比较简单。其中,default和audoDefault影响的是按下enter时,点击那个按钮的行为,flat把按钮设置为扁平的样式
带有图标的按钮
(1)创建 resource.qrc 文件并导入图片
具体操作步骤参见 上一篇的QWidget 中的windowIcon部分
(2)在界面上创建一个按钮
(3)修改 widget.cpp,给按钮设置图标
(4)执行程序
观察结果:
2、Radio Button
带有快捷键的按钮
(1)在界面中拖五个按钮
五个按钮的 objectName 分别为 pushButton_target、pushButton_up、pushButton_down、pushButton_left、pushButton_right。
(2)创建 resource.qrc,并导入 5 个图片
(3)修改 widget.cpp,设置图标资源和快捷键
使用 setShortcut 给按钮设置快捷键,参数是⼀个 QKeySequence 对象,表示一个按键序列,支持持组合键(Ctrl + C 这种)。
QKeySequence 的构造函数参数,可以直接使用 "Ctrl + C" 这样的按键名字符串表示,也可以使用预定义好的常量(形如 Qt::CTRL + Qt::Key_C)表示。
(4)修改 widget.cpp,设置四个方向键的 slot 函数
(5)运行程序
此时点击按钮,或者使用 wasd 均可让 “柯南” 移动:
按钮的重复触发
在上述案例中按住快捷键,是可以进行重复触发的,但是鼠标点击则不能。
(1)修改 widget.cpp,在构造函数中开启重复触发
此时按住鼠标时,即可让 “柯南” 连续移动。
3、Check Box
QCheckBox 表示复选按钮,可以允许选中多个。和 QCheckBox 最相关的属性也是 checkable 和 checked,都是继承自 QAbstractButton。
至于 QCheckBox 独有的属性 tristate 用来实现 “三态复选框”,这个东西比较冷门,这里暂时不讲述。
获取复选按钮的取值
(1)在界面上创建三个复选按钮和一个普通按钮
objectName 分别为 checkBox_study、checkBox_game、checkBox_work 以及 pushButton
(2)给 pushButton 添加 slot 函数
(3)运行程序
可以看到点击确认按钮时,就会在控制台中输出选中的内容:
4、Tool Button
QToolButton 的大部分功能和 QPushButton 是一致的,但 QToolButton 主要应用在工具栏、菜单等场景。
二、显示类控件
1、Label
QLabel 可以用来显示文本和图片。
核心属性如下:
显示不同格式的文本
(1)在界面上创建三个 QLabel
尺存放大一些,objectName 分别为 label、label_2、label_3
(2)修改 widget.cpp,设置三个 label 的属性
(3)运行程序
显示图片
虽然 QPushButton 也可以通过设置图标的方式设置图片,但是并非是一个好的选择,更多的时候还是希望通过 QLabel 来作为一个更单纯的显示图片的方式。
(1)在界面上创建一个 QLabel、objectName 为 label
(2)创建 resource.qrc 文件,并把图片导入到 qrc 中
(3)修改 widget.cpp,给 QLabel 设置图片
这个图片本身的尺寸是 1280 * 150,超出了窗口的大小(800 * 800)。
执行程序,观察结果:
(4)修改代码,设置 scaledContents 属性
再次运行程,观察效果,可以看到图片已经被拉伸,可以把窗口填满了:
此时,如果拖动窗口大小,可以看到图片并不会随着窗口大小的改变而同步变化。
为了解决这个问题,可以在 Widget 中重写resizeEvent函数:
执行程序,此时改变窗口大小,图片也会随之变化:
注意:这里的 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:带有可点击区域的面板边框,但样式取决于窗口主题。
(2)编写 widget.cpp,给这四个 label 设置属性
(3)运行程序
设置伙伴
(1)创建两个 label 和 两个 radioButton
objectName 分别为 label、label_2、radioButton、radioButton_2
此处把 label 中的文本设置为 “快捷键 &A” 这样的形式。其中 & 后面跟着的字符就是快捷键,可以通过 alt + A 的方式来触发该快捷键。但是注意,这里的快捷键和 QPushButton 的不同需要搭配 alt 和 单个字母的方式才能触发。
绑定了伙伴关系之后,通过快捷键就可以选中对应的单选按钮 / 复选按钮。
(2)编写 widget.cpp,设置 buddy 属性
这里也可以使用Qt Designer直接设置:
(3)运行程序
可以看到,按下快捷键 alt + a 或者 alt + b,即可选中对应的选项:
2、LCD Number
QLCDNumer是一个专门用来显示数字的控件,类似于 “老式计算器” 的效果。
核心属性:
倒计时
(1)在界面上创建⼀个 QLCDNumber,初始值设为 10
objectName 为 lcdNumber
(2)修改 widget.h 代码,创建一个 QTimer 成员和一个 handle 函数
(3)修改 widget.cpp,在构造函数中初始化 QTimer
QTimer 表示定时器,通过 start 方法启动定时器之后,就会每隔一定周期触发一次 QTimer::timeout 信号
使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来,意味着每次触发 QTimer::timeout 都会执行 Widget::updateTime
(4)修改 widget.cpp,实现 handle
通过 intValue 获取到 QLCDNumber 内部的数值。
如果 value 的值归 0 了,就停止 QTimer,接下来 QTimer 也就不会触发 timeout 信号了。
(5)执行程序
可以看到每隔一秒钟,显示的数字就减少 1:
(6)针对上述代码,存在两个问题
A. 上述代码如果直接在 Widget 构造函数中,通过一个循环 + sleep 的方式是否可以呢?
显然,上面这段代码是不行的,循环会使 Widget 的构造函数无法执行完毕,此时界面是不能正确构造和显示的。
B. 上述代码如果是在 Widget 构造函数中,另起一个线程,在新线程中完成循环 + sleep 是否可以呢?
这个代码同样是不行的。Qt 中规定:任何对于 GUI 上内容的操作必须在主线程中完成。像 Widget 构造函数,以及 connect 连接的 slot 函数,都是在主线程中调用的。而我们自己创建的线程则不是,当我们自己的线程中尝试对界面元素进行修改时,Qt 程序往往会直接崩溃。
这样的约定主要是因为 GUI 中的状态往往是牵一发动全身的,修改一个地方,就需要同步的对其他内容进行调整。比如调整了某个元素的尺存,就可能影响到内部的文字位置,或者其他元素的位置。这里一连串的修改都是需要按照一定的顺序来完成的。
由于多线程执行的顺序无法保障,因此 Qt 从根本上禁止了其他线程修改 GUI 状态,避免后续的一系列问题。
对于 Qt 的槽函数来说,默认情况下,槽函数都是由主线程调用到,在槽函数中修改界面是没有任何问题的。
综上所述,使用定时器是实现上述功能的最合理方案。后续如果也有类似的需要 “周期性修改界面状态” 的需求也需要优先考虑使用定时器。
3、ProgressBar
使用QProgressBar表示一个进度条。
核心属性:
设置进度条按时间增长
(1)在界面上创建进度条,objectName 为 progressBar
其中最小值设为 0,最大值设为 100,当前值设为 0:
(2)修改 widget.h,创建 QTimer 和 handle 函数
虽然在 widget.h 中用到了 QTimer,但是却没在 widget.h 文件中包含 <QTimer> 头文件,为什么这个代码编译没有出错呢?
上述问题其实是通过 Qt 内部提供的一个特殊技巧来实现的。在 Qt 中有一个专门的头文件(#include <QWidget>),这个头文件中包含了 Qt 中所有类的 “前置声明”(class QWidget,class QPushButton,class QTimer)。这个头文件我们一般不会直接接触到,但是包含其它的 Qt 的头文件,都会间接的包含到这个头文件。
如果 Widget 类的前面以及提供了 QTimer 类的声明的话,此时就可以在 Widget 中声明 QTimer 的指针 / 引用类型的成员。后续如果要真正使用 QTimer 的头文件(包括创建实例,使用里面的成员),仍然要包含 QTimer 的头文件(包含了 QTimer 的详细的类的定义)。
Qt 为什么要使用上述技巧呢?上述技巧能解决什么问题?有什么提升呢?
主要解决的是编译速度的问题。C/C++ 代码,编译速度在其他语言横向对比中是非常慢的。C++ 编译速度慢和 #include 头文件有直接关系。由于 include 关系错综复杂,所以尽可能减少 include 头文件的个数就可以有效地减少编译时间。
Qt 中就使用 class 前置声明的方式来尽量减少头文件的包含。通过前置声明的方式,Qt 中每个头文件包含的其它头文件数量都能得到一定的降低。
(3)修改 widget.cpp,初始化 QTimer
此处设置 100ms 触发一次 timeout 信号,也就是⼀秒钟触发 10 次
(4)修改 widget.cpp,实现 handle
(5) 运行程序
可以看到进度条中的进度在快速增长:
在实际开发中,进度条的取值往往是根据当前任务的实际进度来进行设置的。比如需要读取一个很大的文件,就可以获取文件的总的大小和当前读取完毕的大小,来设置进度条的比例。
由于前面我们介绍了 Qt 禁止在其他线程修改界面,因此进度条的更新往往也是需要搭配定时器来完成的。
通过定时器周期触发信号,主线程调用对应的 slot 函数,再在 slot 函数中对当前的任务进度进行计算,并更新进度条的界面效果。
创建一个红色的进度条
上述的进度条是用绿⾊表示的,但是考虑到有些人可能不喜欢绿⾊,因此我们改成一个红色的进度条。
QProgressBar 同样也是 QWidget 的子类,因此我们可以使用 styleSheet 通过样式来修改进度条的颜色。
(1)在界面上创建一个进度条
(2)在 Qt Designer 右侧的属性编辑器中找到 QWidget 的 styleSheet 属性
编辑内容:其中的 chunk 是选中进度条中的每个 “块”,使用 QProgressBar::text 则可以选中文本。
同时把 QProcessBar 的 alignment 属性设置为垂直水平居中:
此处如果不设置 alignment,进度条中的数字会跑到左上角。
(3)执行程序
可以看到如下效果,就得到了一个红色的进度条:
通过上述方式,也可以修改文字的颜色,字体大小等样式。
4、Calendar Widget
QCalendarWidget表示⼀个 “日历”,形如:
核心属性:
重要信号:
获取选中的日期
(1)在界面上创建一个 QCalendarWidget 和一个 label
objectName 为 calendarWidget,label
(2)给 QCalendarWidget 添加 slot 函数
(3)执行程序
可以看到当选择不同的日期时,label 中的内容就会随之改变: