类型驱动:解锁Qt模板的渲染潜能

目录

1.QSS

1.1:背景介绍

1.2:基本语法

1.2.1:示例代码

1.3:QSS设置方式

1.3.1:指定控件样式设置

1.3.1.1:代码示例1(子元素受到影响)

widget.cpp

1.3.2:全局样式设置

1.3.2.1:代码示例1

main.cpp

[1.3.2.2:代码示例2(样式的层叠特性)](#1.3.2.2:代码示例2(样式的层叠特性))

main.cpp

weight.cpp

1.3.3:样式的优先级

代码示例1

main.cpp

weight.cpp

1.3.4:从文件加载样式表

1.3.4.1:代码示例1(从文件加载全局样式)

main.cpp

[1.3.5:使用Qt Designer 编辑样式](#1.3.5:使用Qt Designer 编辑样式)

代码示例1

1.4:选择器

1.4.1:选择器概况

代码示例1(使用全局选择器)

main.cpp

代码示例2(使用id选择器)

main.cpp

代码示例3(使用并集选择器)

main.cpp

1.4.2:子控件选择器

代码示例1(设置下拉框的下拉按钮样式)

main.cpp

1.4.3:伪类选择器

代码示例1(设置按钮的伪类样式)

main.cpp

1.5:样式属性

1.5.1:盒模型

代码示例1(设置边框和内边距)

main.cpp

代码示例2(设置外边距)

widget.cpp

1.6:控件样式示例

[1.6.1 按钮](#1.6.1 按钮)

1.6.2:复选框

代码示例(自定义复选框)

1.6.3:单选框

代码示例:自定义单选框

1.6.4:输入框

代码示例(自定义单行编辑框)

1.6.5:列表

代码示例(自定义列表框)

1.6.6:渐变色

代码示例

1.6.7:菜单栏

1.6.8:登录界面

2:绘图

2.1:基本概念

2.2:绘制各种形状

2.2.1:代码示例一(画直线)

widget.h

widget.cpp

2.2.2:代码示例二(画直线)

widget.cpp

2.2.3:代码示例三(绘制矩形)

widget.h

widget.cpp

2.2.4:代码示例四(绘制圆形)

widget.h

widget.cpp

2.2.5:代码示例五(绘制文本)

2.2.6:设置画笔

2.2.7:设置画刷

代码示例

widget.h

widget.cpp

2.3:绘制图片

2.3.1:绘制简单图片

widget.h

widget.cpp

2.3.2:平移图片

widget.h

widget.cpp

2.3.3:缩放图片

widget.h

widget.cpp

2.3.4:旋转图片

widget.h

widget.cpp

2.4:其他设置

2.4.1:移动画家位置

代码示例1(未移动画家位置)

widget.h

widgte.cpp

代码示例2(移动画家位置)

widget.h

widget.cpp

2.4.2:保存/加载画家的状态

代码示例一

widget.h

widgte.cpp

2.5:特殊的绘图设备

2.5.1:QPixmap

widget.h

widget.cpp

2.5.2:QImage

widget.h

widget.cpp

2.5.3:QPicture

widget.h

widget.cpp


1.QSS

1.1:背景介绍

在网页前端开发领域中, CSS是⼀个至关重要的部分. 描述了⼀个的 "样式". 从而起到对网页美化的作用.
所谓样式, 包括不限于大小, 位置, 颜⾊, 背景, 间距, 字体等等.
现在的网页很难找到没有 CSS 的. 可以说让 "界面好看" 是⼀个刚需.

  • 网页开发作为 GUI 的典型代表, 也对于其他客户端GUI开发产生了影响. Qt 也是其中之⼀.Qt 仿照 CSS 的模式, 引入了 QSS, 来对 Qt 中的控件做出样式上的设定, 从而允许程序猿写出界面更好看的代码.
  • 当然, 由于Qt 本⾝的设计理念和网页前端还是存在⼀定差异的, 因此QSS 中只能支持部分 CSS 属性.整体来说 QSS 要比CSS 更简单⼀些.

1.2:基本语法

对于CSS来说,基本的语法结构非常简单

选择器 {
属性名:属性值;
}
QSS 沿用了这样的设定.
选择器 {
属性名: 属性值;
}

其中:

  • 选择器描述了 "哪个 widget 要应用样式规则".
  • 属性则是⼀个键值对, 属性名表示要设置哪种样式, 属性值表示了设置的样式的值.
css 复制代码
QPushButton { color: red; };
QPushButton {
 color: red;
}

上述代码的含义表示, 针对界面上所有的 QPushButton,都把文本颜色设置为红色.

1.2.1:示例代码

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->setStyleSheet("QPushButton {color: red; }");
}

Widget::~Widget()
{
    delete ui;
}

上述代码中, 我们是只针对这⼀个按钮通过 setStyleSheet方法设置的样式. 此时这个样式
仅针对该按钮生效. 如果创建其他按钮, 其他按钮不会受到影响.

1.3:QSS设置方式

1.3.1:指定控件样式设置

  • QWidget 中包含了 setStyleSheet方法,可以直接设置样式. 上述代码博主已经演示了上述设置方式.
  • 另一方面,给指定控件设置样式之后,该控件的子元素也会受到影响.
1.3.1.1:代码示例1(子元素受到影响)

在界面上创建一个按钮.

修改 widget.cpp, 这次我们不再给按钮设置样式, 而是给 Widget 设置样式(Widget 是
QPushButton 的父控件).

widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    //给Widget本身设置样式
    this->setStyleSheet("QPushButton { color: red;} ");
}

Widget::~Widget()
{
    delete ui;
}

运行程序, 可以看到样式对于子控件按钮同样会生效.

1.3.2:全局样式设置

1.3.2.1:代码示例1
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    a.setStyleSheet("QPushButton {color: red}");
    
    Widget w;
    w.show();
    return a.exec();
}

运行程序,可以看到此时三个按钮的颜⾊都设置为红色了.

1.3.2.2:代码示例2(样式的层叠特性)

如果通过全局样式给某个控件设置了属性1, 通过指定控件样式给控件设置属性2, 那么这两个属性都会产生作用.
编写 main.cpp, 设置全局样式, 把按钮⽂本设置为红色

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    a.setStyleSheet("QPushButton { color: red }");
    
    Widget w;
    w.show();
    return a.exec();
}

编写 widget.cpp, 给第⼀个按钮设置字体大小

weight.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    ui->pushButton->setStyleSheet("QPushButton { font-size: 50px}");
}

Widget::~Widget()
{
    delete ui;
}

运行程序, 可以看到, 对于第⼀个按钮来说, 同时具备了颜⾊和字体大小样式. 而第⼆个按钮只有颜色样式.说明针对第⼀个按钮, 两种设置方式设置的样式, 叠加起来了.

形如上述这种属性叠加的效果, 我们称为 "层叠性".
CSS 全称为 Cascading Style Sheets, 其中 Cascading 就是 "层叠性" 的意思. QSS 也继承了这样的设定.
实际上把 QSS 叫做 QCSS 也许更合适⼀些~

1.3.3:样式的优先级

如果全局样式, 和指定控件样式冲突, 则指定控件样式优先展示.
编辑 main.cpp, 把全局样式设置为红色.

代码示例1
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    a.setStyleSheet("QPushButton { color : red;}");


    Widget w;
    w.show();
    return a.exec();
}
weight.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->pushButton->setStyleSheet("QPushButton { color: green;}");
}

Widget::~Widget()
{
    delete ui;
}

运行程序, 观察效果. 可以看到第⼀个按钮已经成为绿色了, 但是第⼆个按钮仍然是红色.

1.3.4:从文件加载样式表

上述代码都是把样式通过硬编码的方式设置的. 这样使 QSS 代码和 C++ 代码耦合在⼀起了, 并不方便代码的维护.因此更好的做法是把样式到单独的文件中,然后通过读取文件的方式来加载样式.

1.3.4.1:代码示例1(从文件加载全局样式)

在界⾯上创建⼀个按钮

创建 resource.qrc ⽂件, 并设定前缀为 / .

创建 style.qss ⽂件, 并添加到 resource.qrc 中

  • style.qss 是需要程序运行时加载的. 为了规避绝对路径的问题, 仍然使⽤ qrc 的方式来组
    织. (即把资源文件内容打包到 cpp 代码中).

Qt Creator 没有提供创建 qss ⽂件的选项. 咱们直接 右键 -> 新建⽂件 -> 手动设置文件扩展名 为qss 即可

使用Qt Creator 打开 style.qss ,编写内容

修改 main.cpp, 新增⼀个函数用来加载样式并且调用该函数,来设置样式

main.cpp
cpp 复制代码
#include "widget.h"
#include <QApplication>
#include <QFile>
QString LoadQss()
{
    QFile file(":/style.qss");
    // 读取⽂件内容. 虽然 readAll 返回的是 QByteArray, 但是 QString 提供了
    //QByteArray 版本的构造函数
    file.open(QFile::ReadOnly);
    QString style = file.readAll();
    file.close();
    return style;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    const QString & style = LoadQss();
    a.setStyleSheet(style);

    Widget w;
    w.show();
    return a.exec();
}

这里博主得吐槽⼀下. 理论上来说 Qt 应该要提供, 直接从文件加载样式表的接口. 类似于
setStyleSheetFromFile(const QString& path) 这种, 在内部把读文件操作封装好.

1.3.5:使用Qt Designer 编辑样式

QSS 也可以通过 Qt Designer 直接编辑, 从而起到实时预览的效果. 同时也能避免 C++ 和 QSS 代码的耦合.

代码示例1

在界面上创建⼀个按钮

右键按钮, 选择 "改变样式表"

在弹出的样式表编辑器中, 可以直接填写样式. 填写完毕, 点击 确定 即可.

此时 Qt Designer 的预览界面就会实时显示出样式的变化

运行程序, 可以看到样式确实发生了改变

这种方式设置样式, 样式内容会被以 xml 格式记录到 ui 文件中.

同时在控件的 styleSheet 属性中也会体现.

当我们发现⼀个控件的样式不符合预期的时候, 要记得排查这四个地⽅:

  • 全局样式.
  • 指定控件样式.
  • qss 文件中的样式.
  • ui文件中的样式.

1.4:选择器

1.4.1:选择器概况

|-------------------------------|-----------------------------------|-----------------------------------------------------------------------|
| 选择器 | 示例 | 说明 |
| 全局选择器 | * | 选择所有的widget |
| 类型选择器 (type selector) | QPushButton | 选择所有的 QPushButton和其子类的控件. |
| 类选择器 (class selector) | .QPushButton | 选择所有的 QPushButton 的控件. 不会选择子类 |
| ID 选择器 | #pushButton_2 | 选择 objectName 为 pushButton_2 的控件. |
| 后代选择器 | QDialog QPushButton | 选择QDialog的所有后代(子控件, 孙子控件等等)中的QPushButton. |
| 子选择器 | QDialog > QPushButton | 选择 QDialog 的所有子控件中的 QPushButton. |
| 并集选择器 | QPushButton, QLineEdit, QComboBox | 选择 QPushButton, QLineEdit, QComboBox 这三种控件. (即接下来的样式会针对这三种控件都生效). |
| 属性选择器 | QPushButton[flat="false"] | 选择所有 QPushButton 中, flat 属性为 false 的控件. |

总体来说, QSS 选择器的规则和 CSS 选择器基本⼀致. 上述选择器咱们也不需要全都掌握, 就只熟悉最常用的几个即可(上述加粗的)

代码示例1(使用全局选择器)

在界面上创建⼀个按钮.

  • 修改 main.cpp, 设置全局样式.
  • 注意, 此处选择器使用的是 QWidget . QPushButton 也是 QWidget 的子类, 所以会受到 QWidget 选择器的影响.
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setStyleSheet("QWidget{color:red}");
    Widget w;
    w.show();
    return a.exec();
}

运行程序,可以看到按钮的文本颜色已经是红色了.

如果把上述样式代码修改为下列代码

cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //a.setStyleSheet("QWidget{color:red}");
    a.setStyleSheet(".QWidget { color: red; }");
    Widget w;
    w.show();
    return a.exec();
}

此时按钮的颜色不会发生改变. 此时只是选择 QWidget 类, 而不会选择 QWidget 的子类
QPushButton 了.

代码示例2(使用id选择器)

在界面上创建 3 个按钮, objectName 为 pushButton , pushButton_2 , pushButton_3

  • 编写 main.cpp, 设置全局样式
  • 先通过 QPushButton 设置所有的按钮为黄色.
  • 再通过#pushButton 和 #pushButton_2 分别设置这两个按钮为红色和绿色.
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QString style = "";
    style += "QPushButton{color: yellow;}";
    style += "#pushButton{color: red;}";
    style += "#pushButton_2{color: green;}";
    a.setStyleSheet(style);
    Widget w;
    w.show();
    return a.exec();
}

执行程序, 观察效果

  • 当某个控件身上, 通过类型选择器和 ID 选择器设置了冲突的样式时, ID 选择器样式优先级更高.
  • 同理, 如果是其他的多种选择器作用同⼀个控件时出现冲突的样式, 也会涉及到优先级问题.Qt文档上有具体的优先级规则介绍 (参照The Style Sheet Syntax 的 Conflict Resolution 章节).
  • 这里的规则计算起来⾮常复杂(CSS 中也存在类似的设定), 咱们此处对于优先级不做进⼀步讨论.
  • 实践中我们可以简单的认为, 选择器描述的范围越精准, 则优先级越高. ⼀般来说, ID 选择器优先级是最高的.
代码示例3(使用并集选择器)

编写 main.cpp, 设置全局样式

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setStyleSheet("QPushButton, QLabel, QLineEdit {color : red;} ");
    Widget w;
    w.show();
    return a.exec();
}

运行程序, 可以看到这三种控件的文字颜色都设置为了红色

并集选择器是⼀种很好的代码复用的方式. 很多时候我们希望界面上的多个元素风格是统⼀
的, 就可以使用并集选择器, 把样式属性同时指定给多种控件.

1.4.2:子控件选择器

  • 有些控件内部包含了多个 "子控件" . 比如 QComboBox 的下拉后的⾯板, 比如 QSpinBox 的上下按钮等.

  • 可以通过子控件选择器 ::,针对上述⼦控件进行样式设置

哪些控件拥有哪些⼦控件, 参考文档Qt Style Sheets Reference 中 List of Sub-Controls 章
节.

代码示例1(设置下拉框的下拉按钮样式)

在界面上创建⼀个下拉框, 并创建几个选项

创建 resource.qrc , 并导入图片 Down.png.

  • 修改 main.cpp, 编写全局样式.
  • 使用子控件选择器 QComboBox::down-arrow 选中了 QComboBox 的下拉按钮.
  • 再通过 image 属性设置图片.
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QString style = "";
    style += "QComboBox::down-arrow{image:url(:/Down.png)}";
    a.setStyleSheet(style);
    Widget w;
    w.show();
    return a.exec();
}

执行程序, 观察效果.

1.4.3:伪类选择器

伪类选择器, 是根据控件所处的某个状态被选择的. 例如按钮被按下, 输入框获取到焦点, 鼠标移动到某个控件上等.

  • 当状态具备时, 控件被选中, 样式生效.
  • 当状态不具备时, 控件不被选中, 样式失效.
  • 使用:的方式定义伪类选择器.

常用的伪类选择器

|------------|----------|
| 伪类选择器 | 说明 |
| :hover | 鼠标放到控件上 |
| :pressed | 鼠标左键按下时 |
| :focus | 获取输入焦点时 |
| :enabled | 元素处于可用时 |
| :checked | 被勾选时 |
| :read-only | 元素为只读状态时 |

这些状态可以使用 ! 来取反. 比如 :!hover 就是鼠标离开控件时, :!pressed 就是鼠标松开时,
等等.

代码示例1(设置按钮的伪类样式)

在界面上创建⼀个按钮

编写 main.cpp, 创建全局样式

main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    QString style = "";
    style += "QPushButton{color:red;}";
    style += "QPushButton:hover{color:green;}";
    style += "QPushButton:pressed{color:blue;}";
    a.setStyleSheet(style);
    
    Widget w;
    w.show();
    return a.exec();
}

运行程序, 可以看到, 默认情况下按钮文字是红⾊, 鼠标移动上去是绿⾊, 鼠标按下按钮是蓝色

1.5:样式属性

QSS 中的样式属性非常多, 不需要都记住. 核⼼原则还是⽤到了就去查. 大部分的属性和 CSS 是⾮常相似的.

文档的 Qt Style Sheets Reference 章节详细介绍了哪些控件可以设置属性, 每个控
件都能设置哪些属性等.

1.5.1:盒模型

⼀个遵守盒模型的控件, 由上述几个部分构成.

  • Content 矩形区域:存放控件内容. 比如包含的文本/图标等.
  • Border 矩形区域:控件的边框.
  • Padding 矩形区域:内边距. 边框和内容之间的距离.
  • Margin 矩形区域:外边距. 边框到控件 geometry 返回的矩形边界的距离.

默认情况下,外边距,内边距,边框宽度都是 0.
可以通过⼀些 QSS 属性来设置上述的边距和边框的样式

|--------------|-----------------------------------------------------|
| QSS属性 | 说明 |
| margin | 设置四个方向的外边距.复合属性 |
| padding | 设置四个方向的内边距.复合属性. |
| border-style | 设置边框样式 |
| border-width | 边框的粗细 |
| border-color | 边框的颜⾊ |
| border | 复合属性,相当于 border-style + border-width + border-color |

代码示例1(设置边框和内边距)

在界面上创建⼀个 label

  • border: 5px solid red 相当于 border-style: solid; border-width: 5px;
  • border-color: red; 三个属性的简写形式.
  • padding-left: 10px; 是给左侧设置内边距.
main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setStyleSheet("QLabel{border:5px solid red; padding-left:10px;}");
    Widget w;
    w.show();
    return a.exec();
}

运行程序, 可以看到样式发生了变化

代码示例2(设置外边距)
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    QPushButton * Button = new QPushButton(this);
    Button->setGeometry(0,0,100,100);
    Button->setText("Hello World");
    Button->setStyleSheet("QPushButton{border:5px solid red;margin: 20px}");
    const QRect & Rect = Button->geometry();
    qDebug() << Rect;
}

Widget::~Widget()
{
    delete ui;
}

1.6:控件样式示例

1.6.1 按钮

下面给出⼀些常用控件的样式示例
界⾯上创建⼀个按钮

右键 -> 改变样式表, 使用Qt Designer 设置样式

cpp 复制代码
QPushButton {
 font-size: 20px;
 border: 2px solid #8f8f91;
 border-radius: 15px;
 background-color: #dadbde;
}
QPushButton:pressed {
 background-color: #f6f7fa;
}

执行程序, 可以看到效果
按钮点击之前

按钮点击之后

属性小结

|------------------|---------------------------|
| 属性 | 说明 |
| font-size | 设置文字大小 |
| border-radius | 设置圆⻆矩形. 数值设置的越大, 角就 "越圆". |
| background-color | 设置背景颜⾊. |

形如 #dadbde 是计算机中通过⼗六进制表示颜色的方式.
这⾥在 "常用控件" 的博客已经介绍过啦. 此处不再赘述.
另外, 在实际开发中, 颜色具体使⽤哪种好看, 是需要⼀定的 "艺术细菌" 的. 往往公司会有专门 的美工/设计人员来负责. 程序猿只需要按照设计稿实现程序即可.

1.6.2:复选框

代码示例(自定义复选框)

创建一个resource.qrc文件.并导入以下图片

  • 使用黑色作为默认形态.
  • 使用蓝色作为 hover 形态.
  • 使用红色作为 pressed 形态.
    注意这里的文件命名.

创建⼀个复选框

编辑复选框的样式
QCheckBox

{

font-size:20px;

}

QCheckBox::indicator {

width: 20px;

height: 20px;

}

QCheckBox::indicator:unchecked {

image: url(:/unchecked.png);

}

QCheckBox::indicator:unchecked:hover {

image: url(:/unchecked-hover.png);

}

QCheckBox::indicator:unchecked:pressed {

image: url(:/unchecked-pressed.png);

}

QCheckBox::indicator:checked {

image: url(:/checked.png);

}

QCheckBox::indicator:checked:hover {

image: url(:/checked-hover.png);

}

QCheckBox::indicator:checked:pressed {

image: url(:/checked-pressed.png);

}

运行程序, 可以看到此时的复选框就变的丰富起来了.

|-------------|---------------------------------------------------|
| 要点 | 说明 |
| ::indicator | ⼦控件选择器.选中 checkbox 中的对钩部分. |
| :hover | 伪类选择器.选中⿏标移动上去的状态. |
| :pressed | 伪类选择器. 选中⿏标按下的状态. |
| :checked | 伪类选择器.选中 checkbox 被选中的状态. |
| :unchecked | 伪类选择器.选中 checkbox 未被选中的状态. |
| width | 设置⼦控件宽度.对于普通控件⽆效 (普通控件使⽤ geometry ⽅式设定尺⼨) |
| height | 设置⼦控件⾼度.对于普通控件⽆效 (普通控件使⽤ geometry ⽅式设定尺⼨). |
| image | 设置⼦控件的图片.像 QSpinBox, QComboBox 等可以使⽤这个属性来设置⼦控件的图⽚ |

1.6.3:单选框

代码示例:自定义单选框

创建 resource.qrc 文件, 并导入以下图片.


在界面上创建两个单选按钮.

  • 在 Qt Designer 中编写样式.
  • 此处为了让所有 QRadioButton 都能生效, 把样式设置在 Widget 上了. 并且使⽤后代选择器选中了 QWidget ⾥⾯的 QRadioButton .

PS:
QSS 中有些属性, 子元素能继承父元素(例如 font-size, color 等). 但是也有很多属性是不能继 承的.具体哪些能继承哪些不能继承, 规则⽐较复杂, 咱们不去具体研究. 实践中我们编写更精准的选择器是上策.

运行程序, 观察效果

|------------|-----------------------------------------------------|
| 要点 | 说明 |
| :indicator | ⼦控件选择器. 选中 radioButton 中的对钩部分 |
| :hover | 伪类选择器. 选中⿏标移动上去的状态. |
| :pressed | 伪类选择器. 选中⿏标按下的状态. |
| :checked | 伪类选择器. 选中 radioButton 被选中的状态. |
| :unchecked | 伪类选择器. 选中 radioButton 未被选中的状态 |
| width | 设置⼦控件宽度. 对于普通控件⽆效 (普通控件使⽤ geometry方式设定尺⼨) |
| height | 设置⼦控件⾼度. 对于普通控件⽆效 (普通控件使⽤ geometry方式设定尺⼨) |
| image | 设置子控件的图片. 像 QSpinBox, QComboBox 等可以使⽤这个属性来设置⼦控件的图片. |

1.6.4:输入框

代码示例(自定义单行编辑框)

在界面上创建⼀个单行编辑框.

在 Qt Designer 中编写样式.

cpp 复制代码
QLineEdit {
 	border-width: 1px; 
	border-radius: 10px;
 	border-color: rgb(58, 58, 58);
	border-style: inset;
	padding: 0 8px;
	color: rgb(255, 255, 255);
	background:rgb(100, 100, 100);
	selection-background-color: rgb(170, 170, 255);
	selection-color: rgb(0, 255, 255);
}

执行程序观察效果.

|----------------------------|--------------|
| 属性 | 说明 |
| border-width | 设置边框宽度. |
| border-radius | 设置边框圆⻆. |
| border-color | 设置边框颜色. |
| border-style | 设置边框风格. |
| padding | 设置内边距. |
| color | 设置文字颜色. |
| background | 设置背景颜⾊. |
| selection-background-color | 设置选中文字的背景颜⾊. |
| selection-color | 设置选中文字的⽂本颜⾊ |

1.6.5:列表

代码示例(自定义列表框)

在界面上创建⼀个 ListWidget

添加样式

css 复制代码
QListWidget::item:hover {
 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
 stop: 0 #FAFBFE, stop: 1 #DCDEF1);
}
QListWidget::item:selected {
 border: 1px solid #6a6ea9;
 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
 stop: 0 #6a6ea9, stop: 1 #888dd9);
}

执行程序,观察结果

|-----------------|------------------------|
| 要点 | 说明 |
| ::item | 选中 QListWidget 中的具体条目. |
| :hover | 选中鼠标悬停的条目. |
| :selected | 选中某个被选中的条目. |
| background | 设置背景颜色. |
| border | 设置边框. |
| qlineargradient | 设置渐变⾊. |

1.6.6:渐变色

关于 qlineargradient,qlineargradient 有 6 个参数.

  • x1, y1: 标注了⼀个起点.
  • x2, y2: 标注了⼀个终点.

这两个点描述了⼀个 "方向".例如:

  • x1: 0, y1: 0, x2: 0, y2: 1 就是垂直方向从上向下进行颜色渐变.
  • x1: 0, y1: 0, x2: 1, y2: 0 就是水平方向从左向右进行颜色渐变.
  • x1: 0, y1: 0, x2: 1, y2: 1 就是从左上往右下方向进行颜色渐变.
  • stop0 和 stop1 描述了两个颜色. 渐变过程就是从 stop0 往 stop1 进行渐变的.
代码示例

界面不创建任何控件
编写样式

css 复制代码
QWidget {
 background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #fff, 
stop: 1 #000);
}

当前按照 垂直从上往下 从 白色 过渡到 黑色.

执行效果

修改代码

css 复制代码
QWidget {
 background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #fff, stop: 1 #000);
}

1.6.7:菜单栏

创建QMainWindow项目并且创建菜单栏

编写样式

css 复制代码
QMenuBar
{
    background-color : qlineargradient(x1 : 0, y1 : 0, x2 : 0, y2 : 1,
                                         stop : 0 lightgray, stop : 1 darkgray);
	spacing:3px; /* spacing between menu bar items */
}
QMenuBar::item
{
padding:1px 4px;
background:transparent;
border - radius : 4px;
}

QMenuBar::item : selected
{	 /* when selected using mouse or keyboard */
	
	background-color: rgb(0, 0, 255);
}

QMenuBar::item : pressed{
	background-color: rgb(85, 255, 255);
}

QMenu
{
    background-color : rgb(255,255,255);
	margin:0 2px; /* some spacing around the menu */
}
QMenu::item
{
	padding:2px 25px 2px 20px;
	border:3px solid transparent; /* reserve space for selection border */
}

QMenu::item : selected
{
    border - color : darkblue;
	background:rgba(100, 100, 100, 150);
}
QMenu::separator
{
	height:2px;
	background:lightblue;
    margin - left : 10px;
    margin - right : 5px;
}

执⾏程序, 观察效果

|-------------------------|-----------------|
| QMenuBar::item | 选中菜单栏中的元素. |
| QMenuBar::item:selected | 选中菜单来中的被选中的元素. |
| QMenuBar::item:pressed | 选中菜单栏中的⿏标点击的元素. |
| QMenu::item | 选中菜单中的元素 |
| QMenu::item:selected | 选中菜单中的被选中的元素 |
| QMenu::separator | 选中菜单中的分割线 |

1.6.8:登录界面

基于上述学习过的 QSS 样式, 制作⼀个美化版本的登录界面
1.在界面上创建元素

使用布局管理器, 把上述元素包裹⼀下.

  • 使用QVBoxLayout 来管理上述控件.
  • 两个输入框和按钮的 minimumHeight 均设置为 50. (元素在布局管理器中无法直接设置 width 和height, 使用 minimumWidth 和 minimumHeight 代替, 此时垂直方向的 sizePolicy 要设为 fixed).
  • 右键 QCheckBox , 选择 Layout Alignment 可以设置 checkbox 的对齐方式(左对齐, 居中对齐, 右对齐).

设置背景图片.
把上述控件添加⼀个父元素 QFrame, 并设置 QFrame和窗口一样大

创建resource.qrc, 并导入图片

编写 QSS 样式.
使用border-image 设置背景图片, 而不是 background-image . 主要是因为border- image 是可以自动缩放的. 这⼀点在窗口大小发生改变时是非常有意义的.

css 复制代码
QFrame {
 border-image: url(:/cat.jpg);
}

此时效果如下

设置输入框样式.

css 复制代码
QLineEdit {
	color: #8d98a1;
	background-color: #405361;
	padding: 0 5px;
	font-size: 20px;
	border-style: none;
 	border-radius: 10px;
}

运行程序效果:

设置 checkbox 样式

  • 背景色transparent表示完全透明(应用父元素的背景).
css 复制代码
QCheckBox {
 color: white;
 background-color: transparent;
}

执行效果

设置按钮样式

css 复制代码
QPushButton {
	font-size: 20px;
	color: white;
	background-color: #555;
	border-style: outset;
 	border-radius: 10px;
}

QPushButton:pressed {
 	color: black;
	background-color: #ced1db;
	border-style: inset;
}

最终完整样式代码. 这些代码设置到 QFrame 的属性中即可.

css 复制代码
QFrame {
	 border-image: url(":/cat.png");
}

QLineEdit {
	color: #8d98a1;
	background-color: #405361;
	padding: 0 5px;
	font-size: 20px;
	border-style: none;
 	border-radius: 10px;
}

QCheckBox {
 color: white;
 background-color: transparent;
}

QPushButton {
	font-size: 20px;
	color: white;
	background-color: #555;
	border-style: outset;
 	border-radius: 10px;
}

QPushButton:pressed {
 	color: black;
	background-color: #ced1db;
	border-style: inset;
}

2:绘图

2.1:基本概念

  • 虽然 Qt 已经内置了很多的控件, 但是不能保证现有控件就可以应对所有场景.很多时候我们需要更强的"自定制"能力. Qt 提供了画图相关的 API, 可以允许我们在窗口上绘制任意的图形形状, 来完成更复杂的界面设计.
  • 所谓的 "控件" , 本质上也是通过画图的方式画上去的. 画图 API 和 控件 之间的关系, 可以类比成机器指令和高级语言之间的关系. 控件是对画图 API 的进⼀步封装; 画图 API 是控件的底层实现.
    绘图API核心类

|--------------|-------------------------------------------------------------------------------------------------|
| | 说明 |
| QPainter | "绘画者" 或者 "画家".用来绘图的对象, 提供了⼀系列 drawXXX ⽅法, 可以允许我们绘制各种图形. |
| QPaintDevice | "画板".描述了 QPainter 把图形画到哪个对象上. 像咱们之前⽤过的 QWidget 也是⼀种 QPaintDevice (QWidget 是 QPaintDevice 的子类) . |
| QPen | "画笔". 描述了 QPainter 画出来的线是什么样的. |
| QBrush | "画刷". 描述了 QPainter 填充⼀个区域是什么样的. |

PS:绘图 API 的使用,一般不会在 QWidget 的构造函数中使用,而是要放到 paintEvent 事件中.

paintEvent 会在以下情况下被触发:

  • 控件首次创建.
  • 控件被遮挡, 再解除遮挡.
  • 窗口最小化, 再恢复.窗口最小化, 再恢复.
  • 控件大小发生变化时.
  • 主动调用repaint() 或者 update()方法. (这两个方法都是 QWidget 的方法).

因此, 如果把绘图 api 放到构造函数中调用, 那么一旦出现上述的情况, 界面的绘制效果就⽆法确保符合预期了.

2.2:绘制各种形状

2.2.1:代码示例一(画直线)

cpp 复制代码
void drawLine(const QPoint &p1, const QPoint &p2);
 参数:
 p1:绘制起点坐标
 p2:绘制终点坐标

1、在 "widget.h" 头文件中声明绘图事件.

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    //声明绘画事件
    void paintEvent(QPaintEvent * event);
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在 "widget.cpp" 文件中重写 paintEvent() 方法;

widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    //实例化画家对象, this:表示的是在当前窗口中绘画,即绘画设备不是指父对象
    QPainter painter(this);

    //画一条线
    painter.drawLine(QPoint(20,20),QPoint(200,20));

}

实现效果如下:

2.2.2:代码示例二(画直线)

cpp 复制代码
void drawLine ( int x1, int y1, int x2, int y2 );
参数:
x1,y1:绘制起点坐标
2,y2:绘制终点坐标
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    //实例化画家对象, this:表示的是在当前窗口中绘画,即绘画设备不是指父对象
    QPainter painter(this);

    //画一条线
    painter.drawLine(QPoint(20,20),QPoint(200,20));
    //再画一条直线
    painter.drawLine(20,100,200,100);
}

2.2.3:代码示例三(绘制矩形)

cpp 复制代码
void QPainter::drawRect(int x, int y, int width, int height);
参数:
x:窗⼝横坐标;
y:窗⼝纵坐标;
width:所绘制矩形的宽度;
height:所绘制矩形的⾼度;

1、在 "widget.h" 头文件中声明绘图事件.

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
    //声明绘画事件
    void paintEvent(QPaintEvent * event);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在 "widget.cpp" 文件中重写 paintEvent() 方法;

widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    //实例化画家对象, this:表示的是在当前窗口中绘画,即绘画设备不是指父对象
    QPainter painter(this);
    
    //绘制矩形
    painter.drawRect(20,20,100,50);
}

2.2.4:代码示例四(绘制圆形)

cpp 复制代码
void QPainter::drawEllipse(const QPoint &center, int rx, int ry)
参数:
center:中心点坐标
rx:横坐标
ry:纵坐标
widget.h

1、在 "widget.h" 头文件中声明绘图事件.

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void paintEvent(QPaintEvent *);
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp

2、在 "widget.cpp" 文件中重写 paintEvent() 方法;

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家,this:表示的是在当前窗口绘画,即绘图设备
    QPainter painter(this);
    
    //绘制圆
    painter.drawEllipse(QPoint(100,100),50,50);
    
}

2.2.5:代码示例五(绘制文本)

QPainter类中不仅提供了绘制图形的功能,还可以使用QPainter::drawText() 函数来绘制文字,也可以使用QPainter::setFont() 设置字体等信息。
1、在 "widget.h" 头文件中声明绘图事件.

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void paintEvent(QPaintEvent*);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在 "widget.cpp" 文件中重写 paintEvent() 方法;

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}
Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家,this:表示的是在当前窗口绘画,即绘图设备
    QPainter painter(this);
    //绘制圆
    painter.drawEllipse(QPoint(100,100),50,50);
}

2.2.6:设置画笔

  • QPainter 在绘制时,是有⼀个默认的画笔的。在使⽤时也可以⾃定义画笔。在 Qt 中,QPen类中 定义了 QPainter 应该如何绘制形状、线条和轮廓。同时通过 QPen类 可以设置画笔的线宽、颜色、样式、画刷 等。
  • 画笔的颜色可以在实例化画笔对象时进行设置,画笔的宽度是通过 setWidth() 方法进行设置,画笔的风格是通过setStyle()方法进行设置,设置画刷主要是通过 setBrush()方法。
    • 设置画笔颜色:QPen::QPen(const QColor & color ).
    • 设置画笔宽度:void QPen::setWidth(int width ).
  • 设置画笔风格:void QPen::setStyle(Qt::PenStyle style ).
    画笔的风格:

代码示例

1、在 "widget.h" 头文件中声明绘图事件.

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
    void paintEvent(QPaintEvent*);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在 "widget.cpp" 文件中重写 paintEvent() 方法.

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
     //实例化画家对象, this:表示的是在当前窗口中绘画,即绘画设备不是指父对象
    QPainter painter(this);
    
    //设置画笔
    QPen pen(QColor(255,0,0));
    
    //设置画笔宽度
    pen.setWidth(3);
    
    //设置画笔风格
    pen.setStyle(Qt::DashLine);
    
    //让画家使用画笔
    painter.setPen(pen);
    
    //绘制圆
    painter.drawEllipse(QPoint(100,100),50,50);
}

2.2.7:设置画刷

在 Qt 中,画刷是使用QBrush类 来描述,画刷大多用于填充。QBrush定义了QPainter的填充模式,具有样式、颜色、渐变以及纹理等属性。

画刷的格式中定义了填充的样式,使用 Qt::BrushStyle 枚举,默认值是 Qt::NoBrush,也就是不进行任何填充。可以通过 Qt 助手查找画刷的格式。如下图示:

  • 设置画刷主要通过 void QPen::setBrush(const QBrush &brush) 方法,其参数为画刷的格式。
代码示例
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void paintEvent(QPaintEvent*);
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象 this:表示的是在当前窗口中绘画,即绘画设备
    QPainter painter(this);
    //设置画笔
    QPen pen(QColor(255,0,0));
    //设置画笔宽度
    pen.setWidth(3);
    //让画家使用画笔
    painter.setPen(pen);
    //设置画刷 给封闭的图形填充颜色
    QBrush brush(Qt::cyan);
    //设置画刷风格
    brush.setStyle(Qt::Dense1Pattern);
    //让画家使用画刷
    painter.setBrush(brush);
    //绘制圆
    painter.drawEllipse(QPoint(100,100),50,50);
    
}

2.3:绘制图片

Qt 提供了四个类来处理图像数据:QImage、QPixmap、QBitmap 和 QPicture,它们都是常用的绘图设备。其中QImage主要用来进行 I/O 处理,它对 I/O 处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap 主要用来在屏幕上显示图像,它对在屏幕上显示图像进行了优化;QBitmap是QPixmap 的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演 QPainter 命令。这里博主只讲解 QPixmap.

2.3.1:绘制简单图片

在 "widget.h" 头文件中声明绘图事件.

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    //声明绘画事件
    void paintEvent(QPaintEvent * event);
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

添加资源文件.

widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    //实例化画家对象
    QPainter painter(this);

    //画图片
    painter.drawPixmap(0,0,QPixmap(":/Down.png"));
}

2.3.2:平移图片

平移图片实际是通过改变坐标来实现。QPainter类中提供了 translate()函数 来实现坐标原点的改变。

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void paintEvent(QPaintEvent *);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象
    QPainter painter(this);

    painter.translate(100,100);

    //画图片
    painter.drawPixmap(0,0,QPixmap(":/Down.png"));
}

2.3.3:缩放图片

在 Qt 中,图片的放大和缩小可以使用 QPainter类 中的 drawPixmap()函数 来实现。

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void paintEvent(QPaintEvent*);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象
    QPainter painter(this);

    //以(0,0)点开始画图,图片的尺寸跟原图保持一致
    painter.drawPixmap(0,0,QPixmap(":/Down.png"));

    //以(300,400)点开始画图,图片尺寸:50 * 60
    painter.drawPixmap(300,400,50,60,QPixmap(":/Down.png"));
}

实现效果如下:

2.3.4:旋转图片

图片的旋转使⽤的是 QPainter类中的 rotate()函数,它默认是以原点为中心进行旋转的。如果要改变旋转的中心,可以使用 translate()函数完成。

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void paintEvent(QPaintEvent*);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象
    QPainter painter(this);

    //让图片的中心作为旋转的中心
    painter.translate(200,300);

    //顺时针旋转90°
    painter.rotate(90);

    //使原点复原
    painter.translate(-200,-300);

    painter.drawPixmap(0,0,QPixmap(":/Up.png"));
}

2.4:其他设置

2.4.1:移动画家位置

有时候在绘制多个图形时,想使用同⼀坐标位置,那么绘制出来的图形肯定会重合,此时,可以通过移动画家的位置来使图形不发生重合。

代码示例1(未移动画家位置)
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);

    void paintEvent(QPaintEvent*);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widgte.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象
    QPainter painter(this);

    //画圆
    painter.drawEllipse(QPoint(100,200),50,50);
    //画圆
    painter.drawEllipse(QPoint(100,200),50,50);
}

Widget::~Widget()
{
    delete ui;
}

实现效果如下.

代码示例2(移动画家位置)
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);

    void paintEvent(QPaintEvent*);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

void Widget::paintEvent(QPaintEvent *)
{
    //实例化画家对象
    QPainter painter(this);

    //画圆
    painter.drawEllipse(QPoint(100,200),50,50);
    
    //让画家移动
    painter.translate(200,0);
    
    //画圆
    painter.drawEllipse(QPoint(100,200),50,50);
}

Widget::~Widget()
{
    delete ui;
}

实现效果如下

2.4.2:保存/加载画家的状态

在绘制图形的过程中,可以通过 save() 函数来保存画家的状态,使用restore()函数还原画家状态.

save() 函数原型如下:

cpp 复制代码
void QPainter::save();

restore() 函数原型如下:

cpp 复制代码
void QPainter::restore();

代码示例一

widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void paintEvent(QPaintEvent*);
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widgte.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    painter.drawEllipse(QPoint(100,200),50,50);
    painter.translate(200,0);
    //保存画家状态
    painter.save();
    
    painter.drawEllipse(QPoint(100,200),50,50);
    
    painter.translate(200,0);
    
    //还原画家状态
    painter.restore();
    
    painter.drawEllipse(QPoint(100,200),50,50);
}

上述示例中,在画第三个圆之前,由于还原了画家的状态,所以此时画家的位置坐标会移动到画家状态保存的地方,所以在绘制第三个圆的位置时实际是和第⼆个圆发生了重叠。

2.5:特殊的绘图设备

前图的代码中我们是使用QWidget 作为绘图设备. 在 Qt 中还存在下列三个比较特殊的绘图设备. 此处我们也简要介绍.

  • QPixmap 用于在显示器上显示图片.
  • QImage 用于对图片进行像素级修改.
  • QPicture 用于对 QPainter 的⼀系列操作进行存档.

2.5.1:QPixmap

QPixmap 核心特性:

  • 使用QPainter 直接在上面进行绘制图形.
  • 通过文件路径加载并显示图片.
  • 搭配 QPainter 的 drawPixmap()函数, 可以把这个图片绘制到⼀个 QLabel、QPushButton 等控件上.
  • 和系统/显示设备强相关, 不同系统/显示设备下, QPixmap 的显示可能会有所差别.
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPixmap>
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPixmap pix(500,500);

    //实例化画家对象
    QPainter painter(&pix);

    //设置画笔颜色
    painter.setPen(Qt::red);

    //画圆
    painter.drawEllipse(QPoint(100,100),100,100);

    //保存绘制的图片
    pix.save("D:/桌面/image/Test.png");
}

Widget::~Widget()
{
    delete ui;
}

实现效果:

2.5.2:QImage

QImage 的核心特性:

  • 使用QPainter 直接在上面进行绘制图形.
  • 通过文件路径加载并显示图片.
  • 能够针对图片进⾏像素级别的操作(操作某个指定的像素).
  • 独立于硬件的绘制系统, 能够在不同系统之上提供一致的显示.
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    QImage img(500,500,QImage::Format_RGB32);
    
    //填充色为白色,默认背景色为黑色
    img.fill(Qt::white);
    
    //声明画家,画图设备为img
    QPainter painter(&img);
    
    //设置画笔颜色为蓝绿色(青色)
    painter.setPen(QPen(Qt::cyan));
    //画图
    painter.drawEllipse(QPoint(200,200),100,100);
    
    //保存画好的图片
    img.save("D:\\桌面\\image:img.jpg");
}

Widget::~Widget()
{
    delete ui;
}

2.5.3:QPicture

QPicture 核心特性:

  • 使用QPainter 直接在上⾯进行绘制图形.
  • 通过文件路径加载并显示图片.
  • 能够记录 QPainter 的操作步骤.
  • 独立于硬件的绘制系统, 能够在不同系统之上提供⼀致的显示.

注意:
QPicture加载的必须是自身的存档文件, 而不能是任意的 png, jpg 等图片文件.

  • 如果要记录下 QPainter 的命令,首先要使用 QPainter::begin() 函数,将 QPicture 实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用 QPainter::end() 命令终止。如下示例.
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void paintEvent(QPaintEvent*);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QPicture>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPicture pic;
    QPainter painter;
    painter.begin(&pic);
    painter.setPen(QPen(Qt::red));
    painter.drawEllipse(QPoint(200,200),100,100);
    painter.end();
    pic.save("D:\\桌面\\image\\pic.pic");
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    //重现绘图指令
    QPicture pic;
    //加载图片
    pic.load("D:\\桌面\\image\\pic.pic");
    painter.drawPicture(0,0,pic);

}


通过 QPicture 重现绘图指令后,实现的效果如下:

相关推荐
清水白石0085 小时前
Python 编程实战全景:从基础语法到插件架构、异步性能与工程最佳实践
开发语言·python·架构
Halo_tjn8 小时前
Java 基于字符串相关知识点
java·开发语言·算法
梦想的颜色8 小时前
java 利用redis来限制用户频繁点击
java·开发语言
报错小能手8 小时前
Swift 并发 Combine响应式框架
开发语言·ios·swift
万法若空8 小时前
C++ <memory> 库全方位详解
开发语言·c++
代码中介商8 小时前
C++ 类型转换深度解析:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
青小莫8 小时前
C++之string(OJ练习)
开发语言·c++·stl
freshman_y8 小时前
一篇介绍C语言中二级指针和二维数组的文章
c语言·开发语言
-Marks-9 小时前
【C++编程】STL简介 --- (是什么 | 版本发展历程 | 六大组件 | 重要性缺陷以及如何学习)
开发语言·c++·学习·stl·stl版本