一、QPushButton
使用 QPushButton 来表示按钮控件,这是我们目前最熟悉的控件之一。
QPushButton 继承自 QAbstractButton 类,后者是一个抽象类,作为所有按钮控件的基类。

在 Qt Designer 中也能够看到这里的继承关系

QAbstractButton 中, 和 QPushButton 相关性较大的属性
| 属性 | 说明 |
|---|---|
| text | 按钮中的文本 |
| icon | 按钮中的图标 |
| iconSize | 按钮中图标的尺寸 |
| shortCut | 按钮对应的快捷键 |
| autoRepeat | 按钮是否支持重复触发。当鼠标左键保持按下状态时:若设置为true,将持续触发点击事件(类似游戏手柄的"连发"模式)若设置为false,则需释放并再次点击才能触发新事件 |
| autoRepeatDelay | 重复触发的延时时间,按住按钮多久之后, 开始重复触发 |
| autoRepeatInterval | 重复触发的周期 |
代码样例:
先创建一个按钮:

cpp
#include "widget.h"
#include "ui_widget.h"
#include <QIcon>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建图标
QIcon icon(":/dog.png");
//设置图标
ui->pushButton_target->setIcon(icon);
//设置图片大小
ui->pushButton_target->setIconSize(QSize(50, 50));
}
Widget::~Widget()
{
delete ui;
}
运行代码:

我们再来一个升级版的样例(带有快捷键的按钮):
首先我们再创建四个按钮

再向resource.qrc中导入四张图片
注意导入的图片多的时候,可以创建一个新目录将图片统一放到一个文件夹中
此时就意味着后续访问这些图片就需要再路径中带上image这一级目录的名字。
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置图标
ui->pushButton_target->setIcon(QIcon(":/image/dog.png"));
ui->pushButton_target->setIconSize(QSize(80, 80));
ui->pushButton_up->setIcon(QIcon(":/image/up.png"));
ui->pushButton_down->setIcon(QIcon(":/image/down.png"));
ui->pushButton_left->setIcon(QIcon(":/image/left.png"));
ui->pushButton_right->setIcon(QIcon(":/image/right.png"));
//设置快捷键
ui->pushButton_up->setShortcut(QKeySequence("w"));
ui->pushButton_down->setShortcut(QKeySequence("s"));
ui->pushButton_left->setShortcut(QKeySequence("a"));
ui->pushButton_right->setShortcut(QKeySequence("d"));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_up_clicked()
{
const QRect& rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x(), rect.y() - 5, rect.width(),
rect.height());
qDebug() << "up";
}
void Widget::on_pushButton_down_clicked()
{
const QRect& rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x(), rect.y() + 5, rect.width(),
rect.height());
qDebug() << "down";
}
void Widget::on_pushButton_left_clicked()
{
const QRect& rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x() - 5, rect.y(), rect.width(),
rect.height());
qDebug() << "left";
}
void Widget::on_pushButton_right_clicked()
{
const QRect& rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x() + 5, rect.y(), rect.width(),
rect.height());
qDebug() << "right";
}
代码运行:

但是我们发现只有键盘一直按着,才可以实现连发,鼠标长按却不能实现连发,我们可以加这样一个代码实现鼠标的连发。
cpp
//开启重复触发
ui->pushButton_up->setAutoRepeat(true);
ui->pushButton_down->setAutoRepeat(true);
ui->pushButton_left->setAutoRepeat(true);
ui->pushButton_right->setAutoRepeat(true);
二、Radio Button
QRadioButton 是单选按钮,可以让我们在多个选项中选择⼀个
QAbstractButton 中和 QRadioButton 关系较大的属性
| 属性 | 说明 |
|---|---|
| checkable | 是否能选中 |
| checked | 是否已经被选中,checkable 是 checked 的前提条件 |
| autoExclusive | 是否排他,选中⼀个按钮之后是否会取消其他按钮的选中,对于 QRadioButton 来说默认就是排他的 |
代码样例(选择性别):
首先,先创建一个label和三个按钮

cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_radioButton_male_clicked()
{
ui->label->setText("你选择的性别为:男");
}
void Widget::on_radioButton_female_clicked()
{
ui->label->setText("你选择的性别为:女");
}
void Widget::on_radioButton_order_clicked()
{
ui->label->setText("你选择的性别为:其他");
}
运行代码,我们会发现随着选择的不同,label会随着选择发生变化

我们还可以这样,我们发现在启动的时候没有默认选择,我们可以加上一个默认选择的代码:

同样的我们也可以禁用按钮,举个例子我们将"其他"这个选项禁用

这里我们就发现了一个问题,这个其他的选项虽然点不下去,但是label中还是现实了其他,这里我们就发现setCheckable只是能够让按钮不被选中,但是依旧可以响应点击事件。所以我们可以换其他的,具体如下:

代码示例(click, press, release, toggled 的区别):
在界面上创建四个单选按钮

cpp
void Widget::on_radioButton_clicked()
{
qDebug() << "clicked";
}
void Widget::on_radioButton_2_pressed()
{
qDebug() << "pressed";
}
void Widget::on_radioButton_3_released()
{
qDebug() << "released";
}
void Widget::on_radioButton_4_toggled(bool checked)
{
//状态发生改变就会触发这个信号
if (checked) {
qDebug() << "toggled checked true";
} else {
qDebug() << "toggled checked false";
}
}
运行程序后可以观察到:
- clicked 事件在鼠标按下并释放后触发
- pressed 事件在鼠标按下时触发
- released 事件在鼠标释放时触发
- toggled 事件在 checked 属性发生变化时触发
综合来看,toggled 是最适合 QRadioButton 的事件类型。
代码样例(单选框分组):
界面上创建6个单选框,模拟麦当劳点餐

如上我们运行后发现,每个按钮之间会有排它的现象,那我们怎么解决这个问题呢?
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QButtonGroup>
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);
//把属于一个组的选项,分别归类
group1->addButton(ui->radioButton);
group1->addButton(ui->radioButton_2);
group1->addButton(ui->radioButton_3);
group2->addButton(ui->radioButton_4);
group2->addButton(ui->radioButton_5);
group2->addButton(ui->radioButton_6);
group3->addButton(ui->radioButton_7);
group3->addButton(ui->radioButton_8);
group3->addButton(ui->radioButton_9);
}
Widget::~Widget()
{
delete ui;
}
加入如上的代码,再运行:

三、Check Box
QCheckBox 是用于实现多选的复选框控件。与QCheckBox 相关的主要属性包括 checkable 和checked,这两个属性均继承自 QAbstractButton 基类。
代码示例:
创建三个复选按钮和一个普通按钮

cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QString result;
if(ui->checkBox_eat->isChecked())
{
result += ui->checkBox_eat->text();
result += " ";
}
if(ui->checkBox_sleep->isChecked())
{
result += ui->checkBox_sleep->text();
result += " ";
}
if(ui->checkBox_play->isChecked())
{
result += ui->checkBox_play->text();
result += " ";
}
qDebug() << "选中的内容: " << result;
}
运行结果:

四、QLabel
QLabel 可以用来显示文本和图片
| 属性 | 说明 |
|---|---|
| text | QLabel 中的文本 |
| textFormat | 文本的格式:Qt::PlainText 纯文本,Qt::RichText 富文本(支持 html 标签),Qt::MarkdownText markdown 格式,Qt::AutoText 根据文本内容自动决定文本格式 |
| pixmap | QLabel 内部包含的图片 |
| scaledContents | 设为 true 表示内容自动拉伸填充 QLabel,设为 false 则不会自动拉伸 |
| alignment | 对齐方式,可以设置水平和垂直方向如何对齐 |
| wordWrap | 设为 true 内部的文本会自动换行,设为 false 则内部文本不会自动换行 |
| indent | 设置文本缩进. 水平和垂直方向都生效 |
| margin | 内部文本和边框之间的边距,不同于于 indent, 但是是上下左右四个方向都同时有效,而indent 最多只是两个方向有效(具体哪两个方向有效取决于 alignment |
| openExternalLinks | 是否允许打开⼀个外部的链接(当 QLabel 文本内容包含 url 的时候涉及到) |
| buddy | 给 QLabel 关联⼀个 "伙伴" , 这样点击 QLabel 时就能激活对应的伙伴,例如伙伴如果是⼀个 QCheckBox, 那么该 QCheckBox 就会被选中 |
代码示例:
创建三个QLable
cpp
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>");
ui->label_3->setTextFormat(Qt::MarkdownText);
ui->label_3->setText("## 这是⼀段 markdown ⽂本");
}
运行结果:

显示图片
创建一个resource.qrc文件,将图片载入

修改 widget.cpp, 给QLabel 设置图片
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label,让图片铺满窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/mouse.jpg");
ui->label->setPixmap(pixmap);
}
Widget::~Widget()
{
delete ui;
}
运行结果:

看到上述结果(图片铺满了整个窗口),但是如果我们拖动窗口:

是不是发现了问题,其实上面的代码中是在构造函数里,进行这样的尺寸设置,这个设置相当于是"一次性的",一旦程序运行起来之后,QLabel的尺寸就固定下来了,窗口发生变化,此时QLabel是不会变化的。
如果我们想图片随着窗口的变大变大的话该怎么实现呢?这就是后续事件的内容了?用户的操作其实会对应一些信号,Qt中,表示用户的操作,有两类概念,一个是信号,另一个是事件。当用户拖拽修改窗户的大小的时候,就会触resize事件,想risize这样的事件,是连续变化的。把窗户尺寸从A拖拽到B这个过程,就会触发一系列的resizeEvent。此时就可以借助resizeEvent来完成上述的功能。做法是:可以方Widget窗口类重写父类QWidget的resizeEvent虚函数。在鼠标拖拽窗口尺寸的过程中这个函数就会被反复的调用执行。
所以修改上述代码:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QtDebug>
#include <QResizeEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置label,让图片铺满窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/mouse.jpg");
ui->label->setPixmap(pixmap);
//启动自动拉伸
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
//此时的形参event非常有用,这里就包含了触发这个resize事件这一时刻,窗口的尺寸的数值
void Widget::resizeEvent(QResizeEvent *event)
{
qDebug() << event->size();
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
}
运行结果:

代码示例(文本对齐, 自动换行, 缩进, 边距)
创建四个 label, objectName 分别是 label 到 label_4
并且在 QFrame 中设置 frameShape 为 Box (设置边框之后看起来会更清晰一些)

QFrame 是 QLabel 的父类. 其中 frameShape 属性用来设置边框性质
- QFrame::Box :矩形边框
- QFrame::Panel :带有可点击区域的面板边框
- QFrame::WinPanel :Windows风格的边框
- QFrame::HLine :水平线边框
- QFrame::VLine :垂直线边框
- QFrame::StyledPanel :带有可点击区域的面板边框,但样式取决于窗口主题
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置⽂字居中对⻬
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->label->setText("垂直⽔平居中的⽂本");
// 设置⾃动换⾏
ui->label_2->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_2->setWordWrap(true);
ui->label_2->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本 这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
// 设置⾸⾏缩进
ui->label_3->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_3->setIndent(20);
ui->label_3->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本 这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
// 设置边距
ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_4->setMargin(20);
ui->label_4->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本 这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
}
代码运行:

代码样例(设置伙伴):
创建两个 label 和 两个 radioButton.
objectName 分别问 label , label_2 , radioButton , radioButton_2

