目录
一、控件概述
Widget 是 Qt 中的核心概念. 英文原义是 "小部件", 我们此处也把它翻译为 "控件" 。控件是构成⼀个图形化界面的基本要素。
像上述示例中的,按钮、列表视图、树形视图、单行输入框、多行输入框、滚动条、下拉框等,都可以称为 "控件"。
Qt 作为⼀个成熟的 GUI 开发框架,内置了大量的常用控件。这⼀点在 Qt Designer 中就可以看到端倪。并且 Qt 也提供了 "自定义控件" 的能力,可以让程序猿在现有控件不能满足需求的时候,对现有控件做出扩展,或者手搓出新的控件。
🌴综上, 学习 Qt, 其中⼀个很重要的任务就是熟悉并掌握 Qt 内置的常用控件。这些控件对于我们快速开发出符合需求的界面, 是至关重要的。
1.关于控件体系的发展:
控件是 GUI 开发中的通用概念,不仅仅局限在 Qt 中。
🌵第一阶段:
- 完全没有控件。此时需要通过⼀些绘图 API 手动的绘制出按钮或者输入框等内容, 代码编写繁琐。例如文曲星的 Lava 平台开发。
🌵第二阶段:
- 只包含粗略的控件。只是提供了按钮, 输入框, 单选框, 复选框等最常用的控件。例如 html 的原生控件。
🌵第三阶段:
- 更完整的控件体系, 基本可以覆盖到 GUI 开发中的大部分场景。
- 例如早期的 MFC, VB, C++ Builder, Qt, Delphi, 后来的 Android SDK, Java FX, 前端的各种 UI 库等。
上图是 前端中 的 Element-ui 中的控件概览, 无论是丰富程度还是颜值, 都比 Qt 自带的控件更胜⼀筹。
二、QWidget 核心属性
在 Qt 中, 使用 QWidget 类表示 "控件"。像按钮, 视图, 输入框, 滚动条等具体的控件类, 都是继承自 QWidget。 可以说, QWidget 中就包含了 Qt 整个控件体系中, 通用的部分。
在 Qt Designer 中, 随便拖⼀个控件过来, 选中该控件, 即可在右下方看到 QWidget 中的属性。
这些属性既可以通过 QtDesigner 会直接修改, 也可以通过代码的方式修改。并且这些属性的具体含义, 在 Qt Assistant 中均有详细介绍。
在 Qt Assistant 中搜索 QWidget, 即可找到对应的文档说明。(或者在 Qt Creator 代码中, 选中 QWidget, 按 F1 也可)。
1.核心属性概览
下列表格列出了 QWidget 中的属性及其作用。
属性 | 作用 |
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 中的样式. Qt 中⽀持的样式⾮常丰富, 对于前端开发⼈员上⼿是⾮常友好的. |
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:部件的布局⽅向由全局原⼦性决定(PS 这个翻译其实有点尴尬. 其实就是根据应⽤程序中的其他 widget 布局⽅向确定的). |
autoFillBackground | 是否⾃动填充背景颜⾊. |
windowFilePath | 能够把 widget 和⼀个本地⽂件路径关联起来. PS: 其实作⽤不⼤. |
accessibleName | 设置 widget 的可访问名称. 这个名称可以被辅助技术 (像屏幕阅读器) 获取到.这个属性⽤于实现⽆障碍程序的场景中 (也就是给盲⼈写的程序). PS: 其实盲⼈也是可以使⽤电脑和⼿机的. 甚⾄盲⼈还能成为程序猿. 参⻅ https://www.bilibili.com/video/BV1954y1d7z9 |
accessibleDescripti on | 设置 widget 的详细描述. 作⽤同 accessibleName |
inputMethodHints | 针对输⼊框有效, ⽤来提⽰用户当前能输⼊的合法数据的格式. ⽐如只能输⼊数字, 只能输⼊⽇期等. |
下面是其中一些比较重要比较常用的属性:
2.enabled
API | 说明 |
isEnabled() | 获取到控件的可⽤状态. |
setEnabled | 设置控件是否可使⽤. true 表示可⽤, false 表示禁⽤. |
- 所谓 "禁用" 指的是该控件不能接收任何用户的输入事件, 并且外观上往往是灰色的.
- 如果⼀个 widget 被禁用, 则该 widget 的子元素也被禁用.
代码示例: 使用代码创建一个禁用状态的按钮
🍂widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("这是个被禁用的按钮");
button->setEnabled(false);
button->move(150,150);
}
运行程序, 可以看到按钮处于灰色状态, 无法被点击.
代码示例: 通过按钮2 切换按钮1 的禁用状态.
🌳1) 使用 Qt Designer 拖两个按钮到 Widget 中.
两个按钮的 objectName 分别为 pushButton 和 pushButton_enable
📚 QObject 的 objectName 属性介绍:
- QObject 是 QWidget 的父类. 里面最主要的属性就是 objectName .
- 在⼀个 Qt 程序中, objectName 相当于对象的身份标识, 彼此之间不能重复.
- 在使用 Qt Designer 时, 尤其是界面上存在多个 widget 的时候, 可以通过 objectName 获取到指定的 widget 对象.
- Qt Designer 生成的 ui 文件, 本身是 xml 格式的. qmake 会把这个 xml 文件转换成 C++ 的 .h 文件(这个文件生成在 build 目录中), 构成⼀个 ui_widget 类.
- 每个 widget 的 objectName 最终就会成为 ui_widget 类的属性名字.
最终这个类的实例, 就是 Ui::Widget *ui , 因此就可以通过形如 ui->pushButton 或者 ui->pushButton_enable 这样的代码获取到界面上的 widget 对象了.
class Ui_Widget
{
public:
QPushButton *pushButton;
QPushButton *pushButton_enable;
//.........
}
🌳 2) 生成两个按钮的 slot 函数.
- 使用 isEnabled 获取当前按钮的可用状态.
- 使用 setEnabled 修改按钮的可用状态. 此处是直接针对原来的可用状态进行取反后设置.
运行程序, 可以看到, 初始情况下, 上面的按钮是可用状态. 点击下方按钮, 即可使上方按钮被禁用; 再次点击下方按钮, 上方按钮就会解除禁用. (禁用状态的按钮为灰色, 且不可点击).
- 在 Qt Designer 中创建按钮的时候, 可以设置按钮的初始状态是 "可用" 还是 "禁用" .
- 如果把 enabled 这一列的对钩去掉, 则按钮的初始状态就是 "禁用" 状态.
3.geometry
geometry 原意是 几何 的意思,可以把它视为是以下四个属性的统称:
- x 横坐标
- y 纵坐标
- width 宽度
- height 高度
x,y 代表左上角的位置;width,height 代表当前控件的位置和尺寸。
但是实际开发中, 我们并不会直接使用这几个属性, 而是通过⼀系列封装的方法来获取/修改。
对于 Qt 的坐标系, 不要忘记是⼀个 "左手坐标系". 其中坐标系的原点是当前元素的父元素的左上角。
API | 说明 |
geometry() | 获取到控件的位置和尺⼨. 返回结果是⼀个 QRect(Rect就是矩形), 包含了 x, y, width, height. 其中 x, y 是左上⻆的坐标. |
setGeometry(QRect) setGeometry(int x, int y, int width, int height) | 设置控件的位置和尺⼨. 可以直接设置⼀个 QRect, 也可以分四个属性单独设置.(move 只是修改位置,setGeometry 既可以修改位置,又可以修改尺寸) |
代码示例: 控制按钮的位置
🌻1) 在界面中拖五个按钮.
五个按钮的 objectName 分别为 pushButton_target , pushButton_up , pushButton_down , pushButton_left , pushButton_right
五个按钮的初始位置和大小都随意.
🌻2) 在 widget.cpp 中编写四个按钮的 slot 函数
void Widget::on_pushButton_up_clicked()
{
// 获取到 target 本身的geometry
QRect rect = ui->pushButton_target->geometry();
qDebug() << rect;
rect.setY(rect.y() - 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_down_clicked()
{
QRect rect = ui->pushButton_target->geometry();
qDebug() << rect;
rect.setY(rect.y() + 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_left_clicked()
{
QRect rect = ui->pushButton_target->geometry();
qDebug() << rect;
rect.setX(rect.x() - 5);
ui->pushButton_target->setGeometry(rect);
}
void Widget::on_pushButton_right_clicked()
{
QRect rect = ui->pushButton_target->geometry();
qDebug() << rect;
rect.setX(rect.x() + 5);
ui->pushButton_target->setGeometry(rect);
}
运行程序, 可以看到, 按下下方的四个按钮, 就会控制 target 的左上角的位置. 对应的按钮整个尺寸也会发生改变。
上述代码中我们是直接设置的 QRect 中的 x, y,这样的修改就会使 QRect 宽度高度发生改变 。实际上 QRect 内部是存储了左上和右下两个点的坐标, 再通过这两个点的坐标差值计算长宽。单纯修改左上坐标就会引起整个矩形的长宽发生改变.
如果想让整个按钮都移动(高度宽度不变,整个按钮的位置都发生改变), 可以改成下列代码:(不再修改 QRect ,而是通过 QRect 基于 setGeometry 的第二个版本的函数重新设置位置即可)
void Widget::on_pushButton_up_clicked()
{
// 获取到 target 本身的geometry
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_down_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());
}
注意:上述代码使用 move 方法也是可以的。
代码示例: 写一个给女神表白的程序
🍄1) 往界面上拖拽两个按钮和一个 Label.
按钮 的 objectName 为 pushButton_accept 和 pushButton_reject , label 的 objectName 为 label。
控件中文本如下图所示:
🍄2) 在 widget.cpp 中添加 slot 函数.
// 在构造函数中设置随机种子,使用时间戳作为随机种子
srand(time(0));
void Widget::on_pushButton_accept_clicked()
{
ui->label->setText("女神快来嘴一个!mua~");
}
void Widget::on_pushButton_reject_clicked()
{
// 如果女神点击了这个按钮,就把这个按钮挪走
// 可以通过生成随机数的方式,来确定按钮的新位置
// 获取当前程序窗口的高度和宽度
int width = this->geometry().width();
int height = this->geometry().height();
// 重新生成按钮的位置
int x = rand() % width;
int y = rand() % height;
// 移动按钮的位置
ui->pushButton_reject->move(x, y);
}
- rand() 是C标准库中的函数,能够生成一个随机的整数。
- rand() % 100 的范围是 [0,99];所以 rand() % width 的范围刚好就在当前的窗口范围之内。
- C语言中通过 time 可以获取到 秒级 时间戳,时间戳是以1970 年 1 月 1 日 0 时 0 分 0 秒为基准,计算当前时刻和基准时刻的 秒数、毫秒数、微秒数...之差。
运行程序, 可以看到, 当点击 "残忍拒绝" 时, 按钮就跑了.
上述代码使用的是 clicked 信号,只有在鼠标点击(一下一上是点击)的时候才触发。
我们可以将它换成 pressed,鼠标按下的时候就会触发。代码如下:
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);
}
如果使用 mouseMoveEvent, 会更狠⼀些, 只要鼠标移动过来, 按钮就跑了。但是相对应的代码更麻烦⼀些 (需要自定义类继承自 QPushButton, 重写 mouseMoveEvent 方法)。
🌵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 对象. |
认真观察上面的表格, 可以看到, 其实这里的 API 有 frameGeometry 和 geometry 两个就足够完成所有的需求了.为什么要提供这么多功能重复的 API 呢?
这个就涉及到 Qt API 的设计理念了: 尽量符合人的直觉.举个栗子, Qt 的 QVector, 尾插元素操作, 有以下方法:
- push_back
- append
- +=
- <<
上述方法的效果都是等价的. 即使不翻阅文档, 单纯的凭借直觉就能把代码写对.
代码示例: 感受 geometry 和 frameGeometry 的区别.
🍀1) 在界面上放置⼀个按钮.
🍀2) 在按钮的 slot 函数中, 编写代码
void Widget::on_pushButton_clicked()
{
QRect rect1 = this->geometry();
QRect rect2 = this->frameGeometry();
qDebug() << "构造函数(geometry):" << rect1;
qDebug() << "构造函数(frameGeometry):" << rect2;
}
🍀3) 在构造函数中, 也添加同样的代码
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QRect rect1 = this->geometry();
QRect rect2 = this->frameGeometry();
qDebug() << "构造函数(geometry):" << rect1;
qDebug() << "构造函数(frameGeometry):" << rect2;
}
执行程序, 可以看到, 构造函数中, 打印出的 geometry 和 frameGeometry 是相同的.
但是在点击按钮时, 打印的 geometry 和 frameGeometry 则存在差异.
🌵注意!!!
- 在构造方法中, Widget 刚刚创建出来, 还没有加入到对象树中. 此时也就不具备 Window frame.
- 在按钮的 slot 函数中, 由于用户点击的时候, 对象树已经构造好了, 此时 Widget 已经具备了Window frame, 因此在位置和尺寸上均出现了差异.
如果把上述代码修改成打印 pushButton 的 geometry 和 frameGeometry , 结果就是完全相同的. 因为 pushButton 并非是一个窗口.
4.windowTitle
API | 说明 |
windowTitle() | 获取到控件的窗⼝标题. |
setWindowTitle(const QString& title) | 设置控件的窗⼝标题. |
注意! 上述设置操作针对不同的 widget 可能会有不同的行为. 如果是顶层 widget (独立窗口), 这个操作才会有效.如果是子 widget, 这个操作无任何效果.
代码示例: 设置窗口标题
修改 widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置窗口标题
this->setWindowTitle("这是窗口标题");
QPushButton* button = new QPushButton(this);
button->setText("按钮");
button->setWindowTitle("通过按钮设置窗口标题");
}
执行效果:
通过执行出来的效果我们就可以观察到下面3行代码的执行,对窗口标题没有任何的影响,所以结论就是谁是窗口才能给谁去进行设置。
其实当前不应该给按钮设置 window Title。但是实际设置了之后,没有任何效果,也没有报错。此时 “没有报错” 这样的设定显然是不科学的。万一出了 bug,找不到的时候可就得用年终奖去买单了~
5.windowIcon
API | 说明 |
windowIcon() | 获取到控件的窗⼝图标. 返回 QIcon 对象. |
setWindowIcon(const QIcon& icon) | 设置控件的窗⼝图标. |
同 windowTitle, 上述操作仅针对顶层 widget 有效.
代码示例: 设置窗口图标
🌴1) 先在 D 盘中放⼀个图片, 名字为 rose.jpg
🌴2) 修改 widget.cpp
#include <QIcon>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建图标对象
QIcon icon("d:/rose.jpg");
// 设置图标
this->setWindowIcon(icon);
}
我们之前使用堆来创建对象,主要是因为要确保当前的控件的生命周期是足够的。要通过 Qt 对象树来释放对象。
而 QIcon 自身是一个比较小的对象,创建出来之后,就是要设置到某个 QWidget 里面, QIcon 对象本身释放不释放,不影响图标最终的显示。另一方面,QIcon 也不支持对象树,无法给它指定父对象。
注意: Windows 下路径的分隔符可以使⽤ / 也可以使⽤ \ . 但是如果在 字符串 中使⽤ \ , 需要写作转义字符的形式 \\ .
🌴3) 运行程序, 可以看到窗口图标已经成为上述图片
与此同时, 程序在任务栏中的图表也发生改变.
实际开发中, 我们⼀般不会在代码中通过绝对路径引入图片. 因为我们无法保证程序发布后, 用户的电脑上也有同样的路径.
如果使用相对路径, 则需要确保代码中的相对路径写法和图片实际所在的路径匹配 (比如代码中写作 "./image/rose.jpg", 就需要在当前⼯作目录中创建 image 目录, 并把 rose.jpg 放进去)。
- 绝对路径: 以盘符(windows)或者以 / (Linux) 开头的路径。
- 相对路径: 以 . (表示当前路径) 或者 以 .. (表示当前路径上级路径) 开头的路径。其中 . 经常也会省略。相对路径的前提是需要明确 "当前⼯作⽬录"。
对于 Qt 程序来说, 当前⼯作⽬录可能是变化的. ⽐如通过 Qt Creator 运⾏的程序, 当前⼯作⽬录是项⽬的构建⽬录; 直接双击 exe 运⾏, ⼯作⽬录则是 exe 所在⽬录.所谓构建⽬录, 是和 Qt 项⽬并列的, 专⻔⽤来放⽣成的临时⽂件和最终 exe 的⽬录.
代码示例: 获取当前的工作目录
🌴1) 在界面上创建⼀个比较大的 label, 确保能把路径显示完整. objectName 使用默认的 label 即可.
🌴2) 修改 widget.cpp
- 使用 QDir::currentPath() 即可获取到当前工作目录
#include <QDir>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 获取到当前⼯作⽬录
QString currentDir = QDir::currentPath();
// 设置⼯作⽬录到 label 中.
ui->label->setText(currentDir);
}
🌴3) 直接在 Qt Creator 中执行程序, 可以看到当前工作目录是项目的构建目录.
🌴4) 进入上述构建目录, 把里面的 exe 拷贝到其他目录中, 比如 D: 中. 再次执行程序, 可以看到当前⼯作目录已经发生改变.
要想直接能双击 exe 运行, 需要先把 Qt 的路径添加到 path 环境变量中, 否则会提示找不到动态库.
注意, 上述 构建目录, 是随时可删除的. 比如点击菜单栏中的 "构建" -> "清理项目" , 就会把这个目录中的内容清空掉.
因此如果我们把图片文件放到构建目录中, 可能在不小心删除后就丢失了. 我们还是希望能够把图片和源代码放到⼀起, 并且使我们的程序无论拷贝到任何位置中都能正确使用图片.
🌵使用 qrc 文件管理资源
Qt 使用 qrc 机制帮我们自动完成了上述工作, 更方便的来管理项目依赖的静态资源.
- qrc 文件是⼀种XML格式的资源配置⽂件, 它用XML记录硬盘上的文件和对应的随意指定的资源名称. 应用程序通过资源名称来访问这些资源.
- 在Qt开发中, 可以通过将资源文件添加到项目中来方便地访问和管理这些资源. 这些资源文件可以位于qrc文件所在目录的同级或其子目录下.
- 在构建程序的过程中, Qt 会把资源文件的二进制数据转成 cpp 代码, 编译到 exe 中. 从而使依赖的资源变得 "路径无关".
- 这种资源管理机制并非 Qt 独有, 很多开发框架都有类似的机制. 例如 Android 的 Resources 和 AssetManager 也是类似的效果.
代码示例: 通过 qrc 管理图片作为图标
🍋1) 在项目中创建一个 Qt Resource File (qrc 文件),文件名随意起(不要带中文), 此处叫做resource.qrc .
🍋2) 在 qrc 编辑器中, 添加前缀(Prefix)。
- 此处我们前缀设置成 / 即可.
所谓的前缀, 可以理解成 "虚拟的目录" ,这个目录没有在你的电脑上真实存在,是 Qt 自己抽象出来的。 这个前缀决定了后续我们如何在代码中访问资源。
qrc 机制本质上就是把 图片 的二进制数据转成 C++ 代码,最终就会在代码中看到很大的 char 数组,里面就是图片的二进制数据。
为了方便 Qt 代码访问到这个图片上,Qt 就自己抽象出了 虚拟的目录。
🍋3) 在 资源编辑器 中, 点击 Add Files 添加资源文件. 此处我们需要添加的是 rose.jpg
这个按钮在创建 prefix 之前是禁用的,创建好 prefix 之后就可以使用了,添加的文件就是添加到 prefix 下面的。点击 add Files 得到的目录就是当前代码所在的目录。
注意: 添加的文件必须是在 qrc 文件的同级目录, 或者同级目录的子目录中. 因此我们需要把之前 D 盘中的 rose.jpg 复制到上述目录中.
添加完毕后, 可以在 资源编辑器 中看到添加好的文件:
🍋4) 在代码中使用 rose.jpg
编辑 widget.cpp
#include <QIcon>
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 中资源的路径匹配. 否则资源无法被访问 (同时也不会有报错提示).
🍋5) 运行程序, 可以看到图标已经能正确设置.
接下来, 我们可以进入到项目的构建目录, 可以看到, 目录中多了一个 qrc_resource.cpp 文件. 直接打开这个文件, 可以看到类似如下代码:
static const unsigned char qt_resource_data[] = {
// D:/code/qt/QWidget_5/rose.jpg
0x0,0x0,0x1a,0x42,
0xff,
0xd8,0xff,0xe0,0x0,0x10,0x4a,0x46,0x49,0x46,0x0,0x1,0x1,0x0,0x0,0x1,0x0,
0x1,0x0,0x0,0xff,0xfe,0x0,0x17,0x47,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,
0x20,0x62,0x79,0x20,0x53,0x6e,0x69,0x70,0x61,0x73,0x74,0x65,0xff,0xdb,0x0,0x84,
0x0,0xa,0x7,0x7,0x8,0x7,0x6,0xa,0x8,0x8,0x8,0xb,0xa,0xa,0xb,0xe,
0x18,0x10,0xe,0xd,0xd,0xe,0x1d,0x15,0x16,0x11,0x18,0x23,0x1f,0x25,0x24,0x22,
0x1f,0x22,0x21,0x26,0x2b,0x37,0x2f,0x26,0x29,0x34,0x29,0x21,0x22,0x30,0x41,0x31,
0x34,0x39,0x3b,0x3e,0x3e,0x3e,0x25,0x2e,0x44,0x49,0x43,0x3c,0x48,0x37,0x3d,0x3e,
0x3b,0x1,0xa,0xb,0xb,0xe,0xd,0xe,0x1c,0x10,0x10,0x1c,0x3b,0x28,0x22,0x28,
0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
0x3b,0x3b,0xff,0xc0,0x0,0x11,0x8,0x1,0x9,0x0,0xfd,0x3,0x1,0x11,0x0,0x2,
0x11,0x1,0x3,0x11,0x1,0xff,0xc4,0x1,0xa2,0x0,0x0,0x1,0x5,0x1,0x1,0x1,
0x1,0x1,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x2,0x3,0x4,0x5,
//.........
};
上述代码其实就是通过 unsigned char 数组, 把 rose.jpg 中的每个字节都记录下来. 这些代码会被编译到 exe 中. 后续无论 exe 被复制到哪个目录下, 都确保能够访问到该图片资源.
🍇上述 qrc 这⼀套资源管理方案, 优点和缺点都很明显.
- 优点: 确保了图片, 字体, 声音等资源能够真正做到 "目录无关", 无论如何都不会出现资源丢失的情况.
缺点: 不适合管理体积大的资源. 如果资源比较大 (比如是几个 MB 的文件), 或者资源特别多, 生成的最终的 exe 体积就会比较大, 程序运行消耗的内存也会增大, 程序编译的时间也会显著增加.
6.windowOpacity
API | 说明 |
windowOpacity() | 获取到控件的不透明数值. 返回 float, 取值为 0.0 -> 1.0 其中 0.0 表示全透明, 1.0 表示完全不透明. |
setWindowOpacity(float n) | 设置控件的不透明数值. |
代码示例: 调整窗口透明度
🍓1) 在界面上拖放两个按钮, 分别用来增加不透明度和减少不透明度. objectName 分别为 pushButton_add 和 pushButton_sub
🍓2) 编写 wdiget.cpp, 编写两个按钮的 slot 函数
- 点击 pushButton_sub 会减少不透明度, 也就是窗口越来越透明.
- 点击 pushButton_add 会增加不透明度, 窗口会逐渐恢复.
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)
{
return;
}
qDebug() << opacity;
opacity -= 0.1;
this->setWindowOpacity(opacity);
}
🍓 3) 执行程序, 可以看到, 点击了几下 - 之后, 就可以透过窗口看到后面的桌面背景了. 点击 + 又会逐渐恢复.
同时控制台中也可以看到 opacity 数值的变化:
注意, C++ 中 float 类型遵守 IEEE 754 标准, 因此在进行运算的时候会有⼀定的精度误差. 因此 1 - 0.1 的数值并非是 0.9。
7.cursor
API | 说明 |
cursor() | 获取到当前 widget 的 cursor 属性, 返回 QCursor 对象. 当⿏标悬停在该 widget 上时, 就会显⽰出对应的形状. |
setCursor(const QCursor& cursor) | 设置该 widget 光标的形状. 仅在⿏标停留在该 widget 上时⽣效. |
QGuiApplication::setOverrideCursor(co nst QCursor& cursor) | 设置全局光标的形状. 对整个程序中的所有 widget 都会⽣效. 覆盖上⾯的 setCursor 设置的内容. |
代码示例: 在 Qt Designer 中设置按钮的光标
🍉1) 在界面中创建一个按钮
🍉2) 直接在右侧属性编辑区修改 cursor 属性为 "打开手势"
🍉3) 运行程序, 鼠标悬停到按钮上, 即可看到光标的变化.
代码示例: 通过代码设置按钮的光标
🍏1) 编写 widget.cpp
- 其中 Qt::WaitCursor 就是自带的沙漏形状的光标.
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建按钮
QPushButton* button = new QPushButton(this);
button->move(200, 200);
button->setText("按钮");
// 设置按钮的 cursor
button->setCursor(QCursor(Qt::WaitCursor));
}
系统内置的光标形状如下:
- Ctrl + 左键 点击 Qt::WaitCursor 跳转到源码即可看到.
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) 运行程序, 观察效果.
代码示例: 自定义鼠标光标
Qt 自带的光标形状有限. 我们也可以自己找个图片, 做成鼠标的光标. 比如我们就用前面的玫瑰。
🍒1) 创建 qrc 资源文件, 添加前缀 / , 并加入 rose.jpg
🍒2) 编写 widget.cpp
#include <QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建一个位图对象,加载自定义光标图片
QPixmap pixmap(":/rose.jpg");
// 缩放图片为 64 * 64 的尺寸
// 注意:缩放不是修改图片对象本身,而是返回一个新的图片对象副本
pixmap = pixmap.scaled(64,64);
// 创建 QCursor 对象,并指定 “热点” 为(2,2)坐标位置
// 所谓 “热点” 就是鼠标点击时生效的位置
// 默认情况下,鼠标点击时,相当于在图片的左上角进行点击
QCursor cursor(pixmap, 2, 2);
// 设置光标
this->setCursor(cursor);
}
🍒3) 运行程序, 观察效果
这里给大家安利一个图标网站(免费下载):阿里巴巴矢量图标库
8.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 | 是否带有删除线 |
代码示例: 在 Qt Designer 中设置字体属性
🍌1) 在界面上创建⼀个 label
🍌2) 在右侧的属性编辑区, 设置该 label 的 font 相关属性。
在这⾥调整上述属性, 可以实时的看到文字的变化.
🍌3) 执行程序, 观察效果
代码示例: 在代码中设置字体属性
🍇1) 修改 widget.cpp
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel* label = new QLabel(this);
label->setText("这是一段文本");
// 创建字体对象
QFont font;
// 设置字体家族
font.setFamily("仿宋");
// 设置字体大小
font.setPointSize(20);
// 设置字体加粗
font.setBold(true);
// 设置字体倾斜
font.setItalic(true);
// 设置字体下划线
font.setUnderline(true);
// 设置字体删除线
font.setStrikeOut(true);
// 设置字体对象到 label 上
label->setFont(font);
}
🍇2) 运行程序, 观察效果
实际开发中, 字体属性如何选择, 是⼀个 "审美问题", 而不是 "技术问题". 往往需要有⼀定的艺术细胞.
幸运的是, 公司中往往有专业的 "美工" / "设计" 这样的岗位, 去做这方面的工作. 咱们程序员只要按照人家给的设计稿, 把代码写出来即可. 不必过多考虑怎样搭配才好看的问题.
9.toolTip
API | 说明 |
setToolTip | 设置 toolTip. ⿏标悬停在该 widget 上时会有提示说明. |
setToolTipDuring | 设置 toolTip 提示的时间. 单位 ms. 时间到后 toolTip ⾃动消失. |
toolTip 只是给用户看的. 在代码中⼀般不需要获取到 toolTip.
代码示例: 设置按钮的 toolTip
🍺1) 在界面上拖放两个按钮. objectName 设置为 pushButton_yes 和 pushButton_no
🍺2) 编写 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(5000);
}
🍺3) 运行程序, 观察效果
可以看到鼠标停到按钮上之后, 就能弹出提示. 时间到后自行消失.
10.focusPolicy
设置控件获取到焦点的策略. 比如某个控件能否用鼠标选中或者能否通过 tab 键选中.
所谓 "焦点" , 指的就是能选中这个元素. 接下来的操作 (比如键盘操作), 就都是针对该焦点元素进行的了. 这个对于 输入框, 单选框, 复选框等控件非常有用的.这个事情就和 war3 或者 sc2 中, 先选中单位, 再下达命令是⼀样的.
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 , 同时控件也通过鼠标滚轮获取到焦点 (新增的选项, ⼀般很少使用).
代码示例: 理解不同的 focusPolicy
🌵1) 在界面上创建四个单行输入框 (Line Edit)
🌵2) 修改四个输入框的 focusPolicy 属性为 Qt::StrongFocus (默认取值, ⼀般不用额外修改)
此时运行程序, 可以看到, 使用鼠标单击/tab, 就可以移动光标所在输入框. 从而接下来的输入就是针对这个获取焦点的输入框展开的了.
🌵3) 修改第二个输入框的 focusPolicy 为 Qt::NoFocus , 则第二个输入框不会被 tab / 鼠标左键选中.
此时这个输入框也就无法输入内容了
🌵4) 修改第二个输入框 focusPolicy 为 Qt::TabFocus , 则只能通过 tab 选中, 无法通过鼠标选中.
🌵5) 修改第二个输入框 focusPolicy 为 Qt::ClickFocus , 则只能通过 鼠标 选中, 无法通过 tab 选中.
11.styleSheet
通过 CSS 设置 widget 的样式.
CSS (Cascading Style Sheets 层叠样式表) 本身属于网页前端技术. 主要就是用来描述界面的样式.
- 所谓 "样式", 包括不限于 大小, 位置, 颜色, 间距, 字体, 背景, 边框等. 我们平时看到的丰富多彩的网页, 就都会用到大量的 CSS.
Qt 虽然是做 GUI 开发, 但实际上和 网页前端 有很多异曲同工之处. 因此 Qt 也引入了对于 CSS 的支持.
CSS 中可以设置的样式属性非常多. 基于这些属性 Qt 只能支持其中一部分, 称为 QSS (Qt Style Sheet) . 具体的支持情况可以参考 Qt 文档中 "Qt Style Sheets Reference" 章节.
代码示例: 设置文本样式
🍂1) 在界面上创建 label
🍂2) 编辑右侧的 styleSheet 属性或者鼠标右击选择 改变样式表 设置样式
- 此处的语法格式同 CSS, 使用键值对的方式设置样式. 其中键和值之间使用 : 分割. 键值对之间使用 ; 分割.
- 另外, Qt Designer 只能对样式的基本格式进行校验, 不能检测出哪些样式不被 Qt 支持. 比如 textalign: center 这样的文本居中操作, 就无法支持.
编辑完成样式之后, 可以看到在 Qt Designer 中能够实时预览出效果.
🍂3) 运行程序, 可以看到实际效果和预览效果基本⼀致.
代码示例: 实现切换夜间模式.
🌻1) 在界面上创建一个多行输入框 (Text Edit) 和两个按钮.objectName 分别为 pushButton_light 和 pushButton_dark
🌻2) 编写按钮的 slot 函数.
为了和 widget 的初始背景色更相似,我们就需要知道背景色的数值是多少。这里我们使用 QQ截图 或者 snipaste 截图工具就可以看到默认的背景色(这俩工具都内置了取色器,借助取色器就可以看到) 。
使用在线调色板或者画图板, 都可以看到数字对应的颜色. 参考: https://www.sojson.com/web/online.html
void Widget::on_pushButton_light_clicked()
{
// 设置窗口的样式
this->setStyleSheet("background-color: rgb(240, 240, 240);");
// 设置输入框的样式
ui->plainTextEdit->setStyleSheet("background-color: white; color: black;");
// 设置按钮的样式
ui->pushButton_light->setStyleSheet("color: black;");
ui->pushButton_dark->setStyleSheet("color: black;");
}
void Widget::on_pushButton_dark_clicked()
{
// 设置窗口的样式
this->setStyleSheet("background-color: black;");
// 设置输入框的样式
ui->plainTextEdit->setStyleSheet("background-color: black; color: white;");
// 设置按钮的样式
ui->pushButton_light->setStyleSheet("color: white;");
ui->pushButton_dark->setStyleSheet("color: white;");
}
🍒关于计算机中的颜色表示:
计算机中使用 "像素" 表示屏幕上的一个基本单位(也就是一个发亮的光点).
每个光点都使用三个字节表示颜色, 分别是 R (red), G (green), B (blue) 一个字节表示 (取值范围是 0-255, 或者 0x00-0xFF).
混合三种不同颜色的数值比例, 就能搭配出千千万万的颜色出来.
- rgb(255, 0, 0) 或者 #FF0000 或者 #F00 表示纯红色.
- rgb(0, 255, 0) 或者 #00FF00 或者 #0F0 表示纯绿色.
- rgb(0, 0, 255) 或者 #0000FF 或者 #00F 表示纯蓝色.
- rgb(255, 255, 255) 或者 #FFFFFF 或者 #FFF 表示纯白色.
- rgb(0, 0, 0) 或者 #000000 或者 #000 表示纯黑色.
当然, 上述规则只是针对一般的程序而言是这么设定的. 实际的显示器, 可能有 8bit 色深或者 10bit 色深等, 实际情况会更加复杂。
🌻 3) 运行程序, 点击 "日间模式", 就是浅色背景, 深色文字; 点击 "夜间模式", 就是深色背景, 浅色文字。