常用控件之 QWidget

目录

一、控件概述

1.关于控件体系的发展:

 二、QWidget 核心属性

1.核心属性概览

2.enabled

3.geometry

🌵window frame(窗口框架) 的影响

4.windowTitle

5.windowIcon

🌵使用 qrc 文件管理资源

6.windowOpacity

7.cursor

8.font

9.toolTip

10.focusPolicy

​编辑

11.styleSheet


一、控件概述

        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 上会在状态栏中显⽰的提⽰信息.
toolTipDuringtoolTip 显⽰的持续时间.
statusTipWidget 状态发⽣改变时显⽰的提⽰信息(⽐如按钮被按下等).
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) 运行程序, 点击 "日间模式", 就是浅色背景, 深色文字; 点击 "夜间模式", 就是深色背景, 浅色文字。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南风与鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值