将 label 文本设置为"快捷键 &A"格式,其中 & 后的字符即为快捷键。
使用时需通过 alt + A 组合键触发,但需注意:该快捷键机制与 QPushButton 不同,必须配合 alt 键和单个字母才能生效
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label 的伙伴 widget
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
运行程序, 可以看到, 按下快捷键 alt + a 或者 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::Oct :八进制模式,以八进制格式显示数字 |
| segmentStyle | 设置显示风格:QLCDNumber::Flat :平面的显示风格,数字呈现在一个平坦的表面上;QLCDNumber::Outline :轮廓显示风格,数字具有清晰的轮廓和阴影效果;QLCDNumber::Filled :填充显示风格,数字被填充颜色并与背景区分开 |
| smallDecimalPoint | 设置比较小的小数点 |
代码样例(倒计时):
在界面上创建一个lcdNumber

在widget.h中添加一个QTimer成员和一个updayTime函数

cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建QTimer实例
timer = new QTimer(this);
//连接信号槽,QTimer每隔一段时间就会触发一个timeout信号,把timeout信号和创建的updayTime联系起来
//此时意味着每次触发timeout信号,就会伴随着updayTime函数执行
connect(timer, &QTimer::timeout, this, &Widget::updayTime);
//启动QTimer,并且规定没1000ms触发一次信号
timer->start(1000);
}
Widget::~Widget()
{
delete ui;
}
void Widget::updayTime()
{
//通过inValue获取LCDnumber内部的值
int value = ui->lcdNumber->intValue();
qDebug() << "lcdNumber " << value;
if(value <= 0)
{
timer->stop();
return ;
}
ui->lcdNumber->display(value - 1);
}
运行代码:

针对上面的代码还有两个问题:
1)上述代码能不能在Widget的构造函数中,直接循环+sleep的方式呢?
将代码如下修改:
cpp
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);
}
}
运行结果:

2)能不能另起一个新的线程完成循环+sleep呢?
cpp
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 的规定,所有涉及 GUI 元素的操作都必须在主线程中完成。Widget 的构造函数以及通过 connect 连接的 slot 函数都是在主线程中执行的,而我们自行创建的线程则不属于主线程。当在自定义线程中尝试修改界面元素时,Qt 程序通常会发生崩溃。
六、ProgressBar
使用 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 :表示总时间(以毫秒为单位) |
代码样例(设置进度条按时间增长):
在界面上创建一个进度条

在widget.h中创建QTimer和updayprogressBar函数

cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//初始化QTimer
timer = new QTimer(this);
//连接
connect(timer, &QTimer::timeout, this, &Widget::updayprogressBar);
timer->start(100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::updayprogressBar()
{
int value = ui->progressBar->value();
if(value >= 100)
{
timer->stop();
return ;
}
ui->progressBar->setValue(value + 1);
}
运行结果:

如果你不喜欢上述的绿色,还可以这样更换颜色:

运行结果:

七、Calendar Widget
QCalendarWidget 表示⼀个 "日历" , 形如

| 属性 | 说明 |
|---|---|
| selectDate | 当前选中的日期 |
| minimumDate | 最小日期 |
| maximumDate | 最大日期 |
| firstDayOfWeek | 每周的第⼀天(也就是日历的第⼀列) 是周几 |
| gridVisible | 是否显示表格的边框 |
| selectionMode | 是否允许选择日期 |
| navigationBarVisible | 日历上方标题是否显示 |
| horizontalHeaderFormat | 日历上方标题显示的日期格式 |
| verticalHeaderFormat | 日历第⼀列显示的内容格式 |
| dateEditEnabled | 是否允许日期被编辑 |
重要的信号
| 信号 | 说明 |
|---|---|
| selectionChanged(const QDate&) | 当选中的日期发生改变时发出 |
| activated(const QDate&) | 当双击⼀个有效的日期或者按下回车键时发出,形参是⼀个QDate类型,保存了选中的日期 |
| currentPageChanged(int, int) | 当年份月份改变时发出,形参表示改变后的新年份和月份 |
代码样例(获取选择日期):
在界面上,创建一个calendarWidget和一个label

cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QDate>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
qDebug() << date;
ui->label->setText(date.toString());
}
运行结果:
