Qt-常用控件(2)-按钮类和显示类
1. QPushButton
使用 QPushButton 表示一个按钮.这也是当前我们最熟悉的一个控件了
QPushButton 继承自 QAbstractButton.这个类是一个抽象类.是其他按钮的父类
在 Qt Designer中也能够看到这⾥的继承关系
QAbstractButton 中,和 QPushButton 相关性较⼤的属性
属性 | 说明 |
---|---|
text | 按钮中的文本 |
icon | 按钮中的图标 |
iconSize | 按钮中图标的尺寸 |
shortCut | 按钮对应的快捷键 |
autoRepeat | 按钮是否会重复触发.当鼠标左键按住不放时,如果设为 true,则会持续产生鼠标点击事件; 如果设为 false,则必须释放鼠标,再次按下鼠标时才能产生点击事件(相当于游戏手柄上的"连发"效果) |
autoRepeatDelay | 重复触发的延时时间.按住按钮多久之后,开始重复触发 |
autoRepeatInterval | 重复触发的周期. |
- QAbstractButton 作为 Qwidget 的子类,当然也继承了 QWidget 的属性,上面介绍的 QWidget 里的各种属性用法,对于 QAbstractButton 同样适用.因此表格仅列出 QAbstractButton 独有的属性.
- Qt 的 api 设计风格是非常清晰的.此处列出的属性都是可以 获取 和 设置 的.例如,使用 text()获取按钮文本;使用 setText()设置文本.
事实上,QPushButton 的核心功能都是 QAbstractButton 提供的.自身提供的属性都比较简单.
其中 default 和 audoDefault 影响的是按下enter 时自动点击哪个按钮的行为;flat 把按钮设置为扁平的样式,这里我们暂时都不做过多关注.
代码示例:带有快捷键的按钮
- 在界面中拖五个按钮,
五个按钮的 objectName分别为 pushButton_target,pushButton_up,
pushButton_down,pushButton_left,pushButton_right五个按钮的初始位置随意,其中 pushButton_target 尺寸设置为 100100,其余按钮设为 5050.文本内容均清空
- 创建 resource.qrc , 并导⼊ 5 个图⽚
- 修改 widget.cpp, 设置图标资源和快捷键
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 添加快捷键
// 用图标按钮实现上下左右移动target
// 设置图标对象
ui->pushButton_target->setIcon(QIcon(":/1.png"));
ui->pushButton_target->setIconSize(QSize(200,200));
ui->pushButton_up->setIcon(QIcon(":/image/up.png"));
ui->pushButton_up->setIconSize(QSize(50,50));
ui->pushButton_left->setIcon(QIcon(":/image/left.png"));
ui->pushButton_left->setIconSize(QSize(50,50));
ui->pushButton_right->setIcon(QIcon(":/image/right.png"));
ui->pushButton_right->setIconSize(QSize(50,50));
ui->pushButton_down->setIcon(QIcon(":/image/down.png"));
ui->pushButton_down->setIconSize(QSize(50,50));
// 给按钮添加快捷键
ui->pushButton_up->setShortcut(QKeySequence("w"));
ui->pushButton_left->setShortcut(QKeySequence("a"));
ui->pushButton_right->setShortcut(QKeySequence("d"));
ui->pushButton_down->setShortcut(QKeySequence("s"));
// 用枚举添加快捷键,防止输入错误
ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_W));
ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_A));
ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_D));
ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_S));
// 给鼠标按下按钮添加连机,(键盘按下长按会默认是连机的)
ui->pushButton_up->setAutoRepeat(true);
ui->pushButton_down->setAutoRepeat(true);
ui->pushButton_left->setAutoRepeat(true);
ui->pushButton_right->setAutoRepeat(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_up_clicked()
{
// 获取当前的位置
QRect rect = ui->pushButton_target->geometry();
qDebug()<<rect;
// 设置移动的位置
ui->pushButton_target->setGeometry(rect.x(),rect.y()-5,rect.width(),rect.height());
}
void Widget::on_pushButton_left_clicked()
{
// 获取当前的位置
QRect rect = ui->pushButton_target->geometry();
qDebug()<<rect;
// 设置移动的位置
ui->pushButton_target->setGeometry(rect.x()-5,rect.y(),rect.width(),rect.height());
}
void Widget::on_pushButton_right_clicked()
{
// 获取当前的位置
QRect rect = ui->pushButton_target->geometry();
qDebug()<<rect;
// 设置移动的位置
ui->pushButton_target->setGeometry(rect.x()+5,rect.y(),rect.width(),rect.height());
}
void Widget::on_pushButton_down_clicked()
{
// 获取当前的位置
QRect rect = ui->pushButton_target->geometry();
qDebug()<<rect;
// 设置移动的位置
ui->pushButton_target->setGeometry(rect.x(),rect.y()+5,rect.width(),rect.height());
}
- 运⾏程序, 此时点击按钮即可实现
2. QRadioButton
QRadioButton是单选按钮,可以让我们在多个选项中选择一个
作为 QAbstractButton 和 Qwidget 的子类,上面介绍的属性和用法,对于 QRadioButton同样适用.
QAbstractButton 中和 QRadioButton 关系较大的属性
属性 | 说明 |
---|---|
checkable | 是否能选中 |
checked | 是否已经被选中.checkable是checked 的前提条件: |
autoExclusive | 是否排他. 选中一个按钮之后是否会取消其他按钮的选中 对于 ORadioButton 来说默认就是排他的 默认是只能选择他一个 |
代码⽰例: 选择性别
- 在界⾯上创建⼀个label, 和 3 个 单选按钮
设置的⽂本如下图. 3 个单选按钮的 objectName 分别为 radioButton_male ,radioButton_female , radioButton_other
- 修改 widget.cpp,编辑三个 QRadioButton 的slot 函数
- 运行程序,可以看到随着选择不同的单选按钮,label中的提示文字就会随之变化.
但是Checkable可以让按钮不被选中,但是还是可以触发点击事件
所以将代码改为
使⽤ setEnabled 是更彻底的禁⽤按钮的⽅式.此时该按钮⽆法被选中,也⽆法响应任何输⼊
代码示例2 :单选框分组
- 在界面上创建6个单选框,用来模拟麦当劳点餐界面objectName 分别为radioButton到radioButton_6
由于RadioButton的默认方式是排他的,只能默认选择一个,要想每组里面选一个,应要设置分组- 引⼊QButtonGroup 进⾏分组
修改 widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个 QButtonGroup
QButtonGroup* group1 = new QButtonGroup(this);
QButtonGroup* group2 = new QButtonGroup(this);
QButtonGroup* group3 = new QButtonGroup(this);
// 把 QRadioButton 两两⼀组, 放到三个 QButtonGroup 中.
group1->addButton(ui->radioButton);
group1->addButton(ui->radioButton_2);
group2->addButton(ui->radioButton_3);
group2->addButton(ui->radioButton_4);
group3->addButton(ui->radioButton_5);
group3->addButton(ui->radioButton_6);
}
再次执⾏程序, 可以看到可以按照正确的分组⽅式来完成排他了
3. QCheckBox
QCheckBox表示复选按钮.可以允许选中多个.和 QCheckBox最相关的属性也是 checkable和checked,都是继承自0AbstractButton
至于 QCheckBox 独有的属性 tristate 用来实现"三态复选框".这个东西比较冷门,咱们不做讨论.
代码示例:获取复选按钮的取值
- 在界面上创建 三个复选按钮,和一个普通按钮,
objectName 分别为checkBoxeat,checkBox_sleep,checkBox_play,以及pushButton
- 给 pushButton 添加 slot 函数
void Widget::on_pushButton_clicked()
{
QString result;
if (ui->checkBox_eat->isChecked()) {
result += ui->checkBox_eat->text();
}
if (ui->checkBox_sleep->isChecked()) {
result += ui->checkBox_sleep->text();
}
if (ui->checkBox_play->isChecked()) {
result += ui->checkBox_play->text();
}
qDebug() << "选中的内容: " << result;
}
- 运⾏程序, 可以看到点击确认按钮时, 就会在控制台中输出选中的内容
4. QLabel
属性 | 说明 |
---|---|
text | QLabel中的文本 |
textFormat | 文本的格式. Qt::PlainText 纯文本 Qt::RichText 富文本(支持 html标签) Qt::MarkdownText markdown格式 Ot::AutoText 根据文本内容自动决定文本格式 |
pixmap | QLabel 内部包含的图片 |
scaledContents | 设为 true 表示内容自动拉伸填充 QLabel 设为 false 则不会自动拉伸 |
alignment | 对齐方式. 可以设置水平和垂直方向如何对齐 |
wordWrap | 设为 true 内部的文本会自动换行 设为 false 则内部文本不会自动换行 |
indent | 设置文本缩进.水平和垂直方向都生效. |
margin | 内部文本和边框之间的边距 不同于于indent,但是是上下左右四个方向都同时有效. 而 indent 最多只是两个方向有效(具体哪两个方向有效取决于 alignment) |
openExternalLinks | 是否允许打开一个外部的链接,(当 OLabel文本内容包含 url 的时候涉及到) |
buddy | 给 QLabel关联一个"伙伴",这样点击 QLabel时就能激活对应的伙伴 例如伙伴如果是一个 QCheckBox,那么该QCheckBox 就会被选中. |
代码示例:显示不同格式的文本
- 在界面上创建三个 QLabel尺寸放大一些.objectName 分别为label,label 2,label 3
- 修改 widget.cpp, 设置三个 label 的属性
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
// 纯文本
ui->label->setTextFormat(Qt::PlainText);
ui->label->setText("这是一个纯文本");
// 富文本
ui->label_2->setTextFormat(Qt::RichText);
ui->label_2->setText("<b>这是一个富文本</b>");
// markdown格式
ui->label_3->setTextFormat(Qt::MarkdownText);
ui->label_3->setText("# 这是一个markdown 格式");
}
- 运⾏程序, 观察效果
代码示例: 显示图片,填充整个标签
虽然 QPushButton 也可以通过设置图标的方式设置图片,但是并非是一个好的选择,更多的时候还是希望通过 QLabel 来作为一个更单纯的显示图片的方式
- 在界面上创建一个QLabel,objectName 为 label
- 编写Widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QResizeEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 重置填充图片
QRect rect(this->geometry());
// 设置label大小和窗口大小一样
ui->label->setGeometry(0,0,rect.width(),rect.height());
// 创建图片对象
QPixmap pismap(":/1.png");
ui->label->setPixmap(pismap);
// label内容自动拉伸,充满label
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
一旦程序运行起来之后,QLabel 的尺寸就固定下来了窗口发生改变,此时, QLabel 是不会变化的
这个是固定的,而我们如果在运行之后要去拖动窗口大小,而label的大小不会改变,所以图片还是那么大。
注意:
事件:用户的操作,会对应一些信号
Qt 中,表示用户的操作,有两类概念,
一个是信号,另一个是事件
当用户拖拽修改窗口大小的时候,就会触发 resize 事件(resizeEvent)像 resize 这样的事件,是连续变化的.
把窗口尺寸从 A 拖到 B这个过程中,会触发出一系列的 resizeEvent.此时就可以借助 resizeEvent 来完成上述的功能,
可以让 Widget 窗囗类,重写父类(QWidget) 的 resizeEvent 虚函数
在Widget.cpp代码种添加如下代码
// 此处的 event 是非常有用的,这里就包含了触发 resize 事件的这一时刻,窗口的尺寸数值
void Widget::resizeEvent(QResizeEvent *event)
{
// 打印出相应的尺寸大小
qDebug()<<event->size();
ui->label->setGeometry(0,0,event->size().width(),event->size().height());
}
注意:
此处的 resizeEvent 函数我们没有手动调用,但是能在窗口大小变化时被自动调用这个过程就是依赖 C++ 中的多态来实现的.Qt 框架内部管理着 QWidget 对象表示咱们的窗口.在窗口大小发生改变时,Qt就会自动调用 resizeEvent 函数.但是由于实际上这个表示窗口的并非是 QWidget,而是 QWidget 的子类,也就是咱们自己写的 Widget.此时虽然是通过父类调用函数,但是实际上执行的是子类的函数(也就是我们重写后的 resizeEvent)
此处属于是 多态 机制的一种经典用法,通过上述过程,就可以把自定义的代码,插入到框架内部执行.相当于"注册回调函数"
代码⽰例: 设置伙伴
- 创建两个 label 和两个 radioButton.
此处把 label 中的文本设置为"快捷键 &A" 这样的形式其中&后面跟着的字符,就是快捷键.
可以通过 alt +A的方式来触发该快捷键
但是注意,这里的快捷键和 QPushButton 的不同.需要搭配 alt 和 单个字母的方式才能触发- 编写 widget.cpp, 设置 buddy 属性
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 建立伙伴关系
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
Widget::~Widget()
{
delete ui;
}
- 运⾏程序, 可以看到, 按下快捷键 alt + a 或者 alt + b, 即可选中对应的选项
5. 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:轮廓显示风格,数字具有清晰的轮和阴影效果。 QLCDNumber::Filled:填充显示风格,数字被填充颜色并与背景区分开。 |
smallDecimalPoint | 设置比较小的 小数点. |
代码⽰例: 倒计时
- 在界⾯上创建⼀个 QLCDNumber ,初始值设为10. objectName 为 lcdNumber
"定时器”
C++标准库中,没有提供定时器的实现.Boost 里面提供了对应的功能,
Qt 中也封装了对应的定时器(结合了信号槽机制的)
QTimer
通过这个类创建出来的对象,就会产生一个 timeout 这样的信号~~可以通过 start 方法来开启定时器,并且参数中设定触发 timeout 信号的周期
结合 connect,把这个 timeout 信号绑定到需要的槽函数中,就可以执行逻辑,修改 LCDNumber 中的数字了
- 修改 widget.h代码,创建一个 QTimer 成员,和一个 handle 函数
QTimer 表示定时器.通过 start 方法启动定时器之后,就会每隔一定周期,触发一次QTimer::timeout 信号.
使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来,意味着每次触发 QTimer::timeout都会执行 Widget::updateTime
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// QTimer倒计时
// 先设置一个初始值
ui->lcdNumber->display(10);
// 创建一个QTimer实例
timer=new QTimer(this);
// 用connect函数与槽函数进行连接
connect(timer,&QTimer::timeout,this,&Widget::handle);
// 启动定时器
timer->start(1000);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
// 获取当前的数字
int value=ui->lcdNumber->intValue();
if(value<=0)
{
// 停止定时器
timer->stop();
return;
}
ui->lcdNumber->display(value-1);
}
- 执⾏程序, 可以看到每隔⼀秒钟, 显⽰的数字就减少 1
针对上述代码,存在两个问题:
- 上述代码如果直接在 Widget 构造函数中,通过一个循环+sleep 的方式是否可以呢?代码形如
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);
}
}
显然,这个代码是不行的.循环会使 Widget 的构造函数无法执行完毕,此时界面是不能正确构造和显示的.
Widget 的构造函数, 需要把 Widget 构造完毕, 然后才能执行后续的显示操作
- 上述代码如果是在 Widget 构造函数中,另起一个线程,在新线程中完成 循环+sleep 是否可以呢?代码形如
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
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 的槽函数来说,默认情况下, 槽函数都是由主线程调用的.
6. 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 :表示总时间(以毫秒为单位) |
代码示例:设置进度条按时间增长
- 在界面上创建进度条,objectName 为 progressBar
- 修改 widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实现进度条 QTimer
timer=new QTimer(this);// 创建QTimer对象
connect(timer,&QTimer::timeout,this,&Widget::handle);
// 启动定时器
timer->start(100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
// 获取当前进度条的数值
int value = ui->progressBar->value();
if(value>=100)
{
return;
}
ui->progressBar->setValue(value+1);
}
// Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handle();
private:
Ui::Widget *ui;
QTimer* timer;// 定时器
};
#endif // WIDGET_H
根据上述代码可知
但是编译没有出错,这是因为在 Qt 中, 有一个专门的头文性,这个头文件中包含了 Qt 中所有类的"前置声明"
Widget 类的前面已经提供了 QTimer 类的声名的话此时就可以在 Widget 中 声明QTimer 的指针/引用类型的成员.
后续如果要真正使用 QTimer(包括创建实例,使用里面的成员…)仍然需要包含 QTimer 的头文件(包含了 QTimer 的详细的类的定义)
C/C++ 的代码,编译速度在其他语言横向对比中,是非常慢的
对于一个大规模的项目,编译速度可能非常慢!! 俺在华为的时候,我们哪怕只是给代码中添加一个 printf, 编译消耗的时间,就是1个小时左右, C++ 编译速度慢, 和 #include 头文件, 有直接关系的
由于 include 关系错综复杂,因此, 尽可能减少 include 头文件的个数,就可以有效的减少编译时间
Qt 中就使用 class 前置声明的方式, 来尽量减少头文件的包含
通过前置声明的方式, Qt 中的头文件,每个头文件包含的其他头文件数量都能得到一定的降低
- 运⾏程序, 可以看到进度条中的进度在快速增⻓
注意:可以通过StyleSheet来更改进度条的颜色
7. QCalendarWidget
表示一个"日历",形如OCalendarWidget
核心属性
属性 | 说明 |
---|---|
selectDate | 当前选中的日期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第一天(也就是日历的第一列) 是周几. |
gridVisible | 是否显示表格的边框 |
selectionMode | 是否允许选择日期 |
navigationBarVisible | 日历上方标题是否显示 |
horizontalHeaderFormat | 日历上方标题显示的日期格式 |
verticalHeaderFormat | 日历第一列显示的内容格式 |
dateEditEnabled | 是否允许日期被编辑 |
重要信号
信号 | 说明 |
---|---|
selectionChanged(const QDate&) | 当选中的日期发生改变时发出 |
activated(const QDate&) currentPageChanged(int,int) | 当双击一个有效的日期或者按下回车键时发出,形参是一个QDate类型,保存了选中的日期 当年份月份改变时发出,形参表示改变后的新年份和月份 |
代码示例:获取选中的日期
- 在界面上创建一个 Qcalendarwidget 和一个labelobjectName为calendarWidget,label
- 给 Qcalendarwidget添加 slot 函数
void Widget::on_calendarWidget_clicked(const QDate &date)
{
// 获取当前的选中的日期
QDate ate = ui->calendarWidget->selectedDate();
qDebug()<<ate;
ui->label->setText(ate.toString());
}
- 执⾏程序, 可以看到当选择不同的⽇期时, label 中的内容就会随之改变.