第三章 常用控件
1. 控件概述
Widget 是 Qt 中的核心概念,英文原义是 "小部件",我们此处也把它翻译为 "控件"。
控件是构成一个图形化界面的基本要素。

像上述示例中的:按钮、列表视图、树形视图、单行输入框、多行输入框、滚动条、下拉框等,都可以称为 "控件"。
Qt 作为一个成熟的 GUI 开发框架,内置了大量的常用控件。这一点在 Qt Designer 中就可以看到端倪。
并且 Qt 也提供了 "自定义控件" 的能力,可以让开发者在现有控件不能满足需求的时候,对现有控件做出扩展,或者手搓出新的控件。

2. QWidget 核心属性
在 Qt 中,使用 QWidget 类表示 "控件"。像按钮、视图、输入框、滚动条等具体的控件类,都是继承自 QWidget。
可以说,QWidget 中就包含了 Qt 整个控件体系中,通用的部分。
在 Qt Designer 中,随便拖一个控件过来,选中该控件,即可在右下方看到 QWidget 中的属性。

这些属性既可以通过 QtDesigner 直接修改,也可以通过代码的方式修改。
这些属性的具体含义,在 Qt Assistant 中均有详细介绍。
在 Qt Assistant 中搜索 QWidget,即可找到对应的文档说明。(或者在 Qt Creator 代码中,选中 QWidget,按 F1 也可)。
2.1 核心属性概览
| 属性 | 作用 |
|---|---|
| enabled | 设置控件是否可使用,true 表示可用,false 表示禁用。 |
| geometry | 位置和尺寸,包含 x、y,width、height 四个部分,其中坐标是以父元素为参考进行设置的。 |
| windowTitle | 设置 widget 标题。 |
| windowIcon | 设置 widget 图标。 |
| windowOpacity | 设置 widget 透明度。 |
| cursor | 鼠标悬停时显示的图标形状,是普通箭头、还是沙漏、还是十字等形状,在 Qt Designer 界面中可以清楚看到可选项。 |
| font | 字体相关属性,涉及到字体家族、字体大小、粗体、斜体、下划线等等样式。 |
| toolTip | 鼠标悬停在 widget 上会在状态栏中显示的提示信息。 |
| toolTipDuring | toolTip 显示的持续时间。 |
| statusTip | Widget 状态发生改变时显示的提示信息(比如按钮被按下等)。 |
| whatsThis | 鼠标悬停并按下 alt+F1 时,显示的帮助信息(显示在一个弹出的窗口中)。 |
| styleSheet | 允许使用 CSS 来设置 widget 中的样式。 |
| focusPolicy | 该 widget 如何获取到焦点: - Qt::NoFocus:控件不参与焦点管理,即无法通过键盘或鼠标获取焦点 - Qt::TabFocus:控件可以通过Tab键获得焦点 - Qt::ClickFocus:控件可以通过鼠标点击获得焦点 - Qt::StrongFocus:控件可以通过键盘和鼠标获得焦点 - Qt::WheelFocus:控件可以通过鼠标滚轮获得焦点(在某些平台或样式中可能不可用) |
| contextMenuPolicy | 上下文菜单的显示策略: - Qt::DefaultContextMenu:默认的上下文菜单策略,用户可以通过鼠标右键或键盘快捷键触发上下文菜单 - Qt::NoContextMenu:禁用上下文菜单,即使用户点击鼠标右键也不会显示菜单 - Qt::PreventContextMenu:防止控件显示上下文菜单,即使用户点击鼠标右键也不会显示菜单 - Qt::ActionsContextMenu:将上下文菜单替换为控件的"动作"菜单,用户可以通过鼠标右键或键盘快捷键触发这个菜单 - Qt::CustomContextMenu:使用自定义的上下文菜单,用户可以通过鼠标右键或键盘快捷键触发这个菜单 |
| locale | 设置语言和国家地区。 |
| acceptDrops | 该部件是否接受拖放操作。 如果设置为true,那么该部件就可以接收来自其他部件的拖放操作。当一个部件被拖放到该部件上时,该部件会接收到相应的拖放事件(如dropEvent)。 如果设置为false,那么该部件将不会接收任何拖放操作。 |
| minimumSize | 控件的最小尺寸,包含最小宽度和最小高度。 |
| maximumSize | 控件的最大尺寸,包含最大宽度和最大高度。 |
| sizePolicy | 尺寸策略,设置控件在布局管理器中的缩放方式。 |
| windowModality | 指定窗口是否具有 "模态" 行为。 |
| sizeIncrement | 拖动窗口大小时的增量单位。 |
| baseSize | 窗口的基础大小,用来搭配 sizeIncrement 调整组件尺寸是计算组件应该调整到的合适的值。 |
| palette | 调色板,可以设置 widget 的颜色风格。 |
| mouseTracking | 是否要跟踪鼠标移动事件。 如果设为 true,表示需要跟踪,则鼠标划过的时候该 widget 就能持续收到鼠标移动事件。 如果设为 false, 表示不需要跟踪,则鼠标划过的时候 widget 不会收到鼠标移动事件,只能收到鼠标按下或者释放的事件. |
| tabletTracking | 是否跟踪触摸屏的移动事件。 类似于 mouseTracking,Qt 5.9 中引入的新属性。 |
| layoutDirection | 布局方向: - Qt::LeftToRight:文本从左到右排列,也是默认值。 - Qt::RightToLeft:文本从右到左排列。 - Qt::GlobalAtomics:部件的布局方向由全局原子性决定,其实就是根据应用程序中的其他 widget 布局方向确定的。 |
| autoFillBackground | 是否自动填充背景颜色。 |
| windowFilePath | 能够把 widget 和一个本地文件路径关联起来。PS: 其实作用不大。 |
| accessibleName | 设置 widget 的可访问名称,这个名称可以被辅助技术 (像屏幕阅读器) 获取到。 这个属性用于实现无障碍程序的场景中 (也就是给盲人写的程序)。 |
| accessibleDescription | 设置 widget 的详细描述,作用同 accessibleName。 |
| inputMethodHints | 针对输入框有效,用来提示用户当前能输入的合法数据的格式,比如只能输入数字,只能输入日期等。 |
接下来我们会介绍其中一些比较重要比较常用的属性。
2.2 enabled
| API | 说明 |
|---|---|
| isEnabled() | 获取到控件的可用状态。 |
| setEnabled() | 设置控件是否可使用,true 表示可用,false 表示禁用。 |
2.2.1 代码示例1:使用代码创建一个禁用状态的按钮。
widget.cpp
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
,ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn = new QPushButton(this);
btn->setText("这是个被禁用的按钮");
btn->setEnabled(false);
}

2.2.2 代码示例:通过按钮2切换按钮1的禁用状态。
使用 Qt Designer 拖两个按钮到 Widget 中。

两个按钮的 objectName 分别为 pushButton 和 pushButton_2。
cpp
class Ui_Widget
{
public:
QPushButton *pushButton;
QPushButton *pushButton_2;
// ......
}
生成两个按钮的 slot 函数。
- 使用 isEnabled 获取当前按钮的可用状态。
- 使用 setEnabled 修改按钮的可用状态,此处是直接针对原来的可用状态进行取反后设置。
cpp
void Widget::on_pushButton_clicked()
{
qDebug() << "按下按钮";
}
void Widget::on_pushButton_2_clicked()
{
bool flag = this->ui->pushButton->isEnabled();
this->ui->pushButton->setEnabled(!flag);
}
运行程序,可以看到,初始情况下,上面的按钮是可用状态。
点击下方按钮,即可使上方按钮被禁用,再次点击下方按钮,上方按钮就会解除禁用。(禁用状态的按钮为灰色,且不可点击)。


2.3 geometry
位置和尺寸,其实是四个属性的统称:
- x 横坐标
- y 纵坐标
- width 宽度
- height 高度

但是实际开发中,我们并不会直接使用这几个属性,而是通过一系列封装的方法来获取/修改。
| API | 说明 |
|---|---|
| geometry() | 获取到控件的位置和尺寸,返回结果是一个 QRect,包含了 x、y、width、height。 |
| setGeometry(QRect) setGeometry(int x,int y,int width,int height) | 设置控件的位置和尺寸,可以直接设置一个 QRect,也可以分四个属性单独设置。 |
2.3.1 代码示例1:控制按钮的位置
在界面中拖五个按钮
五个按钮的 objectName 分别为 pushButton_target、pushButton_up、pushButton_down、pushButton_left、pushButton_right,五个按钮的初始位置和大小都随意。

在 widget.cpp 中编写四个按钮的 slot 函数。
cpp
void Widget::on_pushButton_up_clicked()
{
QRect rect = ui->pushButton_target->geometry();
rect.setY(rect.y() - 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_down_clicked()
{
QRect rect = ui->pushButton_target->geometry();
rect.setY(rect.y() + 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_left_clicked()
{
QRect rect = ui->pushButton_target->geometry();
rect.setX(rect.x() - 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_right_clicked()
{
QRect rect = ui->pushButton_target->geometry();
rect.setX(rect.x() + 5);
ui->pushButton_target->setGeometry(rect);
}
运行程序,可以看到,按下下方的四个按钮,就会控制 target 的左上角的位置。对应的按钮整个尺寸也会发生改变。
上述代码中我们是直接设置的 QRect 中的 x、y,实际上 QRect 内部是存储了左上和右下两个点的坐标,再通过这两个点的坐标差值计算长宽。
单纯修改左上坐标就会引起整个矩形的长宽发生改变。

如果想让整个按钮都移动,可以改成下列代码:
cpp
void Widget::on_pushButton_up_clicked()
{
QRect rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height());
}
void Widget::on_pushButton_down_clicked()
{
QRect rect = ui->pushButton_target->geometry();
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();
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();
ui->pushButton_target->setGeometry(rect.x() + 5, rect.y(), rect.width(), rect.height());
}
实现上述功能,使用 move 方法也是可以的。
2.3.2 代码示例2:一个表白程序
往界面上拖拽两个按钮和一个 Label。
pushButton 的 objectName 为 pushButton_accept 和 pushButton_reject,label 的
objectName 为 label。
控件中文本如下图所示。

在 widget.cpp 中添加 slot 函数。
cpp
void Widget::on_pushButton_accept_clicked()
{
ui->label->setText("女神我爱你!");
}
void Widget::on_pushButton_reject_pressed()
{
// 获取窗口的宽度和高度
int width = this->geometry().width();
int height = this->geometry().height();
// 重新生成按钮的位置.
int x = rand() % width;
int y = rand() % height;
// 设置新的位置
ui->pushButton_reject->move(x, y);
}
运行程序,可以看到,当点击 "残忍拒绝" 时,按钮就跑了。

上述代码使用的是 pressed,鼠标按下事件,如果使用 mouseMoveEvent,会更狠一些,只要鼠标移动过来,按钮就跑了。
2.4 window frame 对坐标的影响
如果 widget 作为一个窗口 (带有标题栏, 最小化, 最大化, 关闭按钮), 那么在计算尺寸和坐标的时候就有两种算法. 包含 window frame 和 不包含 window frame.
其中 x(), y(), frameGeometry(), pos(), move() 都是按照包含 window frame 的方式来计算的.
其中 geometry(), width(), height(), rect(), size() 则是按照不包含 window frame 的方式来计算的.
当然, 如果一个不是作为窗口的 widget , 上述两类方式得到的结果是一致的.

相关 API
| API | 说明 |
|---|---|
| x() | 获取横坐标,计算时包含 window frame |
| y() | 获取纵坐标,计算时包含 window frame |
| pos() | 返回 QPoint 对象, 里面包含 x(), y(), setX(), setY() 等方法. 计算时包含 window frame |
| frameSize() | 返回 QSize 对象, 里面包含 width(), height(), setWidth(), setHeight() 等方法. 计算时包含 window frame |
| frameGeometry() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取 x, y, width, size. 计算时包含 window frame 对象. |
| width() | 获取宽度, 计算时不包含 window frame |
| height() | 获取高度, 计算时不包含 window frame |
| size() | 返回 QSize 对象, 里面包含 width(), height(), setWidth(), setHeight() 等方法, 计算时不包含 window frame |
| rect() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取并设置 x, y, width, size. 计算时不包含 window frame 对象. |
| geometry() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取 x, y, width, size. 计算时不包含 window frame 对象. |
| setGeometry() | 直接设置窗口的位置和尺寸. 可以设置 x, y, width, height, 或者 QRect 对象. 计算时不包含 window frame 对象. |
2.4.1 代码示例:感受 geometry 和 frameGeometry 的区别
在界面上放置一个按钮.

在按钮的 slot 函数中, 编写代码.
cpp
void Widget::on_pushButton_clicked()
{
QRect rect1 = this->geometry();
QRect rect2 = this->frameGeometry();
qDebug() << rect1;
qDebug() << rect2;
}
在构造函数中, 也添加同样的代码.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QRect rect1 = this->geometry();
QRect rect2 = this->frameGeometry();
qDebug() << rect1;
qDebug() << rect2;
}
执行程序, 可以看到, 构造函数中, 打印出的 geometry 和 frameGeometry 是相同的.
但是在点击按钮时, 打印的 geometry 和 frameGeometry 则存在差异.

在构造方法中, Widget 刚刚创建出来, 还没有加入到对象树中. 此时也就不具备 Window frame.
在按钮的 slot 函数中, 由于用户点击的时候, 对象树已经构造好了, 此时 Widget 已经具备了 Window frame, 因此在位置和尺寸上均出现了差异.
如果把上述代码修改成打印 pushButton 的 geometry 和 frameGeometry , 结果就是完全相同的. 因为 pushButton 并非是一个窗口.
2.5 windowTitle
| API | 说明 |
|---|---|
| windowTitle() | 获取到控件的窗口标题. |
| setWindowTitle(const QString& title) | 设置控件的窗口标题. |
注意! 上述设置操作针对不同的 widget 可能会有不同的行为.
如果是顶层 widget (独立窗口), 这个操作才会有效.
如果是子 widget, 这个操作无任何效果.
2.5.1 代码示例:设置窗口标题
修改 widget.cpp
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置窗口标题
this->setWindowTitle("这是标题");
}
执行效果:

2.6 windowIcon
| API | 说明 |
|---|---|
| windowIcon() | 获取到控件的窗口图标. 返回 QIcon 对象 |
| setWindowIcon(const QIcon& icon) | 设置控件的窗口图标 |
同 windowTitle, 上述操作仅针对顶层 widget 有效.
2.6.1 代码示例1:设置窗口图标
先在电脑中放一个图片, 名字为 rose.jpg

修改 widget.cpp
cpp
#include <QIcon>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建图标对象
QIcon icon("d:/rose.jpg");
// 设置图标
this->setWindowIcon(icon);
}
运行程序,可以看到窗口图标已经成为上述图片。

与此同时, 程序在任务栏中的图表也发生改变.

2.7 关于路径
实际开发中, 我们一般不会在代码中通过绝对路径引入图片. 因为我们无法保证程序发布后, 用户的电脑上也有同样的路径.
如果使用相对路径, 则需要确保代码中的相对路径写法和图片实际所在的路径匹配 (比如代码中写作 "./image/rose.jpg", 就需要在当前工作目录中创建 image 目录, 并把 rose.jpg 放进去).
2.7.1 代码示例1:获取当前的工作目录
在界面上创建一个比较大的 label, 确保能把路径显示完整. objectName 使用默认的 label 即可.

修改 widget.cpp.
使用 QDir::currentPath() 即可获取到当前工作目录.
cpp
#include <QDir>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 获取到当前工作目录
QString currentDir = QDir::currentPath();
// 设置工作目录到 label 中.
ui->label->setText(currentDir);
}
直接在 Qt Creator 中执行程序, 可以看到当前工作目录是项目的构建目录.

进入上述构建目录, 把里面的 exe 拷贝到其他目录中, 比如 D: 中. 再次执行程序, 可以看到当前工作目录已经发生改变.

要想直接能双击 exe 运行, 需要先把 Qt 的路径添加到 path 环境变量中, 否则会提示找不到动态库.
注意, 上述 构建目录, 是随时可删除的. 比如点击菜单栏中的 "构建" -> "清理项目" , 就会把这个目录中的内容清空掉.
2.7.2 qrc
如果我们把图片文件放到构建目录中, 可能在不小心删除后就丢失了. 我们还是希望能够把图片和源代码放到一起, 并且使我们的程序无论拷贝到任何位置中都能正确使用图片.
Qt 使用 qrc 机制帮我们自动完成了上述工作, 更方便的来管理项目依赖的静态资源.
qrc 文件是一种XML格式的资源配置文件, 它用XML记录硬盘上的文件和对应的随意指定的资源名称. 应用程序通过资源名称来访问这些资源.
在Qt开发中, 可以通过将资源文件添加到项目中来方便地访问和管理这些资源. 这些资源文件可以位于qrc文件所在目录的同级或其子目录下.
在构建程序的过程中, Qt 会把资源文件的二进制数据转成 cpp 代码, 编译到 exe 中. 从而使依赖的资源变得 "路径无关".
这种资源管理机制并非 Qt 独有, 很多开发框架都有类似的机制. 例如 Android 的 Resources 和 AssetManager 也是类似的效果.
2.7.3 代码示例2:通过 qrc 管理图片作为图标
右键项目, 创建一个 Qt Resource File (qrc 文件), 文件名随意起(不要带中文), 此处叫做 resource.qrc .

在 qrc 编辑器中, 添加前缀.

此处我们前缀设置成 / 即可.
所谓的前缀, 可以理解成 "目录" . 这个前缀决定了后续我们如何在代码中访问资源.
在 资源编辑器 中, 点击 add Files 添加资源文件. 此处我们需要添加的是 rose.jpg.

注意: 添加的文件必须是在 qrc 文件的同级目录, 或者同级目录的子目录中. 因此我们需要把之前 D 盘 中的 rose.jpg 复制到上述目录中.
添加完毕后, 可以在 资源编辑器 中看到添加好的文件

在代码中使用 rose.jpg
编辑 widget.cpp
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 访问到 rose.jpg 资源
QIcon icon(":/rose.jpg");
// 设置图标
this->setWindowIcon(icon);
}
注意上述路径的访问规则:
- 使用 : 作为开头, 表示从 qrc 中读取资源.
- / 是上面配置的前缀
- rose.jpg 是资源的名称
需要确保代码中编写的路径和添加到 qrc 中资源的路径匹配. 否则资源无法被访问 (同时也不会有报错提示).
运行程序, 可以看到图标已经能正确设置

接下来, 我们可以进入到项目的构建目录, 可以看到, 目录中多了一个 qrc_resource.cpp 文件. 直接打开这个文件, 可以看到类似如下代码:
static const unsigned char qt_1 resource_data[] = {
// D:/project/ke/qt/DemoCode/DemoWindowIconQrc/rose.jpg
0x0,0x0,0x33,0x2,0xff,0xd8,0xff,0xe0,0x0,0x10,0x4a,0x46,0x49,0x46,0x0,0x1,0x1,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0xff,0xfe,0x0,0x3b,0x43,0x52,0x45,0x41,0x54,0x4f,0x52,0x3a,0x20,0x67,0x64,0x2d,0x6a,0x70,0x65,0x67,0x20,0x76,0x31,0x2e,0x30,0x20,0x28,0x75,0x73,0x69,0x6e,0x67,0x20,0x49,0x4a,0x47,0x20,0x4a,0x50,0x45,0x47,0x20,0x76,0x36,0x32,0x29,0x2c,0x20,0x71,0x75,0x61,0x6c,0x69,0x74,0x79,0x20,0x3d,0x20,0x39,0x35,0xa,0xff,0xdb,0x0,0x43,0x0,0x2,0x1,0x1,0x1,0x1,0x1,0x2,0x1,0x1,0x1,0x2,0x2,0x2,0x2,0x2,0x4,0x3,0x2,0x2,0x2,0x2,0x5,0x4,0x4,0x3,0x4,0x6,0x5,0x6,0x6,0x6,0x5,0x6,0x6,0x6,0x7,0x9,0x8,0x6,0x7,0x9,0x7,0x6,0x6,0x8,0xb,0x8,0x9,0xa,0xa,0xa,0xa,0xa,0x6,0x8,0xb,0xc,0xb,0xa,0xc,0x9,0xa,0xa,0xa,0xff,0xdb,0x0,0x43,0x1,0x2,0x2,0x2,0x2,0x2,0x2,
// .............
上述代码其实就是通过 unsigned char 数组, 把 rose.jpg 中的每个字节都记录下来. 这些代码会被编译到 exe 中. 后续无论 exe 被复制到哪个目录下, 都确保能够访问到该图片资源.
上述 qrc 这一套资源管理方案, 优点和缺点都很明显.
优点: 确保了图片, 字体, 声音等资源能够真正做到 "目录无关", 无论如何都不会出现资源丢失的情况.
缺点: 不适合管理体积大的资源. 如果资源比较大 (比如是几个 MB 的文件), 或者资源特别多,
生成的最终的 exe 体积就会比较大, 程序运行消耗的内存也会增大, 程序编译的时间也会显著增加.
2.8 windowOpacity
| API | 说明 |
|---|---|
| windowOpacity() | 获取到控件的不透明数值. 返回 float, 取值为 0.0 -> 1.0 其中 0.0 表示全透明, 1.0 表示完全不透明. |
| setWindowOpacity(float n) | 设置控件的不透明数值. |
2.8.1 代码示例1:调整窗口透明度
在界面上拖放两个按钮, 分别用来增加不透明度和减少不透明度.
objectName 分别为 pushButton_add 和 pushButton_sub

编写 wdiget.cpp, 编写两个按钮的 slot 函数
点击 pushButton_sub 会减少不透明度, 也就是窗口越来越透明.
点击 pushButton_add 会增加不透明度, 窗口会逐渐恢复.
cpp
void Widget::on_pushButton_add_clicked()
{
float opacity = this->windowOpacity();
if (opacity >= 1.0) {
return;
}
qDebug() << opacity;
opacity += 0.1;
this->setWindowOpacity(opacity);
}
void Widget::on_pushButton_sub_clicked()
{
float opacity = this->windowOpacity();
if (opacity <= 0.0) {
return;
}
qDebug() << opacity;
opacity -= 0.1;
this->setWindowOpacity(opacity);
}
执行程序, 可以看到, 点击了几下 - 之后, 就可以透过窗口看到后面的猫猫头了. 点击 + 又会逐渐恢复.

同时控制台中也可以看到 opacity 数值的变化

注意, C++ 中 float 类型遵守 IEEE 754 标准, 因此在进行运算的时候会有一定的精度误差. 因此 1 - 0.1 的数值并非是 0.9。
2.9 cursor
| API | 说明 |
|---|---|
| cursor() | 获取到当前 widget 的 cursor 属性, 返回 QCursor 对象. 当鼠标悬停在该 widget 上时, 就会显示出对应的形状. |
| setCursor(const QCursor& cursor) | 设置该 widget 光标的形状. 仅在鼠标停留在该 widget 上时生效. |
| QGuiApplication::setOverrideCursor(const QCursor& cursor) | 设置全局光标的形状. 对整个程序中的所有 widget 都会生效. 覆盖上面的 setCursor 设置的内容. |
2.9.1 代码示例:在 Qt Designer 中设置按钮的光标
在界面中创建一个按钮.

直接在右侧属性编辑区修改 cursor 属性为 "等待"

运行程序, 鼠标悬停到按钮上, 即可看到光标的变化(由于截图无法截到鼠标光标,这里就不放效果了,可以自行测试).
2.9.2 代码示例:通过代码设置按钮的光标
编写 widget.cpp
cpp
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建按钮
QPushButton* button = new QPushButton(this);
button->resize(100, 50);
button->move(100, 100);
button->setText("这是一个按钮");
// 设置按钮的 cursor,Qt::WaitCursor就是自带的沙漏形状的光标
button->setCursor(QCursor(Qt::WaitCursor));
}
系统内置的光标形状如下(ctrl + 左键点击Qt::WaitCursor跳转源码就能看到):
cpp
enum CursorShape {
ArrowCursor,
UpArrowCursor,
CrossCursor,
WaitCursor,
IBeamCursor,
SizeVerCursor,
SizeHorCursor,
SizeBDiagCursor,
SizeFDiagCursor,
SizeAllCursor,
BlankCursor,
SplitVCursor,
SplitHCursor,
PointingHandCursor,
ForbiddenCursor,
WhatsThisCursor,
BusyCursor,
OpenHandCursor,
ClosedHandCursor,
DragCopyCursor,
DragMoveCursor,
DragLinkCursor,
LastCursor = DragLinkCursor,
BitmapCursor = 24,
CustomCursor = 25
}
2.9.3 代码示例:自定义鼠标光标
Qt 自带的光标形状有限. 我们也可以自己找个图片, 做成鼠标的光标.

创建 qrc 资源文件, 添加前缀 / , 并加入 huaji.png



编写 widget.cpp
cpp
#include <QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建一个位图对象, 加载自定义光标图片
QPixmap pixmap(":/huaji.png");
// 缩放图片为 64 * 64 的尺寸.
pixmap = pixmap.scaled(64, 64);
// 创建 QCursor 对象, 并指定 "热点" 为 (2, 2) 坐标位置.
// 所谓 "热点" 就是鼠标点击时生效的位置.
QCursor cursor(pixmap, 2, 2);
// 设置光标
this->setCursor(cursor);
}
2.10 font
| API | 说明 |
|---|---|
| font() | 获取当前 widget 的字体信息. 返回 QFont 对象. |
| setFont(const QFont& font) | 设置当前 widget 的字体信息. |
关于 QFont
| 属性 | 说明 |
|---|---|
| family | 字体家族. 比如 "楷体", "宋体", "微软雅黑" 等. |
| pointSize | 字体大小 |
| weight | 字体粗细. 以数值方式表示粗细程度取值范围为 [0, 99], 数值越大, 越粗. |
| bold | 是否加粗. 设置为 true, 相当于 weight 为 75. 设置为 false 相当于 weight 为 50. |
| italic | 是否倾斜 |
| underline | 是否带有下划线 |
| strikeOut | 是否带有删除线 |
2.10.1 代码示例1:在 Qt Designer 中设置字体属性
代码示例: 在 Qt Designer 中设置字体属性
在界面上创建一个 label

在右侧的属性编辑区, 设置该 label 的 font 相关属性

执行程序, 观察效果

2.10.2 代码示例2:在代码中设置字体属性
在界面中创建 label, objectName 使用默认的 label 即可.

修改 widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label 的文本内容
ui->label->setText("这是一段文本");
// 创建字体对象
QFont font;
// 设置字体家族
font.setFamily("仿宋");
// 设置字体大小
font.setPointSize(20);
// 设置字体加粗
font.setBold(true);
// 设置字体倾斜
font.setItalic(true);
// 设置字体下划线
font.setUnderline(true);
// 设置字体删除线
font.setStrikeOut(true);
// 设置字体对象到 label 上
ui->label->setFont(font);
}
运行程序, 观察效果

2.11 toolTip
| API | 说明 |
|---|---|
| setToolTip() | 设置 toolTip. 鼠标悬停在该 widget 上时会有提示说明. |
| setToolTipDuring() | 设置 toolTip 提示的时间. 单位 ms. 时间到后 toolTip 自动消失. |
2.11.1 代码示例1:设置按钮的 toolTip
在界面上拖放两个按钮. objectName 设置为 pushButton_yes 和 pushButton_no

编写 widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton_yes->setToolTip("这个是 yes 按钮");
ui->pushButton_yes->setToolTipDuration(3000);
ui->pushButton_no->setToolTip("这个是 no 按钮");
ui->pushButton_no->setToolTipDuration(10000);
}
运行程序, 观察效果
可以看到鼠标停到按钮上之后, 就能弹出提示. 时间到后自行消失.

2.12 focusPolicy
设置控件获取到焦点的策略. 比如某个控件能否用鼠标选中或者能否通过 tab 键选中.
所谓 "焦点" , 指的就是能选中这个元素. 接下来的操作 (比如键盘操作), 就都是针对该焦点元素进行的了. 这个对于 输入框, 单选框, 复选框等控件非常有用的.
| API | 说明 |
|---|---|
| focusPolicy() | 获取该 widget 的 focusPolicy, 返回 Qt::FocusPolicy |
| setFocusPolicy(Qt::FocusPolicy policy) | 设置 widget 的 focusPolicy. |
Qt::FocusPolicy 是一个枚举类型. 取值如下:
- Qt::NoFocus :控件不会接收键盘焦点
- Qt::TabFocus :控件可以通过Tab键接收焦点
- Qt::ClickFocus :控件在鼠标点击时接收焦点
- Qt::StrongFocus :控件可以通过Tab键和鼠标点击接收焦点 (默认值)
- Qt::WheelFocus : 类似于 Qt::StrongFocus , 同时控件也通过鼠标滚轮获取到焦点 (新增的选项, 一般很少使用).
2.12.1 代码示例1:理解不同的 focusPolicy
在界面上创建四个单行输入框 (Line Edit)

修改四个输入框的 focusPolicy 属性为 Qt::StrongFocus (默认取值, 一般不用额外修改)

此时运行程序, 可以看到, 使用鼠标单击/tab, 就可以移动光标所在输入框. 从而接下来的输入就是针对这个获取焦点的输入框展开的了.
修改第二个输入框的 focusPolicy 为 Qt::NoFocus , 则第二个输入框不会被 tab / 鼠标左键 选中.

修改第二个输入框 focusPolicy 为 Qt::TabFocus , 则只能通过 tab 选中, 无法通过鼠标选中.

修改第二个输入框 focusPolicy 为 Qt::ClickFocus , 则只能通过鼠标选中, 无法通过 tab 选中.

2.13 styleSheet
通过 CSS 设置 widget 的样式.
CSS 中可以设置的样式属性非常多. 基于这些属性 Qt 只能支持其中一部分, 称为 QSS (Qt Style Sheet). 具体的支持情况可以参考 Qt 文档中 "Qt Style Sheets Reference" 章节.
此处只是进行一个简单的演示.
在界面上创建 label

编辑右侧的 styleSheet 属性, 设置样式

此处的语法格式同 CSS, 使用键值对的方式设置样式. 其中键和值之间使用 : 分割. 键值对之间使用 ; 分割.
另外, Qt Designer 只能对样式的基本格式进行校验, 不能检测出哪些样式不被 Qt 支持. 比如 textalign:center 这样的文本居中操作, 就无法支持.
编辑完成样式之后, 可以看到在 Qt Designer 中能够实时预览出效果.

2.13.1 代码示例1:切换夜间模式
在界面上创建一个多行输入框 (Text Edit) 和两个按钮.
objectName 分别为 pushButton_light 和 pushButton_dark

编写按钮的 slot 函数.
- #333 是深色, 但是没那么黑.
- #fff 是纯白色.
- #000 是纯黑色.
使用在线调色板或者画图板, 都可以看到数字对应的颜色.
cpp
void Widget::on_pushButton_dark_clicked()
{
this->setStyleSheet("background-color: #333");
ui->textEdit->setStyleSheet("background-color: #333; color: #fff;");
ui->pushButton_light->setStyleSheet("color: #fff");
ui->pushButton_dark->setStyleSheet("color: #fff");
}
void Widget::on_pushButton_light_clicked()
{
this->setStyleSheet("background-color: #f3f3f3");
ui->textEdit->setStyleSheet("background-color: #fff; color: #000;");
ui->pushButton_light->setStyleSheet("color: #000");
ui->pushButton_dark->setStyleSheet("color: #000");
}
运行程序, 点击 "日间模式", 就是浅色背景, 深色文字; 点击 "夜间模式", 就是深色背景, 浅色文字.


3. 按钮类控件
3.1 Push Button
使用 QPushButton 表示一个按钮. 这也是当前我们最熟悉的一个控件了.
QPushButton 继承自 QAbstractButton . 这个类是一个抽象类. 是其他按钮的父类.

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

QAbstractButton 中, 和 QPushButton 相关性较大的属性
| text | 按钮中的文本 |
| icon | 按钮中的图标 |
| iconSize | 按钮中图标的尺寸 |
| shortCut | 按钮对应的快捷键 |
| autoRepeat | 按钮是否会重复触发. 当鼠标左键按住不放时, 如果设为 true, 则会持续产生鼠标点击事件; 如果设为 false, 则必须释放鼠标, 再次按下鼠标时才能产生点击事件. (相当于游戏手柄上的 "连发" 效果) |
| autoRepeatDelay | 重复触发的延时时间. 按住按钮多久之后, 开始重复触发. |
| autoRepeatInterval | 重复触发的周期. |
事实上, QPushButton 的核心功能都是 QAbstractButton 提供的. 自身提供的属性都比较简单.
其中 default 和 audoDefault 影响的是按下 enter 时自动点击哪个按钮的行为; flat 把按钮
设置为扁平的样式. 这里我们暂时都不做过多关注.
3.1.1 代码示例1:带有图标的按钮
创建 resource.qrc 文件, 并导入图片

在界面上创建一个 按钮

修改 widget.cpp, 给按钮设置图标.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建图标
QIcon icon(":/doge.png");
// 设置图标
ui->pushButton->setIcon(icon);
// 设置图标大小
ui->pushButton->setIconSize(QSize(50, 50));
}
执行程序, 观察效果

3.1.2 代码示例2:带有快捷键的按钮
在界面中拖五个按钮.
五个按钮的 objectName 分别为 pushButton_target , pushButton_up , pushButton_down , pushButton_left , pushButton_right 五个按钮的初始位置随意, 其中 pushButton_target 尺寸设置为 100 * 100, 其余按钮设为 50 *50, 文本内容均清空.

创建 resource.qrc , 并导入 5 个图片.






修改 widget.cpp, 设置图标资源和快捷键
使用 setShortcut 给按钮设置快捷键. 参数是一个 QKeySequence 对象. 表示一个按键序列. 支持组合键 (ctrl + c 这种).
QKeySequence 的构造函数参数, 可以直接使用 "ctrl+c" 这样的按键名字符串表示, 也可以使用预定义好的常量 (形如 Qt::CTRL + Qt::Key_C ) 表示.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置图标
ui->pushButton_target->setIcon(QIcon(":/dog.png"));
ui->pushButton_target->setIconSize(QSize(100, 100));
ui->pushButton_up->setIcon(QIcon(":/caret-up.png"));
ui->pushButton_down->setIcon(QIcon(":/caret-down.png"));
ui->pushButton_left->setIcon(QIcon(":/caret-left.png"));
ui->pushButton_right->setIcon(QIcon(":/caret-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"));
// 设置快捷键也可以写作
// ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_W));
// ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_S));
// ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_A));
// ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_D));
}
修改 widget.cpp, 设置四个方向键的 slot 函数.
cpp
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";
}
运行程序, 此时点击按钮, 或者使用 wasd 均可让狗头移动.

3.1.3 代码示例3:按钮的重复触发
在上述案例中, 按住快捷键, 是可以进行重复触发的. 但是鼠标点击则不能.
修改 widget.cpp, 在构造函数中开启重复触发.
cpp
// 开启重复触发
ui->pushButton_up->setAutoRepeat(true);
ui->pushButton_down->setAutoRepeat(true);
ui->pushButton_left->setAutoRepeat(true);
ui->pushButton_right->setAutoRepeat(true);
此时, 按住鼠标时, 即可让狗头连续移动.
3.2 Radio Buttion
QRadioButton 是单选按钮. 可以让我们在多个选项中选择一个.
作为 QAbstractButton 和 QWidget 的子类, 上面介绍的属性和用法, 对于 QRadioButton
同样适用.
QAbstractButton 中和 QRadioButton 关系较大的属性.
| 属性 | 说明 |
|---|---|
| checkable | 是否能选中 |
| checked | 是否已经被选中. checkable 是 checked 的前提条件. |
| autoExclusive | 是否排他. 选中一个按钮之后是否会取消其他按钮的选中. 对于 QRadioButton 来说默认就是排他的. |
3.2.1 代码示例1:选择性别
在界面上创建一个 label, 和 3 个 单选按钮.
设置的文本如下图. 3 个单选按钮的 objectName 分别为 radioButton_male , radioButton_female , radioButton_other.

修改 widget.cpp, 编辑三个 QRadioButton 的 slot 函数.
cpp
void Widget::on_radioButton_male_clicked()
{
ui->label->setText("你选择的性别为: 男");
}
void Widget::on_radioButton_female_clicked()
{
ui->label->setText("你选择的性别为: 女");
}
void Widget::on_radioButton_other_clicked()
{
ui->label->setText("你选择的性别为: 其他");
}
运行程序, 可以看到随着选择不同的单选按钮, label 中的提示文字就会随之变化.

当前代码中, 如果程序启动, 则不会选择任何选项.
可以修改代码, 让程序启动默认选中性别男
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置默认选中该按钮
ui->radioButton_male->setChecked(true);
ui->label->setText("你选择的性别为: 男");
}
此时运行程序, 即可看到 性别男 已经被选中了.
当前代码中, 也可以禁用 "其他" 被选中.
修改 widget.cpp 的构造函数
cpp
// 禁用 other 选项
ui->radioButton_other->setCheckable(false);
运行程序, 可以看到, 点击 "其他" 按钮的时候, 虽然不会被选中, 但是可以触发点击事件, 使上面的 label 显示性别为其他.
使用 setEnabled 是更彻底的禁用按钮的方式. 此时该按钮无法被选中, 也无法响应任何输入
cpp
// 禁用 other 选项
ui->radioButton_other->setEnabled(false);

3.2.2 代码示例2:click, press, release, toggled 的区别
- clicked 表示一次 "点击"
- pressed 表示鼠标 "按下"
- released 表示鼠标 "释放"
- toggled 表示按钮状态切换
在界面上创建四个单选按钮
objectName 分别为 radioButton , radioButton_2 , radioButton_3 , radioButton_4

给 1 创建 clicked 槽函数, 给 2 创建 pressed 槽函数, 给 3 创建 released 槽函数,给 4 创建 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 的.
3.2.3 代码示例3:单选框分组
在界面上创建 6 个单选框, 用来模拟点餐界面.
objectName 分别为 radioButton 到 radioButton_6.

此时直接运行程序, 可以看到, 这六个 QRadioButton 之间都是排他的.
我们希望每一组内部来控制排他, 但是组和组之间不能排他.
引入 QButtonGroup 进行分组.
修改 widget.cpp
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.3 Check Box
QCheckBox 表示复选按钮. 可以允许选中多个.
和 QCheckBox 最相关的属性也是 checkable 和 checked , 都是继承自 QAbstractButton .
至于 QCheckBox 独有的属性 tristate 用来实现 "三态复选框" . 这个东西比较冷门, 这里不做讨论.
3.3.1 代码示例1:获取复选按钮的取值
在界面上创建 三个复选按钮, 和一个普通按钮.
objectName 分别为 checkBox_eat , checkBox_sleep , checkBox_play 以及 pushButton

给 pushButton 添加 slot 函数
cpp
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;
}
运行程序, 可以看到点击确认按钮时, 就会在控制台中输出选中的内容.


3.4 Tool Button
QToolButton 的大部分功能, 和 QPushButton 是一致的. 但是 QToolButton 主要应用在工
具栏, 菜单等场景. 这里暂时先不介绍.
4. 显示类控件
4.1 Label
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, margin是上下左右四个方向都同时有效. 而 indent 最多只是两个方向有效(具体哪两个方向有效取决于 alignment ) |
| openExternalLinks | 是否允许打开一个外部的链接. (当 QLabel 文本内容包含 url 的时候涉及到) |
| buddy | 给 QLabel 关联一个 "伙伴" , 这样点击 QLabel 时就能激活对应的伙伴. 例如伙伴如果是一个 QCheckBox, 那么该 QCheckBox 就会被选中. |
4.1.1 代码示例1:显示不同格式的文本
在界面上创建三个 QLabel. 尺寸放大一些. objectName 分别为 label, label_2, label_3.

修改 widget.cpp, 设置三个 label 的属性
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 文本");
}
运行程序, 观察效果

4.1.2 代码示例2:显示图片
在界面上创建一个 QLabel, objectName 为 label.

创建 resource.qrc 文件, 并把图片导入到 qrc 中.

修改 widget.cpp, 给 QLabel 设置图片
cpp
// 设置 label 大小和窗口一样大
ui->label->setGeometry(0, 0, 800, 600);
QPixmap pixmap(":/huaji.png");
ui->label->setPixmap(pixmap);
执行程序, 观察效果

这个图片本身的尺寸是 480 * 480, 并没有把 QLabel 填充满.
修改代码, 设置 scaledContents 属性.
cpp
// 设置内容伸缩
ui->label->setScaledContents(true);
再次运行, 观察效果, 可以看到图片已经被拉伸, 可以把窗口填满了.

此时, 如果拖动窗口大小, 可以看到图片并不会随着窗口大小的改变而同步变化.

为了解决这个问题, 可以在 Widget 中重写 resizeEvent 函数.
cpp
// 重写 resizeEvent. 这个函数会在窗口大小发生改变时被自动调用.
void Widget::resizeEvent(QResizeEvent *event) {
// 可以直接通过 this->width() 和 this->height() 设置 label 新的尺寸, 也可以通过event 参数拿到新的尺寸.
// ui->label->setGeometry(0, 0, this->width(), this->height());
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
qDebug() << event->size();
}
执行程序, 此时改变窗口大小, 图片也会随之变化.

于此同时, 在控制台里也能够看到尺寸变化的过程.

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


QFrame 是 QLabel 的父类. 其中 frameShape 属性用来设置边框性质.
- QFrame::Box :矩形边框
- QFrame::Panel :带有可点击区域的面板边框
- QFrame::WinPanel :Windows风格的边框
- QFrame::HLine :水平线边框
- QFrame::VLine :垂直线边框
- QFrame::StyledPanel :带有可点击区域的面板边框,但样式取决于窗口主题
编写 widget.cpp, 给这四个 label 设置属性.
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 垂直水平居中
- 第二个 label 设置了 wordWrap, 能够自动换行
- 第三个 label 设置了 Indent, 左侧和上方和边框有间距. 右侧则没有.
- 第四个 label 设置了 margin, 四个方向均有间距(图上仅体现出三个方向, 下方看不出来).

4.1.4 代码示例4:设置伙伴
创建两个 label 和 两个 radioButton. objectName 分别问 label , label_2 , radioButton , radioButton_2.

此处把 label 中的文本设置为 "快捷键 &A" 这样的形式. 其中 & 后面跟着的字符, 就是快捷键. 可以通过 alt + A 的方式来触发该快捷键. 但是注意, 这里的快捷键和 QPushButton 的不同. 需要搭配 alt 和 单个字母的方式才能触发.
编写 widget.cpp, 设置 buddy 属性
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, 即可选中对应的选项.

4.2 QLCDNumer
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 | 设置比较小的小数点. |
4.2.1 代码示例1:倒计时
在界面上创建一个 QLCDNumber , 初始值设为 10. objectName 为 lcdNumber.

修改 widget.h 代码, 创建一个 QTimer 成员, 和一个 updateTime 函数.
cpp
QTimer* timer;
void updateTime();
修改 widget.cpp, 在构造函数中初始化 QTimer.
- QTimer 表示定时器. 通过 start 方法启动定时器之后, 就会每隔一定周期, 触发一次 QTimer::timeout 信号.
- 使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来, 意味着每次触发 QTimer::timeout 都会执行 Widget::updateTime
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 QTimer 实例
timer = new QTimer(this);
// 连接信号槽. QTimer 会每隔一定的时间触发一个 timeout 信号. 现在把 timeout 信号和 updateTime 连接起来.
// 此时意味着每次触发 timeout 信号都会伴随 updateTime 函数的执行.
connect(timer, &QTimer::timeout, this, &Widget::updateTime);
// 启动 QTimer, 并且规定每隔 1000ms 触发一次 timeout 信号.
timer->start(1000);
}
修改 widget.cpp, 实现 updateTime.
- 通过 intValue 获取到 QLCDNumber 内部的数值.
- 如果 value 的值归 0 了, 就停止 QTimer . 接下来 QTimer 也就不会触发 timeout 信号了.
cpp
void Widget::updateTime() {
qDebug() << "updateTime";
int value = ui->lcdNumber->intValue();
if (value <= 0) {
// 如果时间到, 停止定时器.
timer->stop();
return;
}
ui->lcdNumber->display(value - 1);
}
执行程序, 可以看到每隔一秒钟, 显示的数字就减少 1.

4.3 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 :表示总时间(以毫秒为单位) |
4.3.1 代码示例1:设置进度条按时间增长
在界面上创建进度条, objectName 为 progressBar.

其中最小值设为 0, 最大值设为 100. 当前值设为 0.
cpp
QTimer* timer;
void updateProgressBar ();
修改 widget.cpp, 初始化 QTimer. 此处设置 100ms 触发一次 timeout 信号. 也就是一秒钟触发 10 次.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Widget::updateProgressBar);
timer->start(100);
}
修改 widget.cpp, 实现 updateProgressBar.
cpp
void Widget::updateProgressBar() {
int value = ui->progressBar->value();
if (value >= 100) {
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
运行程序, 可以看到进度条中的进度在快速增长.

4.3.2 代码示例2:创建一个红色的进度条
上述的进度条使用绿色表示的, 但是考虑到有些人可能不喜欢绿色, 因此我们改成一个红色的进度条.
不要忘了, QProgressBar 同样也是 QWidget 的子类, 因此我们可以使用 styleSheet 通过样
式来修改进度条的颜色.

在 Qt Designer 右侧的属性编辑器中, 找到 QWidget 的 styleSheet 属性.
编辑如下内容:
-
其中的 chunk 是选中进度条中的每个 "块" . 使用 QProgressBar::text 则可以选中文本.
QProgressBar::chunk {background-1 color: #FF0000;}

同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.

执行程序, 可以看到如下效果. 我们就得到了一个红色的进度条.

4.4 CalendarWidget
QCalendarWidget 表示一个 "日历" , 形如:

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

给 QCalendarWidget 添加 slot 函数
cpp
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
qDebug() << date;
ui->label->setText(date.toString());
}
执行程序, 可以看到当选择不同的日期时, label 中的内容就会随之改变.

5. 输入类控件
5.1 QLineEdit
QLineEdit 用来表示单行输入框. 可以输入一段文本, 但是不能换行.
核心属性:
| 属性 | 说明 |
|---|---|
| text | 输入框中的文本 |
| inputMask | 输入内容格式约束 |
| maxLength | 最大长度 |
| frame | 是否添加边框 |
| echoMode | 显示方式. - QLineEdit::Normal :这是默认值,文本框会显示输入的文本。 - QLineEdit::Password :在这种模式下,输入的字符会被隐藏,通常用星号(*)或等号(=)代替。 - QLineEdit::NoEcho :在这种模式下,文本框不会显示任何输入的字符。 |
| cursorPosition | 光标所在位置 |
| alignment | 文字对齐方式, 设置水平和垂直方向的对齐. |
| dragEnabled | 是否允许拖拽 |
| readOnly | 是否是只读的(不允许修改) |
| placeHolderText | 当输入框内容为空的时候, 显示什么样的提示信息 |
| clearButtonEnabled | 是否会自动显示出 "清除按钮" . |
核心信号:
| 信号 | 说明 |
|---|---|
| void cursorPositionChanged(int old, int new) | 当鼠标移动时发出此信号,old为先前的位置,new为新位置。 |
| void editingFinished() | 当按返回或者回车键时,或者行编辑失去焦点时,发出此信号。 |
| void returnPressed() | 当返回或回车键按下时发出此信号. 如果设置了验证器, 必须要验证通过, 才能触发. |
| void selectionChanged() | 当选中的文本改变时,发出此信号。 |
| void textChanged(const QString &text) | 当QLineEdit中的文本改变时,发出此信号,text是新的文本。代码对文本的修改能够触发这个信号. |
| void textEdited(const QString &text)) | 当QLineEdit中的文本改变时,发出此信号,text是新的文本。代码对文本的修改不能触发这个信号. |
5.1.1 代码示例1:录入个人信息
在界面上创建三个输入框和两个单选按钮, 一个普通按钮. 三个输入框的 objectName 为 lineEdit_name , lineEdit_password , lineEdit_phone. 两个单选按钮的 objectName 为 radioButton_male , radioButton_female 按钮的 objectName 为 pushButton.

编写 widget.cpp, 在构造函数中编写初始化代码
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 初始化第一个输入框
ui->lineEdit_name->setPlaceholderText("请输入姓名");
ui->lineEdit_name->setClearButtonEnabled(true);
// 初始化第二个输入框
ui->lineEdit_password->setPlaceholderText("请输入密码");
ui->lineEdit_password->setClearButtonEnabled(true);
ui->lineEdit_password->setEchoMode(QLineEdit::Password);
// 初始化第三个输入框
ui->lineEdit_phone->setPlaceholderText("请输入电话号码");
ui->lineEdit_password->setClearButtonEnabled(true);
// 验证手机号码必须是 11 位数字. 并且按照 "344" 的格式来输入.
ui->lineEdit_phone->setInputMask("000-0000-0000");
}
继续修改 widget.cpp, 给按钮添加 slot 函数
cpp
void Widget::on_pushButton_clicked()
{
QString gender = ui->radioButton_male->isChecked() ? "男" : "女";
qDebug() << "姓名: " << ui->lineEdit_name->text() << "密码: " << ui->lineEdit_password->text() << "性别: " << gender << "手机: " << ui->lineEdit_phone->text();
}
执行程序, 可以看到, 随着用户输入内容之后, 点击按钮, 就能打印到输入的信息

5.1.2 代码示例2:使用正则表达式验证输入框的数据
此处要求在输入框中输入一个合法的电话号码(1 开头, 11 位, 全都是数字). 如果验证不通过, 则确定按钮无法点击.
在界面上创建输入框和一个按钮.

编写 widget.cpp, 把按钮初始 enabled 设为 false. 给输入框添加验证器.
- 使用 QRegExp 创建一个正则表达式对象. "^1\d{10}$" 表示 "以 1 开头, 后面跟上任意的10个十进制数字".
- 使用 QRegExpValidator 创建一个验证器对象. Qt 中内置了四个主要的验证器对象.

QRegularExpressionValidator 在匹配性能上做出了一定优化. 但是从使用角度讲, 和 QRegExpValidator 差别不大. 我们使用 QRegExpValidator 即可.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置按钮默认是禁用状态
ui->pushButton->setEnabled(false);
// 给 lineEdit 注册一个 validator
ui->lineEdit->setValidator(new QRegExpValidator(QRegExp("^1\\d{10}$")));
}
编写 widget.cpp, 给 lineEdit 添加 textEdited 信号的 slot 函数.
- on_lineEdit_textEdited 的参数是当前输入框的内容.
- 通过 lineEdit->validator() 获取到内置的验证器.
- 通过 validate 方法验证文本是否符合要求.
- 第一个参数填写的是要验证的字符串. 由于参数要求是 QString& 而不是 const
QString& , 需要把这个变量复制一下. - 第二个参数是一个 int&, 是输出型参数. 当验证的字符串不匹配时, 返回这个字符串的长度. (没有啥实质作用).
- 返回值是一个枚举. QValidator::Acceptable 表示验证通过, QValidator::Invalid 表示验证不通过.
- 第一个参数填写的是要验证的字符串. 由于参数要求是 QString& 而不是 const
cpp
void Widget::on_lineEdit_textEdited(const QString &arg1)
{
qDebug() << arg1;
QString content = arg1;
int pos = 0;
if (ui->lineEdit->validator()->validate(content, pos) == QValidator::Acceptable) {
// 验证通过, 设置按钮的可用状态为启用.
ui->pushButton->setEnabled(true);
} else {
// 验证不通过, 设置按钮的可用状态为禁用.
ui->pushButton->setEnabled(false);
}
}
执行程序, 观察效果. 可以看到此时尝试输入字母是无法输入的. 并且只有当输入的内容符合要求, 确定按钮才能被使用.

5.1.3 代码示例3:验证两次输入的密码一致
在界面上创建两个输入框和一个 label.

编写代码, 设置两个输入框的 echoMode 为 Password.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->lineEdit->setEchoMode(QLineEdit::Password);
ui->lineEdit_2->setEchoMode(QLineEdit::Password);
}
给两个输入框设置 textEdited slot 函数
cpp
void Widget::on_lineEdit_textEdited(const QString &arg1)
{
const QString& s1 = ui->lineEdit->text();
const QString& s2 = ui->lineEdit_2->text();
if (s1.isEmpty() && s2.isEmpty()) {
ui->label->setText("密码为空!");
} else if (s1 == s2) {
ui->label->setText("两次输入的密码相同!");
} else {
ui->label->setText("两次输入的密码不同!");
}
}
void Widget::on_lineEdit_2_textEdited(const QString &arg1)
{
const QString& s1 = ui->lineEdit->text();
const QString& s2 = ui->lineEdit_2->text();
if (s1.isEmpty() && s2.isEmpty()) {
ui->label->setText("密码为空!");
} else if (s1 == s2) {
ui->label->setText("两次输入的密码相同!");
} else {
ui->label->setText("两次输入的密码不同!");
}
}
执行程序, 观察效果.

5.2 QTextEdit
QTextEdit 表示多行输入框. 也是一个富文本 & markdown 编辑器.
并且能在内容超出编辑框范围时自动提供滚动条.
核心属性
| 属性 | 说明 |
|---|---|
| markdown | 输入框内持有的内容. 支持 markdown 格式. 能够自动的对markdown 文本进行渲染成 html |
| html | 输入框内持有的内容. 可以支持大部分 html 标签. 包括 img 和 table 等. |
| placeHolderText | 输入框为空时提示的内容. |
| readOnly | 是否是只读的 |
| undoRedoEnable | 是否开启 undo / redo 功能. 按下 ctrl + z(撤销) 触发 undo. 按下 ctrl + y(重做,也就是撤回上一次的撤销) 触发 redo |
| autoFormating | 开启自动格式化. |
| tabstopWidth | 按下缩进占多少空间 |
| overwriteMode | 是否开启覆盖写模式 |
| acceptRichText | 是否接收富文本内容 |
| verticalScrollBarPolicy | 垂直方向滚动条的出现策略: - Qt::ScrollBarAsNeeded : 根据内容自动决定是否需要滚动条。这是默认值。 - Qt::ScrollBarAlwaysOff : 总是关闭滚动条。 - Qt::ScrollBarAlwaysOn : 总是显示滚动条。 |
| horizontalScrollBarPolicy | 水平方向滚动条的出现策略,与verticalScrollBarPolicy相同。 |
核心信号
| 信号 | 说明 |
|---|---|
| textChanged() | 文本内容改变时触发 |
| selectionChanged() | 选中范围改变时触发 |
| cursorPositionChanged() | 光标移动时触发 |
| undoAvailable(bool) | 可以进行 undo 操作时触发 |
| redoAvailable(bool) | 可以进行 redo 操作时触发 |
| copyAvaiable(bool) | 文本被选中/取消选中时触发 |
5.2.1 代码示例1:获取多行输入框的内容
创建一个多行输入框和一个label

给多行输入框添加 slot 函数. 处理 textChanged 信号.
- 通过 toPlainText 方法获取到内部的文本.
- 类似的, QTextEdit 还提供了 toMarkdown 和 toHtml . 根据需要我们调整不同的获取方式.
cpp
void Widget::on_textEdit_textChanged()
{
const QString& content = ui->textEdit->toPlainText();
qDebug() << content;
ui->label->setText(content);
}
执行程序, 可以看到当输入框中的内容发生变化时, label 中的内容同步发生改变.

5.3 QVomboBox
QComboBox 表示下拉框.
核心属性
| 属性 | 说明 |
|---|---|
| currentText | 当前选中的文本 |
| currentIndex | 当前选中的条目下标. 从 0 开始计算. 如果当前没有条目被选中, 值为 -1. |
| editable | 是否允许修改. 设为 true 时, QComboBox 的行为就非常接近 QLineEdit , 也可以设置 validator |
| iconSize | 下拉框图标 (小三角) 的大小 |
| maxCount | 最多允许有多少个条目 |
核心方法
| 方法 | 说明 |
|---|---|
| addItem(const QString&) | 添加一个条目 |
| currentIndex() | 获取当前条目的下标. 从 0 开始计算. 如果当前没有条目被选中, 值为 -1 |
| currentText() | 获取当前条目的文本内容. |
核心信号
| 信号 | 说明 |
|---|---|
| activated(int) activated(const QString & text) | 当用户选择了一个选项时发出. 这个时候相当于用户点开下拉框, 并且鼠标划过某个选项. 此时还没有确认做出选择. |
| currentIndexChanged(int) currentIndexChanged(const QString & text) | 当前选项改变时发出. 此时用户已经明确的选择了一个选项. 用户操作或者通过程序操作都会触发这个信号. |
| editTextChanged(const QString & text) | 当编辑框中的文本改变时发出(editable 为 true 时有效) |
5.3.1 代码示例1:使用下拉框模拟点餐
在界面上创建三个下拉框, 和一个按钮.

编写 widget.cpp, 初始化三个下拉框的内容
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->comboBox->addItem("巨无霸");
ui->comboBox->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("麦辣鸡翅");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
}
编写 widget.cpp, 给按钮添加 slot 函数
cpp
void Widget::on_pushButton_clicked()
{
qDebug() << "汉堡选择: " << ui->comboBox->currentText();
qDebug() << "小食选择: " << ui->comboBox_2->currentText();
qDebug() << "饮料选择: " << ui->comboBox_3->currentText();
}
执行程序, 可以看到, 在点击确定按钮时, 就能获取到当前下拉框中选中的内容.

5.4 QSpinBox
使用 QSpinBox 或者 QDoubleSpinBox 表示 "微调框", 它是带有按钮的输入框. 可以用来输入整数/浮点数. 通过点击按钮来修改数值大小.
由于 SpinBox 和 QDoubleSpinBox 用法基本相同, 就只介绍 SpinBox 的使用了.

QSpinBox 关键属性
| 属性 | 说明 |
|---|---|
| value | 存储的数值. |
| singleStep | 每次调整的 "步长". 按下一次按钮数据变化多少. |
| displayInteger | 数字的进制, 如2为二进制, 以此类推. |
| minimum | 最小值 |
| maximum | 最大值 |
| suffix | 后缀 |
| prefix | 前缀 |
| wrapping | 是否允许换行 |
| frame | 是否带边框 |
| alignment | 文字对齐方式. |
| readOnly | 是否允许修改 |
| buttonSymbol | 按钮上的图标. - UpDownArrows 上下箭头形式 - PlusMinus 加减号形式 - NoButtons 没有按钮 |
| accelerated | 按下按钮时是否为快速调整模式 |
| correctionMode | 输入有误时如何修正 - QAbstractSpinBox::CorrectToPreviousValue : 如果用户输入了一个无效的值, SpinBox会恢复为上一个有效值 - QAbstractSpinBox::CorrectToNearestValue : 如果用户输入了一个无效的值, SpinBox会恢复为最接近的有效值 |
| keyboardTrack | 是否开启键盘跟踪. 设为 true, 每次在输入框输入一个数字, 都会触发一次 valueChanged() 和 textd() 信号. 设为 false, 只有在最终按下 enter 或者输入框失去焦点, 才会触发 valueChanged() 和 textChanged() 信号. |
核心信号
| 信号 | 说明 |
|---|---|
| textChanged(QString) | 微调框的文本发生改变时会触发. 参数 QString 带有 前缀 和 后缀. |
| valueChanged(int) | 微调框的文本发生改变时会触发. 参数 int, 表示当前的数值. |
5.4.1 代码示例1:调整购物车中的份数
在界面上创建三个下拉框: objectName 为 comboBox 到 comboBox_3, 三个微调框: objectName 为 spinBox 到 spinBox_3, 一个按钮: objectName 为 pushButton.

编写代码, 修改 widget.cpp, 给下拉框设置初始值.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 初始化下拉框
ui->comboBox->addItem("巨无霸");
ui->comboBox->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("麦辣鸡翅");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
// 初始化微调框
ui->spinBox->setValue(1);
ui->spinBox->setRange(1, 5);
ui->spinBox_2->setValue(1);
ui->spinBox_2->setRange(1, 5);
ui->spinBox_3->setValue(1);
ui->spinBox_3->setRange(1, 5);
}
编写代码, 给按钮添加 slot 函数
cpp
void Widget::on_pushButton_clicked()
{
qDebug() << "当前下单的内容: " << ui->comboBox->currentText() << ": " << ui->spinBox->value() << ui->comboBox_2->currentText() << ": " << ui->spinBox_2->value() << ui->comboBox_3->currentText() << ": " << ui->spinBox_3->value();
}
执行程序, 可以看到当用户选择不同的内容时, 点击按钮就能获取到对应的结果. 同时我们也无法输入一些超出范围的非法值.

5.5 QDateEdit & QTimeEdit
使用 QDateEdit 作为日期的微调框.

使用 QTimeEdit 作为时间的微调框.

使用 QDateTimeEdit 作为时间日期的微调框.

这几个控件用法非常相似, 我们以 QDateTimeEdit 为例进行介绍.
QDateTimeEdit 核心属性
| 属性 | 说明 |
|---|---|
| dateTime | 时间日期的值. 形如 2000/1/1 0:00:00 |
| date | 单纯日期的值. 形如 2001/1/1 |
| time | 单纯时间的值. 形如 0:00:00 |
| displayFormat | 时间日期格式. 形如 yyyy/MM/dd HH:mm - y 表示年份 - M 表示月份 - d 表示日期 - H 表示小时 - m 表示分钟 - s 表示秒 |
| minimumDateTime | 最小时间日期 |
| maximumDateTime | 最大时间日期 |
| timeSpec | - Qt::LocalTime :显示本地时间。 - Qt::UTC :显示协调世界时(UTC)。 - Qt::OffsetFromUTC :显示相对于UTC的偏移量(时差). |
核心信号
| 信号 | 说明 |
|---|---|
| dateChanged(QDate) | 日期改变时触发. |
| timeChanged(QTime) | 时间改变时触发. |
| dateTimeChanged(QDateTime) | 时间日期任意一个改变时触发. |
5.5.1 代码示例1:实现日期计算器
在界面上创建两个 QDateTimeEdit 和一个按钮, 一个 label. QDateTimeEdit 的 objectName 为 dateTimeEdit_old 和 dateTimeEdit_new.

编写计算按钮的 slot 函数.
cpp
void Widget::on_pushButton_clicked()
{
// 获取到两个时间框的时间日期
QDateTime timeOld = ui->dateTimeEdit_old->dateTime();
QDateTime timeNew = ui->dateTimeEdit_new->dateTime();
// 计算日期差值
int days = timeOld.daysTo(timeNew);
int hours = (timeOld.secsTo(timeNew) / 3600) % 24;
// 设置 label 的内容
QString text = QString("爱你已经持续了 ") + QString::number(days) + QString(" 天 零 ") + QString::number(hours) + QString(" 个小时!");
ui->label->setText(text);
}
执行程序, 观察结果

5.6 QDial
使用 QDial 表示一个 旋钮.

核心属性
| 属性 | 说明 |
|---|---|
| value | 持有的数值. |
| minimum | 最小值 |
| maximum | 最大值 |
| singleStep | 按下方向键的时候改变的步长. |
| pageStep | 按下 pageUp / pageDown 的时候改变的步长. |
| sliderPosition | 界面上旋钮显示的 初始位置 |
| tracking | 外观是否会跟踪数值变化. 默认值为 true. 一般不需要修改. |
| wrapping | 是否允许循环调整. 即数值如果超过最大值, 是否允许回到最小值. (调整过程能否 "套圈") |
| notchesVisible | 是否显示 刻度线 |
| notchTarget | 刻度线之间的相对位置. 数字越大, 刻度线越稀疏. |
核心信号
| 信号 | 说明 |
|---|---|
| valueChanged(int) | 数值改变时触发 |
| rangeChanged(int, int) | 范围变化时触发 |
5.6.1 代码示例1:调整窗口透明度
在界面上创建一个旋钮和一个 label

编写 widget.cpp, 对旋钮初始化
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置可以循环旋转
ui->dial->setWrapping(true);
// 设置刻度线可见
ui->dial->setNotchesVisible(true);
// 设置最大值为
ui->dial->setMaximum(100);
// 设置最小值为
ui->dial->setMinimum(0);
// 设置初始值为
ui->dial->setValue(100);
}
编写 widget.cpp, 设置旋钮的 valueChanged slot 函数
cpp
void Widget::on_dial_valueChanged(int value)
{
ui->label->setText(QString("当前不透明度为: ") + QString::number(value));
this->setWindowOpacity((double)value / 100);
}
运行程序, 观察效果. 可以看到随着拖动旋钮旋转, 不透明度发生明显变化.

5.7 QSlider
使用 QSlider 表示一个滑动条.

QSlider 和 QDial 都是继承自 QAbstractSlider , 因此用法上基本相同.
核心属性
| 属性 | 说明 |
|---|---|
| value | 持有的数值. |
| minimum | 最小值 |
| maximum | 最大值 |
| singleStep | 按下方向键的时候改变的步长. |
| pageStep | 按下 pageUp / pageDown 的时候改变的步长. |
| sliderPosition | 界面上旋钮显示的 初始位置 |
| tracking | 外观是否会跟踪数值变化. 默认值为 true. 一般不需要修改. |
| orientation | 滑动条的方向是水平还是垂直 |
| invertedAppearance | 是否要翻转滑动条的方向 |
| tickPosition | 刻度的位置. |
| tickInterval | 刻度的密集程度. |
核心信号
| 信号 | 说明 |
|---|---|
| valueChanged(int) | 数值改变时触发 |
| rangeChanged(int, int) | 范围变化时触发 |
5.7.1 代码示例1:调整窗口大小
在界面上创建两个滑动条, 分别是水平和垂直滑动条. objectName 分别为 horizontalSlider 和 verticalSlider.

编写代码初始化滑动条
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->horizontalSlider->setMinimum(500);
ui->horizontalSlider->setMaximum(2000);
ui->horizontalSlider->setSingleStep(100);
ui->horizontalSlider->setValue(800);
ui->verticalSlider->setMinimum(500);
ui->verticalSlider->setMaximum(1500);
ui->verticalSlider->setSingleStep(100);
ui->verticalSlider->setValue(600);
// 翻转朝向, 默认滑块从下向上增长, 改成从上往下增长.
ui->verticalSlider->setInvertedAppearance(true);
}
编写滑动条的 valueChanged slot 函数
cpp
void Widget::on_horizontalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), value, rect.height());
qDebug() << value;
}
void Widget::on_verticalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), rect.width(), value);
qDebug() << value;
}
执行程序, 可以看到调整滑动条, 窗口大小就会随之改变.

5.7.2 代码示例2:通过自定义快捷键调整滑动条位置

编写初始化代码
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setText("");
ui->horizontalSlider->setMinimum(0);
ui->horizontalSlider->setMaximum(100);
ui->horizontalSlider->setSingleStep(10);
ui->horizontalSlider->setValue(0);
}
创建 valueChanged 的 slot 函数
cpp
void Widget::on_horizontalSlider_valueChanged(int value)
{
ui->label->setText(QString::number(value));
}
修改 widget.cpp 构造函数, 增加快捷键
- 使用 QShortCut 类设置快捷键.
- 快捷键触发时, 会发出 QShortcut::activated 信号, 我们连接到自己写的 slot 函数.
cpp
// 设置快捷键
QShortcut* shortCut1 = new QShortcut(this);
shortCut1->setKey(QKeySequence("-"));
connect(shortCut1, &QShortcut::activated, this, &Widget::subValue);
QShortcut* shortCut2 = new QShortcut(this);
shortCut2->setKey(QKeySequence("="));
connect(shortCut2, &QShortcut::activated, this, &Widget::addValue);
编写自定义 slot 函数
cpp
void Widget::subValue() {
int value = ui->horizontalSlider->value();
ui->horizontalSlider->setValue(value - 20);
}
void Widget::addValue() {
int value = ui->horizontalSlider->value();
ui->horizontalSlider->setValue(value + 20);
}
执行程序, 观察效果. 可以看到此时按下 - 和 = 就可以调整 value 的值了.

6. 多元素控件
Qt 中提供的多元素控件有:
- QListWidget
- QListView
- QTableWidget
- QTableView
- QTreeWidget
- QTreeView
xxWidget和xxView的区别(以QTableWidget和QTableView为例):
- QTableView 是基于 MVC 设计的控件. QTableView 自身不持有数据. 使用 QTableView 的时候需要用户创建一个 Model 对象 (比如 QStandardModel ), 并且把 Model 和 QTableView 关联起来. 后续修改 Model 中的数据就会影响 QTableView 的显示; 修改 QTableView 的显示也会影响到 Model 中的数据(双向绑定).
- QTableWidget 则是 QTableView 的子类, 对 Model 进行了封装. 不需要用户手动创建 Model 对象, 直接就可以往 QTableWidget 中添加数据了.
6.1 QListWidget
使用 QListWidget 能够显示一个纵向的列表. 形如:

每个选项都可以被选中.
核心属性
| 属性 | 说明 |
|---|---|
| currentRow | 当前被选中的是第几行 |
| count | 一共有多少行 |
| sortingEnabled | 是否允许排序 |
| isWrapping | 是否允许换行 |
| itemAlignment | 元素的对齐方式 |
| selectRectVisible | 被选中的元素矩形是否可见 |
| spacing | 元素之间的间隔 |
核心方法
| 方法 | 说明 |
|---|---|
| addItem(const QString& label) addItem(QListWidgetItem *item) | 列表中添加元素. |
| currentItem() | 返回 QListWidgetItem* 表示当前选中的元素 |
| setCurrentItem(QListWidgetItem* item) | 设置选中哪个元素 |
| setCurrentRow(int row) | 设置选中第几行的元素 |
| insertItem(const QString& label, int row) insertItem(QListWidgetItem *item, int row) | 在指定的位置插入元素 |
| item(int row) | 返回 QListWidgetItem* 表示第 row 行的元素 |
| takeItem(int row) | 删除指定行的元素, 返回 QListWidgetItem* 表示是哪个元素被删除了 |
核心信号
| 方法 | 说明 |
|---|---|
| currentItemChanged(QListWidgetItem* current, QListWidgetItem* old) | 选中不同元素时会触发. 参数是当前选中的元素和之前选中的元素. |
| currentRowChanged(int) | 选中不同元素时会触发. 参数是当前选中元素的行数. |
| itemClicked(QListWidgetItem* item) | 点击某个元素时触发 |
| itemDoubleClicked(QListWidgetItem* item) | 双击某个元素时触发 |
| itemEntered(QListWidgetItem* item) | 鼠标进入元素时触发 |
在上述介绍中, 涉及到一个关键的类, QListWidgetItem .
这个类表示 QListWidget 中的一个元素.
核心方法如下, 本质上就是一个 "文本+图标" 构成的.
| 方法 | 说明 |
|---|---|
| setFont | 设置字体 |
| setIcon | 设置图标 |
| setHidden | 设置隐藏 |
| setSizeHint | 设置尺寸 |
| setSelected | 设置是否选中 |
| setText | 设置文本 |
| setTextAlignment | 设置文本对齐方式 |
6.1.1 代码示例1:使用QListWidget
在界面上创建一个 ListView , 右键 => 变形为 => ListWidget , 再创建一个 lineEdit 和 两个按钮.
ListWidget 是 ListView 的子类, 功能比 ListView 更丰富. 咱们使用 ListWidget 即可.

编写 widget.cpp, 在构造函数中添加初始元素
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->listWidget->addItem("C++");
ui->listWidget->addItem("Java");
ui->listWidget->addItem("Python");
}
编写 listWidget 的 slot 函数
此处需要判定 current 和 previous 非空. 初始情况下是没有元素选中的, 就导致这俩指针可
能是 NULL.
cpp
void Widget::on_listWidget_currentItemChanged(QListWidgetItem *current,
QListWidgetItem *previous)
{
if (current != NULL && previous != NULL) {
qDebug() << "当前选中: " << current->text() << "之前选中: " << previous->text();
}
}
这里需要给 widget.h 前面加上 #include <QListWidgetItem>
编写按钮的 slot 函数
cpp
void Widget::on_pushButton_clicked()
{
// 获取到输入框的内容
const QString& text = ui->lineEdit->text();
if (text.isEmpty()) {
return;
}
ui->listWidget->addItem(text);
}
void Widget::on_pushButton_2_clicked()
{
// 获取当前被选中的元素
int row = ui->listWidget->currentRow();
// 删除这一行
ui->listWidget->takeItem(row);
}
执行程序, 观察效果. 可以新增元素, 选中元素, 删除元素.

6.2 QTableWidget
使用 QTableWidget 表示一个表格控件. 一个表格中包含若干行, 每一行又包含若干列. 表格中的每个单元格, 是一个 QTableWidgetItem 对象.
QTableWidget 核心方法
| 方法 | 说明 |
|---|---|
| item(int row, int column) | 根据行数列数获取指定的 QTableWidgetItem* |
| setItem(int row, int column, QTableWidget*) | 根据行数列数设置表格中的元素 |
| currentItem() | 返回被选中的元素 QTableWidgetItem* |
| currentRow() | 返回被选中元素是第几行 |
| currentColumn() | 返回被选中元素是第几列 |
| row(QTableWidgetItem* ) | 获取指定 item 是第几行 |
| column(QTableWidgetItem* ) | 获取指定 item 是第几列 |
| rowCount() | 获取行数 |
| columnCount() | 获取列数 |
| insertRow(int row) | 在第 row 行处插入新行 |
| insertColumn(int column) | 在第 column 列插入新列 |
| removeRow(int row) | 删除第 row 行 |
| removeColumn(int column) | 删除第 column 列 |
| etHorizontalHeaderItem(int column, QTableWidget*) | 设置指定列的表头 |
| setVerticalHeaderItem(int row, QTableWidget*) | 设置指定行的表头 |
QTableWidgetItem 核心信号
| 信号 | 说明 |
|---|---|
| cellClicked(int row, int column) | 点击单元格时触发 |
| cellDoubleClicked(int row, int column) | 双击单元格时触发 |
| cellEntered(int row, int column) | 鼠标进入单元格时触发 |
| currentCellChanged(int row, int column, int previousRow, int previousColumn) | 选中不同单元格时触发 |
QTableWidgetItem 核心方法
| 方法 | 说明 |
|---|---|
| row() | 获取当前是第几行获取当前是第几行 |
| column() | 获取当前是第几列 |
| setText(const QString&) | 设置文本 |
| setTextAlignment(int) | 设置文本对齐 |
| setIcon(const QIcon&) | 设置图标 |
| setSelected(bool) | 设置被选中 |
| setSizeHints(const QSize&) | 设置尺寸 |
| setFont(const QFont&) | 设置字体 |
6.2.1 代码示例1:使用 QTableWidget
在界面上创建 QTableWidget 和 三个按钮, 一个输入框
注意: QTableWidget 是 QTableView 的子类, 功能比 QTableView 更丰富. 咱们使用 QTableWidget 即可.

编写 widget.cpp 构造函数, 构造表格中的初始数据.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 3 行
ui->tableWidget->insertRow(0);
ui->tableWidget->insertRow(1);
ui->tableWidget->insertRow(2);
// 创建 3 列
ui->tableWidget->insertColumn(0);
ui->tableWidget->insertColumn(1);
ui->tableWidget->insertColumn(2);
// 给 3 列设定列名
ui->tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("学号"));
ui->tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("姓名"));
ui->tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("年龄"));
// 设置初始数据
ui->tableWidget->setItem(0, 0, new QTableWidgetItem("1001"));
ui->tableWidget->setItem(0, 1, new QTableWidgetItem("张三"));
ui->tableWidget->setItem(0, 2, new QTableWidgetItem("20"));
ui->tableWidget->setItem(1, 0, new QTableWidgetItem("1002"));
ui->tableWidget->setItem(1, 1, new QTableWidgetItem("李四"));
ui->tableWidget->setItem(1, 2, new QTableWidgetItem("21"));
ui->tableWidget->setItem(2, 0, new QTableWidgetItem("1003"));
ui->tableWidget->setItem(2, 1, new QTableWidgetItem("王五"));
ui->tableWidget->setItem(2, 2, new QTableWidgetItem("19"));
}
编写按钮的 slot 函数
cpp
void Widget::on_pushButton_addRow_clicked()
{
// 1. 获取到行数
int rowCount = ui->tableWidget->rowCount();
// 2. 插入新行
ui->tableWidget->insertRow(rowCount);
}
void Widget::on_pushButton_delRow_clicked()
{
// 1. 获取选中的行号
int curRow = ui->tableWidget->currentRow();
// 2. 删除对应行
ui->tableWidget->removeRow(curRow);
}
void Widget::on_pushButton_addCol_clicked()
{
// 1. 获取到列数
int colCount = ui->tableWidget->columnCount();
// 2. 插入新列
ui->tableWidget->insertColumn(colCount);
// 3. 设置列名
const QString& name = ui->lineEdit->text();
ui->tableWidget->setHorizontalHeaderItem(colCount, new
QTableWidgetItem(name));
}
void Widget::on_pushButton_delCol_clicked()
{
// 1. 获取选中的列号
int curCol = ui->tableWidget->currentColumn();
// 2. 删除对应的列
ui->tableWidget->removeColumn(curCol);
}
执行程序, 即可完成表格的基本操作.

默认情况下, 单元格中的内容直接就是可编辑的.
如果不想让用户编辑, 可以设置 ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
6.3 QTreeWidget
使用 QTreeWidget 表示一个树形控件. 里面的每个元素, 都是一个 QTreeWidgetItem , 每个 QTreeWidgetItem 可以包含多个文本和图标, 每个文本/图标为一个列.
可以给 QTreeWidget 设置顶层节点(顶层节点可以有多个), 然后再给顶层节点添加子节点, 从而构成树形结构.
QTreeWidget 核心方法
| 方法 | 说明 |
|---|---|
| clear | 清空所有子节点 |
| addTopLevelItem(QTreeWidgetItem* item) | 新增顶层节点 |
| topLevelItem(int index) | 获取指定下标的顶层节点. |
| topLevelItemCount() | 获取顶层节点个数 |
| indexOfTopLevelItem(QTreeWidgetItem* item) | 查询指定节点是顶层节点中的下标 |
| takeTopLevelItem(int index) | 删除指定的顶层节点. 返回 QTreeWidgetItem* 表示被删除的元素 |
| currentItem() | 获取到当前选中的节点, 返回 QTreeWidgetItem* |
| setCurrentItem(QTreeWidgetItem* item) | 选中指定节点 |
| setExpanded(bool) | 展开/关闭节点 |
| setHeaderLabel(const QString& text) | 设置 TreeWidget 的 header 名称. |
QTreeWidget 核心信号
| 信号 | 说明 |
|---|---|
| currentItemChanged(QTreeWidgetItem current, QTreeWidgetItem* old) | 切换选中元素时触发 |
| itemClicked(QTreeWidgetItem* item, int col) | 点击元素时触发 |
| itemDoubleClicked(QTreeWidgetItem* item, int col) | 双击元素时触发 |
| itemEntered(QTreeWidgetItem* item, int col) | 鼠标进入时触发 |
| itemExpanded(QTreeWidgetItem* item) | 元素被展开时触发 |
| itemCollapsend(QTreeWidgetItem* item) | 元素被折叠时触发 |
QTreeWidgetItem 核心属性
| 属性 | 说明 |
|---|---|
| text | 持有的文本 |
| textAlignment | 文本对齐方式 |
| icon | 持有的图表 |
| font | 文本字体 |
| hidden | 是否隐藏 |
| disabled | 是否禁用 |
| expand | 是否展开 |
| sizeHint | 尺寸大小 |
| selected | 是否选中 |
QTreeWidgetItem 核心方法
| 方法 | 说明 |
|---|---|
| addChild(QTreeWidgetItem* child) | 新增子节点 |
| childCount() | 子节点的个数 |
| child(int index) | 获取指定下标的子节点. 返回 QTreeWidgetItem* |
| takeChild(int index) | 删除对应下标的子节点 |
| removeChild(QTreeWidgetItem* child) | 删除对应的子节点 |
| parent() | 获取该元素的父节点 |
6.3.1 代码示例1:使用 QTreeWidget
在界面上创建一个 TreeView , 右键 => 变形为 => TreeWidget , 再创建一个 lineEdit 和 两个按钮.
注意: TreeWidget 是 TreeView 的子类, 功能比 TreeView 更丰富. 咱们使用 TreeWidget 即可.

编写代码, 构造初始数据
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setHeaderLabel("动物");
QTreeWidgetItem* item1 = new QTreeWidgetItem();
item1->setText(0, "猫");
ui->treeWidget->addTopLevelItem(item1);
QTreeWidgetItem* item2 = new QTreeWidgetItem();
item2->setText(0, "狗");
ui->treeWidget->addTopLevelItem(item2);
QTreeWidgetItem* item3 = new QTreeWidgetItem();
item3->setText(0, "鸟");
ui->treeWidget->addTopLevelItem(item3);
}
编写代码, 实现按钮的 slot 函数
cpp
void Widget::on_pushButton_clicked()
{
// 获取输入框内容
const QString& text = ui->lineEdit->text();
if (text.isEmpty()) {
return;
}
// 添加到顶层节点中
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0, text);
ui->treeWidget->addTopLevelItem(item);
}
void Widget::on_pushButton_2_clicked()
{
// 获取输入框内容
const QString& text = ui->lineEdit->text();
if (text.isEmpty()) {
return;
}
// 获取到当前选中的节点
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if (currentItem == NULL) {
return;
}
// 构造新的 item
QTreeWidgetItem* newItem = new QTreeWidgetItem();
newItem->setText(0, text);
// 添加 item 到选中节点
currentItem->addChild(newItem);
// 展开父节点
currentItem->setExpanded(true);
}
void Widget::on_pushButton_3_clicked()
{
// 获取到当前选中的节点
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if (currentItem == NULL) {
return;
}
// 获取当前节点的父节点
QTreeWidgetItem* parent = currentItem->parent();
if (parent == NULL) {
// 顶层节点
int index = ui->treeWidget->indexOfTopLevelItem(currentItem);
ui->treeWidget->takeTopLevelItem(index);
} else {
// 非顶层节点
parent->removeChild(currentItem);
}
}
7. 容器类控件
7.1 QGroupBox
使用 QGroupBox 实现一个带有标题的分组框. 可以把其他的控件放到里面作为一组. 这样看起来能更好看一点.

核心属性
| 属性 | 说明 |
|---|---|
| title | 分组框的标题 |
| alignment | 分组框内部内容的对齐方式 |
| flat | 是否是 "扁平" 模式 |
| checkable | 是否可选择. 设为 true, 则在 title 前方会多出一个可勾选的部分. |
| checked | 描述分组框的选择状态 (前提是 checkable 为 true) |
分组框只是一个用来 "美化界面" 这样的组件, 并不涉及到用户交互和业务逻辑. 属于 "锦上添花" .
7.1.1 代码示例1:为5.4.1案例加上分组框
在界面上创建三个分组框, 并且在分组框内部创建下拉框和微调框.

在复制粘贴控件的时候, 一定要先选中对应的父控件, 再粘贴.
编写 widget.cpp, 添加初始化下拉框的代码
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->comboBox->addItem("巨无霸");
ui->comboBox->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("麦辣鸡翅");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
}
运行程序, 观察效果

7.2 QTabWidget
使用 QTabWidget 实现一个带有标签页的控件, 可以往里面添加一些 widget. 进一步的就可以通过标签页来切换.
核心属性
| 属性 | 说明 |
|---|---|
| tabPosition | 标签页所在的位置: - North 上方 - South 下方 - West 左侧 - East 右侧 |
| currentIndex | 当前选中了第几个标签页 (从 0 开始计算) |
| currentTabText | 当前选中的标签页的文本 |
| currentTabName | 当前选中的标签页的名字 |
| currentTabIcon | 当前选中的标签页的图标 |
| currentTabToolTip | 当前选中的标签页的提示信息 |
| tabsCloseable | 标签页是否可以关闭 |
| movable | 标签页是否可以移动 |
核心信号
| 信号 | 说明 |
|---|---|
| currentChanged(int) | 在标签页发生切换时触发, 参数为被点击的选项卡编号. |
| tabBarClicked(int) | 在点击选项卡的标签条的时候触发. 参数为被点击的选项卡编号. |
| tabBarDoubleClicked(int) | 在双击选项卡的标签条的时候触发. 参数为被点击的选项卡编号. |
| tabCloseRequest(int) | 在标签页关闭时触发. 参数为被关闭的选项卡编号. |
7.2.1 代码示例1:使用标签页管理多组控件
在界面上创建一个 QTabWidget , 和两个按钮. 按钮的 objectName 为 pushButton_add 和 pushButton_remove

注意:
- QTabWidget 中的每个标签页都是一个 QWidget
- 点击标签页, 就可以直接切换.
- 右键 QTabWidget , 可以添加标签页或者删除标签页.
编写 widget.cpp, 进行初始化, 给标签页中放个简单的 label
- 注意新创建的 label 的父元素, 是 ui->tab 和 ui->tab_2 . Qt 中使用父子关系决定该控件 "在哪里".
cpp
QLabel* label = new QLabel(ui->tab);
label->setText("标签页1");
label->resize(100, 50);
QLabel* label2 = new QLabel(ui->tab_2);
label2->setText("标签页2");
label2->resize(100, 50);
编写按钮的 slot 函数
- 使用 count() 获取到标签页的个数.
- 使用 addTab 新增标签页.
- 使用 removeTab 删除标签页.
- 使用 currentIndex 获取到当前标签页的下标.
- 使用 setCurrentIndex 切换当前标签页.
cpp
void Widget::on_pushButton_add_clicked()
{
// 获取当前有几个标签页了
int count = ui->tabWidget->count();
// 创建新的 widget
QWidget* w = new QWidget();
ui->tabWidget->addTab(w, QString("Tab ") + QString::number(count + 1));
// 给 widget 中添加 label
QLabel* label = new QLabel(w);
label->setText(QString("标签页") + QString::number(count + 1));
label->resize(100, 50);
// 选中这个新的标签页
ui->tabWidget->setCurrentIndex(count);
}
void Widget::on_pushButton_remove_clicked()
{
// 获取当前标签页的下标
int index = ui->tabWidget->currentIndex();
// 删除这个标签页
ui->tabWidget->removeTab(index);
}
编写 QTabWidget 的 currentChanged 函数
cpp
void Widget::on_tabWidget_currentChanged(int index)
{
qDebug() << "当前选中标签页为: " << index;
}
运行程序, 观察效果
- 点击新建标签页, 可以创建出新的标签.
- 点击删除当前标签页, 可以删除标签.
- 切换标签页时, 可以看到 qDebug 打印出的标签页编号.

8. 布局管理器
之前使用 Qt 在界面上创建的控件, 都是通过 "绝对定位" 的方式来设定的.
也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move 方式摆放过去.
这种设定方式其实并不方便. 尤其是界面如果内容比较多, 不好计算. 而且一个窗口大小往往是可以调整的, 按照绝对定位的方式, 也无法自适应窗口大小.
因此 Qt 引入 "布局管理器" (Layout) 机制, 来解决上述问题.
8.1 垂直布局
使用 QVBoxLayout 表示垂直的布局管理器. V 是 vertical(垂直)的缩写.
核心属性
| 属性 | 说明 |
|---|---|
| layoutLeftMargin | 左侧边距 |
| layoutRightMargin | 右侧边距 |
| layoutTopMargin | 上方边距 |
| layoutBottomMargin | 下方边距 |
| layoutSpacing | 相邻元素之间的间距 |
Layout 只是用于界面布局, 并没有提供信号.
8.1.1 代码示例1: 使用 QVBoxLayout 管理多个控件.
编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.
使用 addWidget 把控件添加到布局管理器中.
使用 setLayout 设置该布局管理器到 widget 中.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
// 创建布局管理器, 并且把按钮添加进去
// 如果创建的时候指定父元素为 this, 则后面不需要 setLayout 方法了.
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);
// 把布局管理器设置到 widget 中
this->setLayout(layout);
}
运行程序, 可以看到此时界面上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发生改变.
此时三个按钮的尺寸和位置, 都是自动计算出来的.

通过上述代码的方式, 只能给这个 widget 设定一个布局管理器. 实际上也可以通过 Qt Design 在一个窗口中创建多个布局管理器.
8.1.2 代码示例2: 创建两个 QVBoxLayout
在界面上创建两个 QVBoxLayout , 每个 QVBoxLayout 各放三个按钮.

运行程序, 可以看到这些按钮已经自动排列好. 只不过当前这些按钮的位置不能随着窗口大小自动变化.

8.2 水平布局
使用 QHBoxLayout 表示垂直的布局管理器. H 是 horizontal(水平)的缩写.
核心属性 (和 QVBoxLayout 属性是一致的)
| 属性 | 说明 |
|---|---|
| layoutLeftMargin | 左侧边距 |
| layoutRightMargin | 右侧边距 |
| layoutTopMargin | 上方边距 |
| layoutBottomMargin | 下方边距 |
| layoutSpacing | 相邻元素之间的间距 |
8.2.1 代码示例1: 使用 QHBoxLayout 管理控件
编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
// 创建布局管理器
QHBoxLayout* layout = new QHBoxLayout();
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);
// 设置 layout 到 widget 上
this->setLayout(layout);
}
运行程序, 可以看到此时界面上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发生改变.

Layout 里面可以再嵌套上其他的 layout, 从而达到更复杂的布局效果.
8.2.2 代码示例2: 嵌套的 layout
在代码中创建以下内容
使用 addLayout 给 layout 中添加子 layout.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建顶层 layout
QVBoxLayout* layoutParent = new QVBoxLayout();
this->setLayout(layoutParent);
// 添加两个按钮进去
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
layoutParent->addWidget(btn1);
layoutParent->addWidget(btn2);
// 创建子 layout
QHBoxLayout* layoutChild = new QHBoxLayout();
// 添加两个按钮进去
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
layoutChild->addWidget(btn3);
layoutChild->addWidget(btn4);
// 把这个子 layout 添加到 父 layout 中
layoutParent->addLayout(layoutChild);
}
执行程序, 观察结果

结合 QHBoxLayout 和 QVBoxLayout , 就可以做出各种复杂的界面了.
8.3 网格布局
Qt 中还提供了 QGridLayout 用来实现网格布局的效果. 可以达到 M * N 的这种网格的效果.
核心属性
整体和 QVBoxLayout 以及 QHBoxLayout 相似. 但是设置 spacing 的时候是按照垂直水平两个方向来设置的.
| 属性 | 说明 |
|---|---|
| layoutLeftMargin | 左侧边距 |
| layoutRightMargin | 右侧边距 |
| layoutTopMargin | 上方边距 |
| ayoutBottomMargin | 下方边距 |
| layoutHorizontalSpacing | 相邻元素之间水平方向的间距 |
| layoutVerticalSpacing | 相邻元素之间垂直方向的间距 |
| layoutRowStretch | 行方向的拉伸系数 |
| layoutColumnStretch | 列方向的拉伸系数 |
8.3.1 代码示例1: 使用 QGridLayout 管理元素
代码中创建 QGridLayout 和 4 个按钮.
使用 addWidget 添加控件到布局管理器中. 但是添加的同时会指定两个坐标. 表示放在第几行, 第几列.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 4 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
// 创建网格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 1, 0);
layout->addWidget(btn4, 1, 1);
// 设置 layout 到窗口中.
this->setLayout(layout);
}
执行代码, 观察效果. 可以看到当前的这几个按钮是按照 2 行 2 列的方式排列的.

如果调整行列坐标为下列代码
cpp
// 创建网格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 0, 2);
layout->addWidget(btn4, 0, 3);
执行代码, 可以看到这几个按钮都在同一行了. 相当于 QHBoxLayout

如果调整行列坐标为下列代码
cpp
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 1, 0);
layout->addWidget(btn2, 2, 0);
layout->addWidget(btn3, 3, 0);
layout->addWidget(btn4, 4, 0);
执行代码, 可以看到这几个按钮都在同一列了. 相当于 QVBoxLayout

任意调整行列, 即可看到不同的效果.
cpp
// 创建网格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 1, 1);
layout->addWidget(btn3, 2, 2);
layout->addWidget(btn4, 3, 3);

编写代码形如
cpp
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 1, 0);
layout->addWidget(btn3, 2, 0);
layout->addWidget(btn4, 10, 0);
此处也要注意, 设置行和列的时候, 如果设置的是一个很大的值, 但是这个值和上一个值之间并没有其他的元素, 那么并不会在中间腾出额外的空间.
虽然把 btn4 设置在第 10 行, 但是由于 3-9 行没有元素. 因此 btn4 仍然会紧挨在 btn3 下方. 看起来和上面的 0 1 2 3 的情况是相同的.

8.3.2 代码示例2: 设置 QGridLayout 中元素的大小比例
创建 6 个按钮, 按照 2 行 3 列的方式排列
使用 setColumnStretch 设置每一列的拉伸系数.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 6 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
QPushButton* btn5 = new QPushButton("按钮5");
QPushButton* btn6 = new QPushButton("按钮6");
// 创建网格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 0, 2);
layout->addWidget(btn4, 1, 0);
layout->addWidget(btn5, 1, 1);
layout->addWidget(btn6, 1, 2);
// 设置拉伸比例
// 第 0 列拉伸比例设为 1;
layout->setColumnStretch(0, 1);
// 第 1 列拉伸比例设为 0, 即为固定大小, 不参与拉伸
layout->setColumnStretch(1, 0);
// 第 2 列拉伸比例设为 3, 即为第 2 列的宽度是第 0 列的 3 倍
layout->setColumnStretch(2, 3);
// 设置 layout 到窗口中.
this->setLayout(layout);
}
执行程序, 可以看到每一列的宽度是不同的. 并且随着窗口调整动态变化.

另外, QGridLayout 也提供了 setRowStretch 设置行之间的拉伸系数.
上述案例中, 直接设置 setRowStretch 效果不明显, 因为每个按钮的高度是固定的. 需要把按钮的垂直方向的 sizePolicy 属性设置为 QSizePolicy::Expanding 尽可能填充满布局管理器, 才能看到效果.
8.3.3 代码示例3: 设置垂直方向的拉伸系数
编写代码, 创建 6 个按钮, 按照 3 行 2 列方式排列.
使用 setSizePolicy 设置按钮的尺寸策略. 可选的值如下:
- QSizePolicy::Ignored : 忽略控件的尺寸,不对布局产生影响。
- QSizePolicy::Minimum : 控件的最小尺寸为固定值,布局时不会小于该值。
- QSizePolicy::Maximum : 控件的最大尺寸为固定值,布局时不会大于该值。
- QSizePolicy::Preferred : 控件的理想尺寸为固定值,布局时会尽量接近该值。
- QSizePolicy::Expanding : 控件的尺寸可以根据空间调整,尽可能占据更多空间。
- QSizePolicy::Shrinking : 控件的尺寸可以根据空间调整,尽可能缩小以适应空间。
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 6 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
QPushButton* btn5 = new QPushButton("按钮5");
QPushButton* btn6 = new QPushButton("按钮6");
// 设置按钮的 sizePolicy, 此时按钮的水平方向和垂直方向都会尽量舒展开
btn1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn4->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn5->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn6->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// 创建网格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 1, 0);
layout->addWidget(btn4, 1, 1);
layout->addWidget(btn5, 2, 0);
layout->addWidget(btn6, 2, 1);
// 设置拉伸比例
// 第 0 行拉伸比例设为 1;
layout->setRowStretch(0, 1);
// 第 1 行拉伸比例设为 0, 即为固定大小, 不参与拉伸
layout->setRowStretch(1, 0);
// 第 2 行拉伸比例设为 3, 即为第 2 行的宽度是第 0 行的 3 倍
layout->setRowStretch(2, 3);
// 设置 layout 到窗口中.
this->setLayout(layout);
}
执行代码, 观察效果.
此时的按钮垂直方向都舒展开了. 并且调整窗口尺寸, 也会按照设定的比例同步变化.

总的来说, 使用 QGridLayout 能够代替很多 QHBoxLayout 和 QVBoxLayout 嵌套的场景. 毕竟嵌套的代码写起来是比较麻烦的.
另外不要忘了, QGridLayout 里面也能嵌套 QHBoxLayout 和 QVBoxLayout ,QHBoxLayout 和 QVBoxLayout 里面也能嵌套 QGridLayout .
灵活使用上述布局管理器, 就可以实现出任意的复杂界面.
8.4 表单布局
除了上述的布局管理器之外, Qt 还提供了 QFormLayout , 属于是 QGridLayout 的特殊情况, 专门用于实现两列表单的布局.
这种表单布局多用于让用户填写信息的场景. 左侧列为提示, 右侧列为输入框.
8.4.1 代码示例1: 使用 QFormLayout 创建表单
编写代码, 创建 QFormLayout , 以及三个 label 和三个 lineEdit
- 使用 addRow 方法来添加一行. 每行包含两个控件. 第一个控件固定是 QLabel / 文本, 第二个控件则可以是任意控件.
- 如果把第一个参数填写为 NULL, 则什么都不显示.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 layout
QFormLayout* layout = new QFormLayout();
this->setLayout(layout);
// 创建三个 label
QLabel* label1 = new QLabel("姓名");
QLabel* label2 = new QLabel("年龄");
QLabel* label3 = new QLabel("电话");
// 创建三个 lineEdit
QLineEdit* lineEdit1 = new QLineEdit();
QLineEdit* lineEdit2 = new QLineEdit();
QLineEdit* lineEdit3 = new QLineEdit();
// 创建一个提交按钮
QPushButton* btn = new QPushButton("提交");
// 把上述元素添加到 layout 中
layout->addRow(label1, lineEdit1);
layout->addRow(label2, lineEdit2);
layout->addRow(label3, lineEdit3);
layout->addRow(NULL, btn);
}
执行程序, 可以看到以下结果.

8.5 Spacer
使用布局管理器的时候, 可能需要在控件之间, 添加一段空白. 就可以使用 QSpacerItem 来表示.
核心属性
| 属性 | 说明 |
|---|---|
| width | 宽度 |
| height | 高度 |
| hData | 水平方向的sizePolicy - QSizePolicy::Ignored : 忽略控件的尺寸,不对布局产生影响。 - QSizePolicy::Minimum : 控件的最小尺寸为固定值,布局时不会超过该值。 - QSizePolicy::Maximum : 控件的最大尺寸为固定值,布局时不会小于该值。 - QSizePolicy::Preferred : 控件的理想尺寸为固定值,布局时会尽量接近该值。 - QSizePolicy::Expanding : 控件的尺寸可以根据空间调整,尽可能占据更多空间。 - QSizePolicy::Shrinking : 控件的尺寸可以根据空间调整,尽可能缩小以适应空间。 |
| vData | 垂直方向的 sizePolicy,选项同上. |
8.5.1 代码示例: 创建一组左右排列的按钮
在界面上创建一个 QVBoxLayout , 并添加两个按钮.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
layout->addWidget(btn1);
layout->addWidget(btn2);
}
直接运行程序, 可以看到两个按钮是紧挨着的.

在两个按钮中间添加一个 spacer
cpp
Widget::Widget(1 QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
// 创建 Spacer
QSpacerItem* spacer = new QSpacerItem(200, 20);
layout->addWidget(btn1);
// 在两个 widget 中间添加空白
layout->addSpacerItem(spacer);
layout->addWidget(btn2);
}
运行程序, 观察代码效果. 可以看到两个按钮之间已经存在了间隔了.
调整 QSpacerItem 不同的尺寸, 即可看到不同的间距.

在 Qt Designer 中, 也可以直接给界面上添加 spacer.
