Qt_基础

目录

1概述

1.1 什么是QT

Qt 是一个跨平台的 C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。

1.2 QT的发展史

  • 1991年 Qt 最早由奇趣科技开发
  • 1996年 进入商业领域,它也是目前流行的 Linux 桌面环境KDE 的基础
  • 2008年 奇趣科技被诺基亚公司收购,Qt 称为诺基亚旗下的编程语言
  • 2012年 Qt 又被 Digia公司收购
  • 2014年4月 跨平台的集成开发环境Qt Crcator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt 实现了对 iOS、Android、WP 等各平台的全面支持

1.3 支持的平台

  • Windows - XP、Vista、Win7、Win8、Win2008、Winl0
  • Uinux/X11 - Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/0S、和其他很多X11平台
  • Macintosh - Mac OS X
  • Embedded- 有帧缓冲支持的嵌入式 Linux平台,Windows CE

1.4 QT版本

Qt 按照不同的版本发行,分为商业版和开源版

  • 商业版

    ​ 为商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务

  • 开源的 LGPL版本:

    ​ 为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU 通用公共许可下,它是免费的。

1.5 下载与安装

官网:https://www.qt.io/download-open-source

1.6 QT的优点

  • 跨平台,几乎支持所有的平台
  • 接口简单,容易上手,学习 QT 框架对学习其他框架有参考意义。
  • 一定程度上简化了内存回收机制
  • 开发效率高,能够快速的构建应用程序。
  • 有很好的社区氛围,市场份额在缓慢上升。
  • 可以进行嵌入式开发
  • 面向对象 :Qt 的良好封装机制使得 Qt 的模块化程度非常高,可重用性较好,对于用户开发来说 是非常方便的。QT提供了一种称为 signals/slots 的安全类型来替代 callback,这使得 各个元件之间的协同工作变得十分简单。
  • 丰富的 API:Qt 包括多达 250 个以上的 C++ 类,还提供基于模板的 collections, serialization, file, I/O device,directory management, date/time 类。甚至还 包括正则表达式的处理功能,支持 2D/3D 图形渲染,支持 OpenGL。

1.7 成功案例

  • Linux 桌面环境 KDE
  • WPS Office 办公软件
  • Skype 网络电话
  • Google Earth 谷歌地图
  • VLC 多媒体播放器
  • VirtualBox 虚拟机软件

2 创建 Qt 项目

2.1 使用向导创建

image-20230822224030263

image-20230822224140009

  • QMainWindow:带菜单栏的窗口
  • QWidget:精简版窗口
  • QDialog:对话框

image-20230822224825644

完成

image-20230822225125281

crtl+R创建

image-20230822230016809

2.2 .pro文件

在使用 Qt 向导生成的应用程序.pro 文件格式如下:

QT += core gui 		//包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 	//大于 Qt4 版本 才包含 widget 模块,低于包含在上面gui
TARGET = QtFirst 	//应用程序名 生成的.exe 程序名称
TEMPLATE = app 		//模板类型 应用程序模板
SOURCES += main.cpp\ 		//源文件
	mywidget.cpp
HEADERS += mywidget.h 	//头文件

image-20230822230746900

.pro 就是工程文件(project),它是 qmake 自动生成的用于生产 makefile 的配置文件。

.pro 文件的写法如下:

  • 注释 :从“#”开始,到这一行结束。

  • 模板变量告诉 qmake 为这个应用程序生成哪种 makefile。下面是可供使用的 选择:TEMPLATE = app

    • app -建立一个应用程序的 makefile。这是默认值,所以如果模板没有被指定,这个将被使用。

    • lib - 建立一个库的 makefile。

    • vcapp - 建立一个应用程序的 VisualStudio 项目文件。

    • vclib - 建立一个库的 VisualStudio 项目文件。

    • subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且 为一个项目文件生成 makefile 并且为它调用make 的 makefile。

  • #指定生成的应用程序名: TARGET = QtDemo

  • #工程中包含的头文件 HEADERS += include/painter.h

  • #工程中包含的.ui 设计文件 FORMS += forms/painter.ui

  • #工程中包含的源文件 SOURCES += sources/main.cpp sources

  • #工程中包含的资源文件 RESOURCES += qrc/painter.qrc

  • greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 这条语句的含义是,如果 QT_MAJOR_VERSION 大于 4(也就是当前使用的 Qt5 及更高版本)需要增加 widgets 模块。如果项目仅需支持 Qt5,也可以直接 添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照 QtCreator 生成的语句编写。

  • #配置信息

    • CONFIG 用来告诉 qmake 关于应用程序的配置信息。

    • CONFIG += c++11 //使用 c++11 的特性 。在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。

2.3 帮助文档(QTcreator自带的)

image-20230822231215973

image-20230822231333989

例如:QPushButton

image-20230822231728685

Public Functions		//公共函数
Public Slots			//公共槽函数

Header:	#include <QPushButton> 	//头文件
qmake:	QT += widgets					//所在模块
Inherits:	QAbstractButton			//父类
Inherited By:	QCommandLinkButton	//子类

2.4 QT应用程序介绍

首先,对于main函数

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
  //应用程序类,初始化我们的应用程序
    QApplication a(argc, argv);
  
  //创建一个主窗口控件
    Widget w;
  
  //显示一个窗口,hide隐藏窗口(窗口默认隐藏)
    w.show();
  
  //主事件循环,带阻塞,等待用户操作界面
    return a.exec();
}

解释:

  • Qt 系统提供的标准类名声明头文件没有.h 后缀 ,即系统头文件

  • Qt 一个类对应一个头文件,类名就是头文件名

  • QApplication 应用程序类

    • 管理图形用户界面应用程序的控制流和主要设置

    • 是 Qt 的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束, 并且提供对话管理

    • 对于任何一个使用 Qt 的图形用户界面应用程序,都正好存在一个 QApplication 对象,而不论这个应用程序在同一时间内是不是有 0、1、 2 或更多个窗口

  • a.exec() 程序:主事件循环:进入消息循环,等待对用户输入进行响应。这里 main()把控制权转交给 Qt,Qt 完成事件处理工作,当应用程序退出的时候 exec()的值就会返回。 在 exec()中,Qt 接受并处理用户和系统的事件并且把它们传递给适当的窗口部件

在实际设计界面时,我们只需要在窗口控件的构造函数中设计即可,而不在main函数中设计

image-20230822233825002

3 创建第一个小程序

3.1 按钮的创建

首先我们知道,窗口控件widget继承于QWidget

image-20230822235047086

因此我们创建按钮时,要在QWidget里寻找对应的函数

3.1.1 设置主窗口标题的函数

image-20230822234842866

setWindowTitle(const QTstring &)是在槽函数

image-20230822235257670

同时这里也可以看到,函数左边有个带凹槽的图标,即说明是槽函数

 this->setWindowTitle("The first window");

运行

image-20230822235426411

在输出为中文时,显示可能为乱码,将文件编码改为UTF-8即可

image-20230822235628969

3.1.2 固定大小函数

void setFixedSize(const QSize &s)
void setFixedSize(int w, int h)

有两个重载,我们使用第二个,指定宽高

this->setFixedSize(400,300);

image-20230823000112430

此时,鼠标在边缘无法再拖动修改大小

3.1.3 创建按钮

使用QPushButton

The QPushButton widget provides a command button. More...

Header:		#include <QPushButton> 
qmake:		QT += widgets
Inherits:	QAbstractButton
Inherited By:QCommandLinkButton

构造函数:3个重载

QPushButton(const QIcon &icon, const QString &text, QWidget *parent = nullptr)
QPushButton(const QString &text, QWidget *parent = nullptr)
  								//文本					//要放置在哪里,即哪个窗口
QPushButton(QWidget *parent = nullptr)
3.1.3.1 创建一个按钮

包含头文件后,我使用第二个

QPushButton *button= new QPushButton("button1",this);

image-20230823001234652

new完后,不需要手动 delete,关闭后将会被父类结束所有窗口

3.1.3.2 创建第二个按钮

接下来创建button2

 QPushButton *button= new QPushButton("button1",this);
 QPushButton *button2= new QPushButton("button2",this);

image-20230823001523744

发现button1消失,因为2个都是默认控件,会自动显示到窗口的左上方

这时就需要将button2移动到其他地方

3.1.3.3 移动函数

经过查询,是在QWidget类中才有move移动函数

void move(const QPoint &)
void move(int x, int y)
button2->move(200,150);

image-20230823002214268

3.1.3.4 创建第三个按钮
    //  button3
    QPushButton *button3= new QPushButton;
    button3->setParent(this);   //设置父亲,即将展示在哪个窗口
    button3->setText("button3");//设置文字
    button3->move(100,75);      //移动位置

image-20230823134146376

上面代码中,一个按钮其实就是一个 QPushButton 类下的对象,如果只是创建出对象, 是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲利用 setParent 函数即可,如果想设置按钮上显示的文字利用 setText,移动按钮位置用 move

3.1.3.5 重设大小
    //  重设大小
    this->resize(800,600);

注意:前面有setFixedSize则必须将其注释,否则大小固定

3.2 对象模型(对象树)

在 Qt 中创建对象的时候会提供一个 Parent 对象指针,下面来解释这个 parent到底是干什么的。

QObject 是以对象树的形式组织起来的。

  • 当你创建一个 QObject 对象时,会看到 QObject 的构造函数接收一个QObject 指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建 QOb.ject 对象时,可以提供一个其父对象,我们创建的这个 QObject 对象会自动添加到其父对象的 childrenO列表当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类。)
  • 这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键) 对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的

QWidget 是能够在屏幕上显示的一切组件的父类。

  • QWidget 继承自 QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
  • 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除,比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。

Qt 引入对象树的概念,在一定程度上解决了内存问题,

  • 当一个 QObject 对象在堆上创建的时候,Qt 会同时为其创建一个对象树不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
  • 任何对象树中的 QObject 对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的 children0列表中删除;如果有孩子,则自动delete 每一个孩子。Qt 保证没有 QObject 会被 delete 两次,这是由析构顺序决定的。

如果 QObject 在上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段:

{
    QWidget window;
    QPushButton quit("Quit", &window);
}

作为父组件的 window 和作为子组件的 quit 都是 QObject 的子类(事实上,它 们都是 QWidget 的子类,而 QWidget 是 QObject 的子类)。这段代码是正确的, quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应 该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。 但是,如果我们使用下面的代码

{
    QPushButton quit("Quit");
    QWidget window;
    quit.setParent(&window);
}

情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说,quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。

由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建

3.3 Qt窗口坐标体系

坐标体系: 以左上角为原点(0,0),X 向右增加,Y 向下增加

image-20230823141736425

对于嵌套窗口,其坐标是相对于父窗口来说的

4 信号和槽机制

概述:

信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当 某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣, 它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函 数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

4.1 系统自带的信号和槽

4.1.1 点击控件关闭窗口

  • 发起signal:控件
  • 关闭窗口:槽函数
  • 窗口:this,父类
  • 建立信号和槽函数的关系:connect函数
void clicked(bool checked = false)		//	发出信号
bool close()	//槽函数,执行关闭动作
connect(sender, signal, receiver, slot);
connect(信号发起者,信号,信号接收者,行为函数);

这里有小喇叭,即信号

image-20230823144849194

 //  信号:单击关闭
 connect(button2,&QPushButton::clicked,this,&QWidget::close);

单击关闭

4.2 自定义信号和槽

使用 connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还允许我们自己设计自己的信号和槽。 下面我们看看使用 Qt 的信号槽

首先创建新的工程,然后创建学生类student和老师类teacher

右击工程,点击Add new

image-20230823152316770

创建

image-20230823152343794 注意要继承QObject类,否则没有槽函数可使用

image-20230823152438140

class studnet : public QObject
{
    Q_OBJECT
public:
    explicit studnet(QObject *parent = nullptr);

signals:

};

注意:

定义信号:

  • 在signals下方
  • 返回值类型为void
  • 只需声明,不用实现
  • 可以有参数
  • 可以重载

定义槽函数:

  • 一般在public slots下方
  • 返回值类型为void
  • 需要声明和实现
  • 可以有参数
  • 可以重载

用户使用emit可以发出信号

4.2.1 无参的信号和槽

student.h里:
public slots:
    void treat();

student.cpp里
void student::treat()
{
    qDebug()<<"hello T"<<endl;
}

teacher.h里
signals:    // 自定义信号函数
    void send();
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->resize(600,400);
    teacher *tea = new teacher(this);   //实例化一个老师
    student *stu = new student(this);   //实例化一个学生
    //  建立二者之间关系
    connect(tea,&teacher::send,stu,&student::treat);
    // 此时无法触发条件
    QPushButton *btn = new QPushButton("Click",this);
    connect(btn,&QPushButton::clicked,[=](){
        // 单击后,tea发出信号
        emit tea->send();
    });
}
自定义信号和槽1

4.2.2 带参数的信号和槽

student.h里:
public slots:
    void treat();
    void treat(QString foodname);

student.cpp里
void student::treat(QString foodname)
{
    qDebug()<<"hello T:"<<foodname<<endl;
}

teacher.h里
signals:    // 自定义信号函数
    void send();
    void send(QString foodname);

widget:

#define n 0	//决定调用哪一块函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->resize(600,400);
    teacher *tea = new teacher(this);   //实例化一个老师
    student *stu = new student(this);   //实例化一个学生
    //  建立二者之间关系
    //connect(tea,&teacher::send,stu,&student::treat);

#if n       // 重载,调用无参的信号和槽
    void (teacher::*p1)() = &teacher::send;
    void (student::*p2)() = &student::treat;
    connect(tea,p1,stu,p2);

    QPushButton *btn = new QPushButton("Click",this);
    connect(btn,&QPushButton::clicked,[=](){
        // 单击后,tea发出信号
        emit tea->send();
    });
#else       // 重载,调用有参的信号和槽
    void (teacher::*p1)(QString foodname) = &teacher::send;
    void (student::*p2)(QString foodname) = &student::treat;
    connect(tea,p1,stu,p2);

    QPushButton *btn = new QPushButton("Click",this);
    connect(btn,&QPushButton::clicked,[=](){
        // 单击后,tea发出信号
        emit tea->send("joyce");
    });
#endif
}
信号和槽的重载

4.2.3 定义信号槽需要注意的事项

  • 发送者和接收者都需要是 QObject 的子类(当然,槽函数是全局函数、Lambda表达式等无需接收者的时候除外)
  • 信号和槽函数返回值是 void
  • 信号只需要声明,不需要实现
  • 槽函数需要声明也需要实现
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、 protected 的影响;
  • 使用 emit 在恰当的位置发送信号;
  • 使用 connect()函数连接信号和槽。
  • 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
  • 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
  • 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少, 即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起 来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数 的参数比信号的少)

4.3 信号槽的拓展

  • 一个信号可以和多个槽相连:如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
  • 多个信号可以连接到一个槽:只要任意一个信号发出,这个槽就会被调用
  • 一个信号可以连接到另外的一个信号:当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
  • 槽可以被取消链接:该情况并不经常出现,因为当一个对象 delete 之后,Qt 自动取消所有连接到这个对象上面的槽
  • 信号槽可以断开:利用 disconnect 关键字是可以断开信号槽的
  • 使用 Lambda 表达式:在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。在连接信号和槽的时候,槽函数可以使用 Lambda 表达式的方式进行处理

4.4 Qt4版本的信号槽写法

在Qt5中,我们刚才是这么写信号槽:

connect(tea,&teacher::send,stu,&student::treat)

而在Qt4是这样:

connect(tea,SIGNAL(send()),stu,SLOT(treat()))

这里使用了 SIGNAL 和 SLOT 这两个宏,将两个函数名转换成了字符串。注意到 connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情 况,Qt4 是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。

Qt5 在语法上完全兼容 Qt4,而反之是不可以的

Qt5 因为不接收参数,因此不能重载

例:创建2个窗口,第一个窗口有next按钮,点击进入下一个L窗口,下一个窗口有back按钮,点击回到上一个窗口

首先新建LWidget窗口类

image-20230825151256148.png

方法1

  • 在widget.cpp中实现主窗口和2个按钮的创建
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->resize(800,600);
    this->setWindowTitle("登陆界面");

    LWidget *win1=new LWidget();    //创建第二个窗口

    {       // 创建按钮1并设置属性
        QPushButton *btn1 =new QPushButton(this);
        btn1->move(400,300);
        btn1->setText("next");
            // 设置按钮1的作用
        connect(btn1,&QPushButton::clicked,[=]()
        {
           this->hide();
           win1->show();
        });
    }
    
    {       // 创建按钮2并设置属性
        QPushButton *btn2 =new QPushButton(win1);
        btn2->move(500,200);
        btn2->setText("back");
            // 设置按钮2的作用
        connect(btn2,&QPushButton::clicked,[=]()
        {
           win1->hide();
           this->show();
        });
    }
}
  • 在lwidget中设置第二个窗口的属性
LWidget::LWidget(QWidget *parent) : QWidget(parent)
{   // 设置第二个窗口的属性

    this->resize(800,600);
    this->setWindowTitle("确认界面");
}

2个窗口来回切换1

方法2

  • 先在lwidget.h中设置back信号,然后在lwidget.cpp中设置窗口属性,创建按钮2,并创建按钮2的动作:点击后发出back信号

lwidget.h:

signals:
       void back();

lwidget.cpp:

LWidget::LWidget(QWidget *parent) : QWidget(parent)
{   // 设置第二个窗口的属性

    this->resize(800,600);
    this->setWindowTitle("确认界面");

    QPushButton *btn =new QPushButton("back",this);
    btn->move(300,400);
    connect(btn,&QPushButton::clicked,[=]()
    {	// 如有点击,则发出back信号
        emit this->back();
    });
  • 在widget.cpp中实现主窗口和1个按钮的创建,并检测第2个窗口的信号
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->resize(800,600);
    this->setWindowTitle("登陆界面");

    LWidget *win1=new LWidget();    //创建第二个窗口,不在当前窗口上,因此不加this

    {       // 创建按钮1并设置属性
        QPushButton *btn1 =new QPushButton(this);
        btn1->move(400,300);
        btn1->setText("next");
            // 设置按钮1的作用
        connect(btn1,&QPushButton::clicked,[=]()
        {
           this->hide();
           win1->show();
        });
    }
    // 检测第二个窗口的行为
    connect(win1,&LWidget::back,[=](){
        win1->hide();
        this->show();
    });
}

2个窗口来回切换2

4.5 lambda表达式

C++11 中的Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。 首先看一下 Lambda表达式的基本构成:

[函数对象参数](mutable) ->返回值{函数体)
[capture](parameters) mutable ->return-type
{
		statement
}
  1. 函数对象参数:[],标识一个 Lambda 的开始,这部分必须存在,不能省略。函数对象参数 是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义 Lambda 为止时 Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类的 this)。

    函数对象参数有以下形式:

    形式作用
    没有使用任何函数对象参数
    =函数体内可以使用 Lambda 所在作用范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)
    &函数体内可以使用 Lambda 所在作用范围内所有可见的局部变量(包 括 Lambda 所在类的 this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)
    this函数体内可以使用 Lambda 所在类中的成员变量
    a将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的。要修改传递进来的 a 的拷 贝,可以添加 mutable 修饰符
    &a将 a 按引用进行传递
    a,&b将 a 按值进行传递,b 按引用进行传递。
    =,&a,&b除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
    &,a,b除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
  2. 操作符重载函数参数: 标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过 按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递

  3. 可修改标示符: mutable 声明,这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)

  4. 函数返回值: ->返回值类型,标识函数返回值的类型,当返回值为 void,或者函数体中只 有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部 分可以省略

  5. 是函数体: {},标识函数的实现,这部分不能省略,但函数体可以为空

5 QMainWindow

QMainWindow 是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、 多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar) 及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等

image-20230825175817763

5.1 菜单栏

image-20230825181728884

Qt 并没有专门的菜单项类,只是使用一个 QAction 类,抽象出公共的动作。 当我们把 QAction 对象添加到菜单,就显示成一个菜单项,添加到工具栏, 就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击 快捷键来激活这个动作。


创建工程

image-20230825180116801

注意不要选择QWidget,而是QMainWindow

5.1.1 添加菜单栏

一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。

如果这么写

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setWindowTitle("test");
    this->resize(600,400);

    //  创建一个菜单栏
    QMenuBar *menuBar=new QMenuBar(this);
}

在这里插入图片描述

菜单栏会被视为普通控件添加到主窗口的正文部分,可以被move随便移动

所以我们需要将菜单栏单独列出,添加到主窗口中

且由于是主窗口添加,所以在主窗口QMainWindow中查找对应函数

//原函数:
void QMainWindow::setMenuBar(QMenuBar *menuBar)
  
//添加
//  将菜单栏添加到主窗口
this->setMenuBar(menuBar);

5.1.2 添加菜单

同理,在Qmenu里找对应函数

//原函数:
QAction *addMenu(QMenu *menu)
QMenu *addMenu(const QString &title)
QMenu *addMenu(const QIcon &icon, const QString &title)
  
// 这里我使用第2个
    //  在菜单栏中添加菜单
    //  1、定义菜单
    QMenu *file = new QMenu("文件",this);
    QMenu *edit = new QMenu("编辑",this);
    QMenu *tool = new QMenu("工具",this);
    QMenu *setting = new QMenu("设置",this);
    QMenu *help = new QMenu("帮助",this);
    //  2、将定义的菜单加入到菜单栏
    menuBar->addMenu(file);
    menuBar->addMenu(edit);
    menuBar->addMenu(tool);
    menuBar->addMenu(setting);
    menuBar->addMenu(help);

image-20230825182255609

5.1.3 在菜单里添加菜单项

在Qmenu里找对应函数

QAction *addAction(const QString &text)
QAction *addAction(const QIcon &icon, const QString &text)
QAction *addAction(const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut = 0)
QAction *addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char *member, const QKeySequence &shortcut = 0)
QAction *addAction(const QString &text, Functor functor, const QKeySequence &shortcut = ...)
QAction *addAction(const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut = 0)
QAction *addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut = ...)
QAction *addAction(const QIcon &icon, const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut = 0)

例:

//  在菜单里添加菜单项
    //  1、定义菜单项
    QAction *newfile = new QAction("新建",this);
    QAction *save = new QAction("保存",this);
    QAction *option = new QAction("选项",this);
    QAction *test = new QAction("测试",this);
    QAction *dialog = new QAction("对话",this);

    //  2、将定义的菜单项添加到菜单
    file->addAction(newfile);
    edit->addAction(save);
    tool->addAction(test);
    setting->addAction(option);
    help->addAction(dialog);

image-20230825183240294

5.1.4 给菜单项添加快捷键

在QAction里找对应函数

//原函数
void setShortcut(const QKeySequence &shortcut)
void setShortcutContext(Qt::ShortcutContext context)
void setShortcutVisibleInContextMenu(bool show)
void setShortcuts(const QList<QKeySequence> &shortcuts)
void setShortcuts(QKeySequence::StandardKey key)

我们使用第一个

帮助里的例子:

#include <QKeySequence>
QKeySequence(QKeySequence::Print);
QKeySequence(tr("Ctrl+P"));
QKeySequence(tr("Ctrl+p"));
QKeySequence(Qt::CTRL + Qt::Key_P);

例:

    //  给菜单项里添加快捷键
    newfile->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_N));
    save->setShortcut(QKeySequence(tr("Ctrl+s")));

image-20230825184524861

image-20230825184534951

5.1.5 给菜单项添加行为

在QAction里找对应函数

//原函数:发出信号
void triggered(bool checked = false)

例:

    //  给菜单项添加行为
    connect(newfile,&QAction::triggered,[=](){
       qDebug()<<"新建文件"<<endl;
    });

为快捷键添加动作_1

5.1.5 在菜单项之间添加分隔符

    file->addAction(recent);
    file->addSeparator();   // 在同一个菜单的相邻菜单项之间添加分隔符
    file->addAction(exit);

image-20230825190551829

5.2 工具栏

image-20230825222812224

主窗口的工具栏上可以有多个工具条,通常采用一个菜单对应一个工具条的的方 式,也可根据需要进行工具条的划分

  • 直接调用 QMainWindow 类的 addToolBar () 函数获取主窗口的工具条对象,每增加一个工具条都需要调用一次该函数
  • 插入属于工具条的动作,即在工具条上添加操作。通过 QToolBar 类的 addAction 函数添加
  • 工具条是一个可移动的窗口,它的停靠区域由 QToolBar 的 allowAreas 决定

5.2.1 添加工具栏

头文件:

#include <QToolBar> 

**创建工具栏原函数:**在主窗口函数QMainWindow中查找

void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar)
void addToolBar(QToolBar *toolbar)
QToolBar *addToolBar(const QString &title)

例:

    //  创建一个工具栏
    QToolBar *toolBar = new QToolBar(this);
	 //  将工具栏添加到主窗口中,并默认出现在左侧
    this->addToolBar(Qt::LeftToolBarArea, toolBar);	

在这里插入图片描述

上下左右及中间拖动会停靠到四周或悬浮中间
在这里插入图片描述

5.2.2 添加工具栏选项

在QToolBar中查找

原函数:

QAction *addAction(const QString &text)
QAction *addAction(const QIcon &icon, const QString &text)
QAction *addAction(const QString &text, const QObject *receiver, const char *member)
QAction *addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char *member)
QAction *addAction(const QString &text, Functor functor)
QAction *addAction(const QString &text, const QObject *context, Functor functor)
QAction *addAction(const QIcon &icon, const QString &text, Functor functor)
QAction *addAction(const QIcon &icon, const QString &text, const QObject *context, Functor functor)

将上面创建的几个菜单项添加到工具栏中

    //  将菜单项添加到工具栏中
    toolBar->addAction(newfile);
    toolBar->addAction(save);
    toolBar->addSeparator();
    toolBar->addAction(test);
    toolBar->addAction(option);
    toolBar->addAction(recent);
    toolBar->addAction(exit);

image-20230825223033002

同时,由于我们刚才有设置菜单项的动作,因此点击对应的菜单项,仍可以触发动作image-20230825223229674

5.2.3 设置工具栏是否可浮动

void setFloatable(bool floatable)	//true可以浮动,false不可浮动

例:

    //  设置工具栏不可浮动
    toolBar->setFloatable(false);

禁用浮动

5.2.4 设置工具栏允许停靠的位置

包括:

allowAreas停靠区域
Qt::LeftToolBarArea停靠在左侧
Qt::RightToolBarArea停靠在右侧
Qt::TopToolBarArea停靠在顶部
Qt::BottomToolBarArea停靠在底部
Qt::AllToolBarAreas以上四个位置都可以停靠

使用 setAllowedAreas()函数指定停靠区域:

原函数:

void setAllowedAreas(Qt::ToolBarAreas areas)

例:

//  设置工具栏停靠的位置
    toolBar->setAllowedAreas(Qt::LeftToolBarArea  |Qt::RightToolBarArea);

允许工具栏停靠的位置

注意:由于默认是停靠上面,因此当我们移动工具栏后,将无法停靠在除设置外的其他位置

5.2.5 其他相关函数

使用 setMoveable()函数设定工具栏的可移动性:

setMoveable(false)		//工具条不可移动, 只能停靠在初始化的位置上

5.3 状态栏

  • 派生自 QWidget 类,使用方法与 QWidget 类似,QStatusBar 类常用成员函数:
  • 状态栏也只能最多有一个

一般下面的是状态栏

image-20230825230315765

头文件

#include <QStatusBar> 

5.3.1 创建状态栏

void setStatusBar(QStatusBar *statusbar)

例:

    //  创建一个状态栏
    QStatusBar *statusBar = new QStatusBar(this);
    //  将状态栏添加到主窗口
    this->setStatusBar(statusBar);

image-20230825230915171

5.3.2 添加标签

头文件

#include<QLabel>

添加左右控件

void addPermanentWidget(QWidget *widget, int stretch = 0)	//添加右侧控件
void addWidget(QWidget *widget, int stretch = 0)				 //添加左侧控件

例:通过控件添加到状态栏:

//  创建2个标签lable
    QLabel *label1 = new QLabel("左侧",this);
    QLabel *label2 = new QLabel("右侧",this);
    //  将标签添加到状态栏
    statusBar->addWidget(label1);
    statusBar->addPermanentWidget(label2);

image-20230825231921206

5.3.3 其他相关函数

//添加小部件
void addWidget(QWidget * widget, int stretch = 0)
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch = 0)
//删除小部件
void removeWidget(QWidget * widget)

5.4 核心部件(中心部件)

中心显示的部件可以作为核心部件,例如一个记事本文件,可以利用 QTextEdit 做核心部件

void setCentralWidget(QWidget *widget)

需要另一个部件

这里使用文本域:QTextEdit

头文件

#include<QTextEdit>

例:

    //  创建一个核心部件
    //  创建一个文本域
    QTextEdit *text = new QTextEdit(this);
    text->setText("如果阳光永远\n都炽热");//设置内容
    this->setCentralWidget(text);

image-20230825235912628

5.5 铆接部件

铆接部件 QDockWidget,也称浮动窗口,可以有多个,分布在中心内容的四周

image-20230825232339938

头文件

#include <QDockWidget>

原函数,在QMainWindow里

void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget)
void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, Qt::Orientation orientation)

铆接部件的区域

Qt::LeftDockWidgetArea	
Qt::RightDockWidgetArea
Qt::TopDockWidgetArea
Qt::BottomDockWidgetArea
Qt::AllDockWidgetAreas
Qt::NoDockWidgetArea

例:

		//  创建一个铆接部件
   QDockWidget *DockWidget = new QDockWidget("铆接部件",this);
    //  将铆接部件添加到主窗口
   this->addDockWidget(Qt::TopDockWidgetArea,DockWidget);
    //  设置允许铆接部件停靠的位置				上  下
   DockWidget->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);

铆接部件停靠的位置

5.6 资源文件

Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进 制的形式存储于可执行文件内部。

如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。 也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。 使用 Qt Creator 可以很方便地创建资源文件。

我们可以在工程上点右键,选择 “添加新文件…”,可以在 Qt 分类下找到“Qt 资源文件”

5.6.1 添加资源文件

  1. 将资源文件放入工程目录(非必要)

    image-20230826002227843

  2. 添加新文件

    image-20230826002251796

  3. 选择资源文件,一直下一步就行

    image-20230826002321541

  4. 点击添加前缀,并修改为/
    在这里插入图片描述

  5. 然后点击添加文件,并选择所有要添加的资源(必须先添加前缀)

    image-20230826002820314

  6. 最后点击左下角的构建,才算添加成功

    image-20230826002949381

    image-20230826003022616

  7. 如果后续想要继续添加,右击资源并点击open in edit打开刚才的窗口继续编辑

    image-20230826003305784

5.6.2 使用资源

给菜单项添加图标:

void setIcon(const QIcon &icon)			  //添加图标
void setIconText(const QString &text)	//添加图标文本

这里需要使用图片控件

头文件

#include <QPixmap>

添加函数load

bool load(const QString &fileName, const char *format = nullptr, Qt::ImageConversionFlags flags = Qt::AutoColor)

例:

//给菜单项添加图标:
    //  创建图片控件
   QPixmap pix;
    //  给图片控件添加资源图片
    //  :+资源的路径
   pix.load(":/logo/qq.png");
    //  将控件添加到菜单项
   newfile->setIcon(QIcon(pix));

image-20230826005019398

image-20230826005008184

6 UI界面

6.1 认识UI

可以辅助用户设计文件

创建工程

image-20230826142149181

image-20230826142640218

image-20230826142724723

image-20230826145629164

6.2 基本使用

在右下角这里

geometry	//可以修改窗口尺寸
minimumsize可 //以修改允许缩放到的最小尺寸

image-20230826144218028

windowTitle	//可以修改窗口标题
windowIcon	//可以修改窗口图标

image-20230826153000541

  • 实现添加资源后

  • 点击添加资源即可选择标题

image-20230826144449167

image-20230826144510508

  • 窗口这里,在这里输入内容回车即可自动添加菜单

image-20230826144619004

  • 点击某个菜单,右侧会提示该处对象的代码,最好修改名称否则不知道它代表哪个菜单

image-20230826144820123

image-20230826144915911

  • 然后点击某一菜单,点击输入并输入内容,即可添加菜单项

  • 填加分隔符即可添加分割符

image-20230826144659006

  • 点击某菜单项,即可在右下角修改其图标,方法同修改窗口

image-20230826145035718

  • 点击某菜单项,即可在右下角设置快捷键,双击右侧后在键盘上按下快捷键即可设置,而非手动打字输入

image-20230826145316819

image-20230826145350814

6.3 控件的使用

由于UI已经给我们生成了各个控件,因此我们不需要再生成控件,只需要对其动作做出设定即可,即设计控件的行为

  • 回到主窗口文件mainWindow.cpp中,在其构造函数中实现

image-20230826150352687

  • 添加菜单项newfile的行为
//通过UI指针成员访问UI文件上的控件,而不需要再new
    connect(ui->actionnew,&QAction::triggered,[=]()
    {
        qDebug()<<"new a file."<<endl;
    });

在这里插入图片描述

7 对话框QDialog

7.1 基本概念

对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。

Qt 中使用 QDialog 类实现对话框。就像主窗口一样,我们通常会设计一个类继 承 QDialog。QDialog(及其子类,以及所有 Qt::Dialog 类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶 层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。

对话框分为模态对话框和非模态对话框。

  • 模态对话框:就是会阻塞同一应用程序中其它窗口的输入。 模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开 文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分 进行操作的。
  • 非模态对话框:例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。

7.2 标准对话框

头文件

#include<QDialog>

所谓标准对话框,是 Qt 内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这么一个对话框

Qt 的内置对话框大致分为以下几类:

类型作用
QColorDialog选择颜色
QFileDialog选择文件或者目录
QFontDialog选择字体
QInputDialog允许用户输入一个值,并将其值返回
QMessageBox模态对话框,用于显示信息、询问问题等
QPageSetupDialog为打印机提供纸张相关的选项
QPrintDialog打印机配置
QPrintPreviewDialog打印预览
QProgressDialog显示操作过程

7.3 自定义消息框

Qt 支持模态对话框和非模态对话框。

模态与非模态的实现:

  • 使用 QDialog::exec()实现应用程序级别的模态对话框
  • 使用 QDialog::open()实现窗口级别的模态对话框
  • 使用 QDialog::show()实现非模态对话框

7.3.1 模态对话框

Qt 有两种级别的模态对话框:

  • **应用程序级别的模态:**当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关 闭对话框,然后才能访问程序中其他的窗口。
  • **窗口级别的模态:**该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它 窗口交互。窗口级别的模态尤其适用于多窗口模式。

一般默认是应用程序级别的模态

003-模态对话框

例:将模态对话框连接菜单项copy

#if n   //使对话框运行模式为模态对话框
    connect(ui->actioncopy,&QAction::triggered,[=]()
    {
        dlg->exec();
        qDebug()<<"模态对话框"<<endl;
    });

003-模态对话框连接copy

7.3.2 非模态对话框

show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。注意,dialog 是建立在栈上的,show()函数返回,MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了,我们将 dialog 改成堆上建立,就没有这个问题

003-非模态对话框

例:非模态对话框连接菜单项paste

#else   //使对话框运行模式为非模态对话框
    connect(ui->actionpaste,&QAction::triggered,[=]()
    {
        dlg->show();
        qDebug()<<"非模态对话框"<<endl;
    });
#endif

003-非模态对话框paste

7.4 消息对话框

QMessageBox 用于显示消息提示。我们一般会使用其提供的几个 static 函数

头文件

#include<QMessageBox>
  • 显示关于对话框
void about(QWidget * parent, const QString & title, const QString & text) 

这是一个最简单的对话框,其标题是 title,内容是 text,父窗口是 parent。对话框只有一个 OK 按钮。

  • 显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。
void aboutQt(QWidget * parent, const QString & title = QString());
  • 警告对话框:对话框提供一个黄色叹号图标。
StandardButton warning(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton) 

7.4.1 显示严重错误对话框

 StandardButton critical(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton);

这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明 其 显 示 的 按 钮 。 默 认 情 况 下 只 有 一 个 Ok 按 钮 , 我 们 可 以 使 用 StandardButtons 类型指定多种按钮。

例:先显示错误对话框,再显示模态对话框

#if n   //使对话框运行模式为模态对话框
    connect(ui->actioncopy,&QAction::triggered,[=]()
    {
        QMessageBox::critical(this,"error","there has been a huge error!");
        dlg->exec();
        qDebug()<<"模态对话框"<<endl;
    });

00-3先错误对话框再模态

7.4.2 信息对话框

提供一个普通信息图标

 StandardButton information(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton) 

例:同时显示非模态对话框和信息对话框

#else   //使对话框运行模式为非模态对话框
    connect(ui->actionpaste,&QAction::triggered,[=]()
    {
        dlg->show();
        qDebug()<<"非模态对话框"<<endl;
        QMessageBox::information(this,"info","there is a vital information!");
    });

003-非模态信息对话框

7.4.3 提问对话框

提供一个问号 图标,并且其显示的按钮是“是”和“否”。

StandardButton question(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = StandardButtons( Yes | No ), StandardButton defaultButton = NoButton)

例:

QMessageBox::question(this,"ques","do u want to exit?");

image-20230826165828302

同时,提问对话框的button可以修改:

ConstantValueDescription
OMessageBox::Ok0x00000400An “OK” button defined with the AcceptRole.
OMessageBox::Open0x00002000An “Open” button defined with the AcceptRole.
OMessageBox: :Save0x00000800An “Save” button defined with the AcceptRole.
QMessageBox::Cancel0x00400000A “Cancel” button defined with the RejectRole.
QMessageBox::Close0x00200000A "Close button defined with the RejectRole.
QMessageBox::Discard0x00800000A Discard" or “Don’t Save” button, depending on the platformdefined with the DestructiveRole.
OMessageBox: :Apply0x02000000An “Apply” button defined with the ApplyRole
OMessageBox: :Reset0x04000000A “Reset” button defined with the ResetRole.
QMessageBox::RestoreDefaults0x08000000A “Restore” Defaults" button defined with the ResetRole.
OMessageBox::Help0x01000000A “Help” button defined with the HelpRole.
QMessageBox: : SaveA110x00001000A “Save All” button defined with the AcceptRole.
OMessageBox: :Yes0x00004000A “Yes” button defined with the YesRole.
QMessageBox::YesToA110x00008000A “Yes to All” button defined with the YesRole.
OMessageBox::No0x00010000A “No” button defined with the NoRole.
QMessageBox::NoToA1]0x00020000A “No to All” button defined with the NoRole.
QMessageBox: :Abort0x00040000An “Abort” button defined with the RejectRole.
OMessageBox: :Retry0x00080000A “Retry” button defined with the AcceptRole
OMessageBox::Ignore0x00100000An “Ignore” button defined with the AcceptRole.
OMessageBox: :NoButton0x00000000An invalid button.

例:修改button为save和cancel

QMessageBox::question(this,"ques","tdo u want to exit?",QMessageBox::Save | QMessageBox::Cancel);

image-20230826163633244

注意,button默认是左边的,有点蓝色

例:将默认修改为右边的cancel

        QMessageBox::question(this,"ques","tdo u want to exit?",QMessageBox::Save | QMessageBox::Cancel,QMessageBox::Cancel);

image-20230826163847275

  • 同时,可以为选项添加行为
 QMessageBox::StandardButton ret ;
       ret = QMessageBox::question(this,"ques","tdo u want to exit?",
                              QMessageBox::Save | QMessageBox::Cancel,QMessageBox::Cancel);
        if (ret == QMessageBox::Save)
            qDebug()<<"保存数据!"<<endl;
        else if(ret == QMessageBox::Cancel)
            qDebug()<<"取消!"<<endl;

003-为对话框的选项添加行为

7.5 字体对话框

头文件

#include <QFontDialog> 

原函数

//获得字体
QFont getFont(bool *ok, const QFont &initial, QWidget *parent = nullptr, const QString &title = QString(), QFontDialog::FontDialogOptions options = FontDialogOptions())
QFont getFont(bool *ok, QWidget *parent = nullptr)

例:

 bool yes;
    QFontDialog::getFont(&yes,QFont("宋体"),this);

image-20230826171336283

其他函数:

QString family() const		//查看字体类型
int pointSize() const			//查看字体大小

例:

    bool yes;
    QFont font;
    font = QFontDialog::getFont(&yes,QFont("宋体"),this);
    if(yes)
    {
        qDebug()<<"字体为:"<<font.family()<<"大小为:"<<font.pointSize()<<endl;
    }

image-20230826171527923

然后点击OK

image-20230826171552310

7.6 颜色对话框

头文件

#include <QColorDialog> 

7.6.1 获取颜色函数

QColor getColor(const QColor &initial = Qt::white, QWidget *parent = nullptr, const QString &title = QString(), QColorDialog::ColorDialogOptions options = ColorDialogOptions())

例:显示颜色对话框

QColorDialog::getColor();

image-20230826172416268

7.6.2 输出对应三原色的值

    QColor color;
    color = QColorDialog::getColor();
    qDebug()<<"red:"<<color.red()<<",green:"<<color.green()<<",blue:"<<color.blue()<<endl;

image-20230826172646848

image-20230826172707393

image-20230826172729759

image-20230826172742162

7.7 标准文件对话框

QFileDialog,也就是文件对话框。在本节中,我们将尝试编写一个简单的文本文件编辑器,我们将使用 QFileDialog 来打开一个文本文件,并将修改过的文件保存到硬盘。

头文件

#include <QFileDialog>

7.7.1 获取打开的文件名

QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = Options())

QStringList getOpenFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const Q String &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = Options())

不过注意,它的所有参数都是可选的,因此在一定程度上说,这个函数也是简单的。这六个参数分别是:

  • **parent:父窗口:**Qt 的标准对话框提供静态函数,用于返回一个模态对话框;
  • caption:对话框标题;
  • dir:对话框打开时的默认目录
    • “.” 代表程序运行目录
    • “/” 代表当前盘符的根目录(特指 Windows 平台;Linux 平台当然就是根目录),这个参数也可以是平台相关的,比如“C:\”等;
  • **filter:过滤器:**我们使用文件对话框可以浏览很多类型的文件,但是,很多时候我们仅希望打开特定类型的文件。比如,文本编辑器希望打开文本文件,图片浏览器希望打开图片文件。过滤器就是用于过滤特定的后缀名。如果我们使用“ImageFiles(*.jpg*.png)”,则只能显示后缀名是 jpg 或者 png 的文件。如果需要多个过滤器,使用“;;”分割,比如“JPEG Files(*.jpg);;PNGFiles(*.png)”;
  • selectedFilter:默认选择的过滤器;
  • options:对话框的一些参数设定:比如只显示文件夹等等,它的取值是 enum QFileDialog::Option,每个选项可以使用 | 运算组合起来。

QFileDialog::getOpenFileName()返回值是选择的文件路径。我们将其赋值给filename。通过判断 filename 是否为空,可以确定用户是否选择了某一文件。只有当用户选择了一个文件时,我们才执行下面的操作

在 saveFile()中使用的 QFileDialog::getSaveFileName()也是类似的。使用这 种静态函数,在 Windows、Mac OS 上面都是直接调用本地对话框,但是 Linux 上 则是 QFileDialog 自己的模拟。这暗示了,如果你不使用这些静态函数,而是直 接使用 QFileDialog 进行设置,那么得到的对话框很可能与系统对话框的外观不 一致。这一点是需要注意的

例:随便打开个文件

    QString filename;
    filename = QFileDialog::getOpenFileName();
    qDebug()<<filename<<endl;

image-20230826175037935

成功显示

image-20230826175102536

7.7.2 添加打开时显示的指定路径

    QString filename;
    filename = 		QFileDialog::getOpenFileName(this,"file","C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\03\\logo");
   																			//注意:要用双斜线\\
	qDebug()<<filename<<endl;

image-20230826175427364

则会自动打开该路径

7.7.3 打开某路径下某类型文件

  • 创建3个txt后缀文件

image-20230826175535951

  • 修改代码
    QString filename;
    filename = QFileDialog::getOpenFileName(this,"file",
         "C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\03\\logo","(*.txt)");
    qDebug()<<filename<<endl;													// 类型

image-20230826175710882

7.7.4 设置多个类型文件同时显示

    QString filename;
    filename = QFileDialog::getOpenFileName(this,"file",
         "C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\03\\logo","(*.txt *.png)");
    qDebug()<<filename<<endl;															// 不同类型中加空格

image-20230826175815810

image-20230826180109101

7.8 下拉列表框

  • 拖入下拉列表框Combo box

image-20230827172523064

7.8.1 UI添加内容

同样的,双击可以添加内容

image-20230827172608875

7.8.2 代码添加

头文件

#include <QComboBox> 

添加项目函数

void addItem(const QString &text, const QVariant &userData = QVariant())
void addItem(const QIcon &icon, const QString &text, const QVariant &userData = QVariant())
void addItems(const QStringList &texts)		//链表,只能是string

这里我使用第三个

    //给下拉列表框添加选项
    QStringList list;
    list<<"梅赛德斯"<<"帕拉梅拉"<<"科尼赛克";
    ui->comboBox->addItems(list);

image-20230827173536958

  • 打开默认是第一个元素,我们修改默认索引

函数

void setCurrentIndex(int index)
    ui->comboBox->setCurrentIndex(2);//设置默认索引

image-20230827173749204

7.8.3 添加行为

信号

void currentIndexChanged(const QString &text)
void currentIndexChanged(int index)
  //重载
void currentTextChanged(const QString &text)

例:

    //添加行为
    void ( QComboBox::*p )(int) = &QComboBox::currentIndexChanged;
//  返回值      作用域    希望他的类型
    connect(ui->comboBox,p,[=](int index)
    {
        qDebug()<<index<<endl;  // 下标
        qDebug()<<ui->comboBox->currentIndex()<<endl; //也是下标
        
        qDebug()<<ui->comboBox->currentText()<<endl;    //文本
    });

添加行为

注意:信号发生重载,要用函数指针匹配

8 布局管理器

所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮 放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。 我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件定位的机制。

Qt 提供了两种组件定位机制:绝对定位和布局定位。

  • **绝对定位:**一种最原始的定位方法,给出这个组件的坐标和长宽值。 这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使 用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。这也很自然,因为你并没有告诉 Qt,在窗口变化时,组件是否要更新自己以及如何 更新。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。
  • **布局定位:**你只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。 布局定位完美的解决了使用绝对定位的缺陷。

Qt 提供的布局中以下三种是我们最常用的:

  • QHBoxLayout:按照水平方向从左到右布局
  • QVBoxLayout:按照竖直方向从上到下布局
  • QGridLayout:在一个网格中进行布局,类似于 HTML 的 table

8.1 系统提供的布局控件

分别是垂直布局、水平布局、栅格布局,表单布局

image-20230826231749964

同时这里有水平布局、垂直布局和栅格布局

这 4 个为系统给我们提供的布局的控件,但是使用起来不是非常的灵活,这里不再详细介绍。

8.2 利用widget做布局

8.2.1 ui布局

接下来设计一个登录窗口

image-20230826232020895

直接拖动过去即可,并输入内容

image-20230826232307548

image-20230826232338300

运行

比较丑,接下来优化

image-20230826232404039

将水平弹簧分别放在用户名、密码两侧,以及下面的三处

image-20230826232604857

然后选中一整行,点击上面的水平布局将其重新划分,下面同理

image-20230826232726817

然后随便点击空白处,再点击上面的垂直布局

image-20230826232834000

image-20230826232847949

重新分布后,运行

image-20230826232923931

随着我们拖动边缘,它的布局仍然规整

image-20230826232949568

点击某一弹簧后,可以设置其宽高

image-20230826233157995

如果要解除布局,可以点击空白处或某一控件,再点击上面的打破布局

image-20230826233251015

接下来,双击输入框,并修改名字,以方便我们识别与使用

image-20230826233925638

同理,我们把登录和注册账号按钮也制定一下

image-20230826234027893

完成后运行,我们输入用户名和密码,会发现密码可以被看到

image-20230826234144691

因此需要修改密码输入框的模式

双击密码框,右下角在echoMode里选择password

image-20230826234300774

image-20230826234327613

8.2.2 设计行为

获取user和passwd

点击登录,获取用户名和密码

QString text() const

因此我们需要QString类型的字段接收

    //点击登录,获取用户名和密码
    connect(ui->pushButton_login,&QPushButton::clicked,[=](){
        QString username = ui->lineEdit_user->text(); // 获取用户名
        QString passwd = ui->lineEdit_passwd->text(); // 获取密码
        qDebug()<<"用户名为:"<<username<<"\t密码为:"<<passwd<<endl;

image-20230826235416562

9 容器container

拖动Radio Button按钮,并输入内容,创建4个按钮

image-20230827000041542

一次性只能从4个选择一个

image-20230827000431915

接下来我们使用Group Box组,拖动出2个组,并将2组年龄和性别加入(注意:button需要先放在外面,否则会压住

image-20230827000721734

此时可以选择2组

image-20230827000841939

双击修改一下各个组件的名字,方便后面识别与使用

image-20230827001056919

9.2 设计行为

9.2.1 选中输出

选中male则输出男

    //选中则输出对应的内容
    connect(ui->radioButton_male,&QRadioButton::clicked,[=]()
    {
        qDebug()<<"男"<<endl;
    });

image-20230827001537160

9.2.2 默认选择

打开程序时默认的选择

void setChecked(bool)
 //默认选择
    ui->radioButton_female->setChecked(true);

image-20230827001922621

10 常用控件

Qt 为我们应用程序界面开发提供的一系列的控件,所有控件的使用方法我们都可以通过帮助文档获取

10.1 QLabel控件使用

10.1.1 显示文字(普通文本、html)

通过 QLabel 类的 setText 函数设置显示的内容:

void setText(const QString &)

可以显示普通文本字符串

QLable *label = new QLable; 
label->setText(“Hello, World!”);

可以显示 HTML 格式的字符串 比如显示一个链接:

QLabel * label = new QLabel(this); 
label ->setText("Hello, World"); 
label ->setText("<h1><a href=\"https://www.baidu.com\">百度一下</a></h1>");
label ->setOpenExternalLinks(true);

其中 setOpenExternalLinks()函数是用来设置用户点击链接之后是否自动 打开链接,如果参数指定为 true 则会自动打开

例:

  • 创建工程,并拖入label

image-20230827185318357


QString text() const	//获取文本函数
    //添加文本
    ui->label->setText("babe hello");
    qDebug()<<ui->label->text()<<endl;

image-20230827190212486

10.1.2 显示图片

可以使用 QLabel 的成员函数 setPixmap 设置图片

头文件

#include <QPixmap> 
void setPixmap(const QPixmap &) 
  • 首先添加资源,然后拖动一个新的label_2进入画布
    在这里插入图片描述

  • 定义 QPixmap 对象

QPixmap pixmap; 
  • 然后pixmap加载图片
bool load(const QString &fileName, const char *format = nullptr, Qt::ImageConversionFlags flags = Qt::AutoColor)

    pixmap.load(":/logo/anime.png");
  • 最后将图片设置到 label_2中
void setPixmap(const QPixmap &)

    ui->label_2->setPixmap(pixmap);

在这里插入图片描述

10.1.3 显示动画

  • 先创建新项目,并拖入label和button

在这里插入图片描述

头文件

#include <QMovie> 
  • 首先定义 QMovied 对象,并初始化:
    QMovie *movie = new QMovie(":/game.gif");
  • 使用 QLabel 的成员函数 setMovie 加载动画,可以播放 gif 格式的文件 ,movie->start(); 将动画设置到 QLabel 中:
void setMovie(QMovie * movie)
  例:
      ui->label->setMovie(movie);
  • 播放加载的动画: 绑定按钮
signal:
bool jumpToNextFrame() 	//跳到下一个动画
void setPaused(bool paused)	//暂停
void setSpeed(int percentSpeed)	//设置速度
void start()	//开始
void stop()		//停止

  例:
      //开始播放
    connect(ui->pushButton,&QPushButton::clicked,[=]()
    {
        movie->start();
    });
    //停止播放
    connect(ui->pushButton_2,&QPushButton::clicked,[=]()
    {
        movie->stop();
    });
    //开始播放
    connect(ui->pushButton_3,&QPushButton::clicked,[=]()
    {
        movie->setPaused(1);
    });
  • 运行:

播放动画

10.2 QLineEdit

Qt 提供的单行文本编辑框。

设置/获取内容

  • 获取编辑框内容使用 text(),函数声明如下:
 QString text() const 
  • 设置编辑框内容
 void setText(const QString &)

设置显示模式

  • 使用 QLineEdit 类的 setEchoMode () 函数设置文本的显示模式,函数声明:
void setEchoMode(EchoMode mode) 

EchoMode 是一个枚举类型,一共定义了四种显示模式:

  • **QLineEdit::Normal 模式显示方式:**按照输入的内容显示。
  • **QLineEdit::NoEcho 不显示任何内容:**此模式下无法看到用户的输入。
  • **QLineEdit::Password 密码模式:**输入的字符会根据平台转换为特殊字符。
  • QLineEdit::PasswordEchoOnEdit: 编辑时显示字符否则显示字符作为密码。

另外,我们再使用 QLineEdit 显示文本的时候,希望在左侧留出一段空白的区域, 那么,就可以使用 QLineEdit 给我们提供的 setTextMargins 函数:

void setTextMargins(int left, int top, int right, int bottom)

用此函数可以指定显示的文本与输入框上下左右边界的间隔的像素数。

10.3 列表控件QListWidget

这里接着容器9.2.2的部分

注意不要选错

image-20230827004228988

头文件

#include <QListWidget> 
#include <QListWidgetItem>

拖动进主窗口(注意:拖进去了后面才有listwidget,否则没有)

image-20230827005457912

添加项目函数

void addItem(const QString &label)
void addItem(QListWidgetItem *item)
void addItems(const QStringList &labels)

例:使用第二个函数

    //添加列表控件:方法2
    QListWidgetItem *item = new QListWidgetItem("多远都要在一起");
    ui->listWidget->addItem(item);

在这里插入图片描述

例:使用第三个函数:链表list

    //添加列表控件:方法3
    QStringList list;   // 使用链表
    list<<"就让我独自守着回忆"<<"如果阳光永远都炽热"<<"如果彩虹不会掉颜色";
    ui->listWidget->addItems(list);

image-20230827010301409

扩展:点击项目输出内容

image-20230827010535912

这里使用

void itemClicked(QListWidgetItem *item)

例:参数保持一致

    //点击项目输出内容
    connect(ui->listWidget,&QListWidget::itemClicked,[=](QListWidgetItem *item)
    {
        qDebug()<<item->text()<<endl;
    });

004-单击输出歌词

10.4 树控件QTreeWidget

10.4.1 ui添加

拖入树控件tree widgetimage-20230827141612166

双击树控件最上面

image-20230827142216785

双击列以修改内容,下面±可以增加或减少列

image-20230827142231965

点击项目,可以双击列下面的位置,以输入内容

紫色:可以设置子项目

image-20230827142719592

image-20230827142938239

运行

在这里插入图片描述

这种方法好处是操作方便简单,缺点是运行起来后,无法再修改

因此需要使用代码添加

10.4.2 代码添加

创建新项目,先把树控件拖入主窗口

在这里插入图片描述

头文件

#include <QTreeWidget> 

添加头信息函数

void setHeaderItem(QTreeWidgetItem *item)
void setHeaderLabel(const QString &label) //只能1个
void setHeaderLabels(const QStringList &labels) // 多个

这里使用第三个

    //设置树控件头信息
    //创建链表以存入各个头信息
    QStringList list;
    list<<"语言"<<"介绍";
    ui->treeWidget->setHeaderLabels(list);

在这里插入图片描述

接下来添加顶层控件

原函数

void addTopLevelItem(QTreeWidgetItem *item)		//单个
void addTopLevelItems(const QList<QTreeWidgetItem *> &items)	//多个

由于我们等下会对顶层控件操作,因此使用第一个逐个添加

而他需要QTreeWidgetItem类型的参数

QTreeWidgetItem(const QTreeWidgetItem &other)
QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding, int type = Type)
QTreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings, int type = Type)
QTreeWidgetItem(QTreeWidgetItem *parent, int type = Type)
QTreeWidgetItem(QTreeWidget *parent, QTreeWidgetItem *preceding, int type = Type)
QTreeWidgetItem(QTreeWidget *parent, const QStringList &strings, int type = Type)
QTreeWidgetItem(QTreeWidget *parent, int type = Type)
QTreeWidgetItem(const QStringList &strings, int type = Type)
QTreeWidgetItem(int type = Type)

我们使用匿名函数对象,注意只能写一个内容

    //添加顶层控件
    QTreeWidgetItem *item1 = new QTreeWidgetItem(QStringList()<<"Chinese");
    ui->treeWidget->addTopLevelItem(item1);
    QTreeWidgetItem *item2 = new QTreeWidgetItem(QStringList()<<"mandarin"<<"普通话");
    ui->treeWidget->addTopLevelItem(item1);
    QTreeWidgetItem *item3 = new QTreeWidgetItem(QStringList()<<"English");
    ui->treeWidget->addTopLevelItem(item1);

在这里插入图片描述

接下来添加顶层控件的子控件

来自于QTreeWidgetItem

void addChild(QTreeWidgetItem *child)
void addChildren(const QList<QTreeWidgetItem *> &children)
    //添加子控件
    //方法1:先创建再传入
    QTreeWidgetItem *child1 = new QTreeWidgetItem(QStringList()<<"简体"<<"中文(简体)");
    item1->addChild(child1);
    //方法2:创建时直接作为参数传入
    item1->addChild(new QTreeWidgetItem(QStringList()<<"繁体"<<"中文(繁体)"));
    item3->addChild(new QTreeWidgetItem(QStringList()<<"英式"<<"英语(英式)"));
    item3->addChild(new QTreeWidgetItem(QStringList()<<"美式"<<"英语(美式)"));

image-20230827150746952

10.4.3 添加行为

当点击某个项目时,我们为其添加输出内容的行为

信号

void itemClicked(QTreeWidgetItem *item, int column)	//单击信号
void itemDoubleClicked(QTreeWidgetItem *item, int column) //双击信号

注意:这里参数column相当于一个数组,0代表第0列子空控件,1代表第1列的子控件

输出内容

QString text(int column) const
    //添加行为
    connect(ui->treeWidget,&QTreeWidget::itemClicked,[](QTreeWidgetItem *item, int column)
    {
        qDebug()<<item->text(column)<<endl;
    });

shuchu

去掉双引号"",以utf8格式输出

    //添加行为
    connect(ui->treeWidget,&QTreeWidget::itemClicked,[](QTreeWidgetItem *item, int column)
    {
        qDebug()<<item->text(column).toUtf8().data()<<endl;
    });

image-20230827152218788

10.5 表格控件QLabelWidget

10.5.1 ui添加

拖入表格控件table widget

image-20230827152703779

也可以双击空白处添加内容

在这里插入图片描述

运行

在这里插入图片描述

紫色:项目

image-20230827153317451

运行

image-20230827153336018

10.5.2 代码添加

重新创建工程,拖出一个表格控件

头文件

#include <QTableWidget>

设置列/行数函数

void setColumnCount(int columns)
void setRowCount(int rows)
    //设置3列
    ui->tableWidget->setColumnCount(3);
    //设置4行
    ui->tableWidget->setRowCount(4);

image-20230827163416926

image-20230827163702751

设置水平/垂直表头信息函数

void setHorizontalHeaderItem(int column, QTableWidgetItem *item)
void setHorizontalHeaderLabels(const QStringList &labels)
  
void setVerticalHeaderItem(int row, QTableWidgetItem *item)
void setVerticalHeaderLabels(const QStringList &labels)

例:

 //添加表头信息
    ui->tableWidget->setHorizontalHeaderLabels(QStringList()<<"国家"<<"语言"<<"介绍");

image-20230827164057406

为表格添加项目

函数

void setItem(int row, int column, QTableWidgetItem *item)
  						//行				列				item

首先,向表中添加控件有2中方式:

  • 按列添加

即一列一列添加,创建一列的数据,再循环将这一列的0行数据,1行数据,2行数据…添加进去

image-20230827165637614

例:

    //向表中添加控件
    //创建一列控件
    QStringList country;
    country<<"China"<<"USA"<<"Japan"<<"UK";
    QStringList language;
    language<<"Chinese"<<"English"<<"Japanese"<<"English";
    QStringList introduce;
    introduce<<"good"<<"nice"<<"shit"<<"nice";
    //将控件按列添加进去
    for(int i=0;i<4;i++)
    {
        ui->tableWidget->setItem(i,0,new QTableWidgetItem(country[i]));
        ui->tableWidget->setItem(i,1,new QTableWidgetItem(language[i]));
        ui->tableWidget->setItem(i,2,new QTableWidgetItem(introduce[i]));
    }

image-20230827170233131

  • 按行添加

即一行数据一行数据添加,原理相同,不再演示

image-20230827170331435

10.5.3 添加行为

点击表格中的某个数据,将其内容输出,来自于QTableWidget

发出信号函数

void cellActivated(int row, int column)
void cellChanged(int row, int column)
void cellClicked(int row, int column)
void cellDoubleClicked(int row, int column)
void cellEntered(int row, int column)
void cellPressed(int row, int column)
void currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
void currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
void itemActivated(QTableWidgetItem *item)
void itemChanged(QTableWidgetItem *item)
void itemClicked(QTableWidgetItem *item)
void itemDoubleClicked(QTableWidgetItem *item)
void itemEntered(QTableWidgetItem *item)
void itemPressed(QTableWidgetItem *item)
void itemSelectionChanged()

1、这里我们使用单击信号itemClicked

例:

    //添加动作
    connect(ui->tableWidget,&QTableWidget::itemClicked,[=](QTableWidgetItem *item)
    {
       qDebug()<<item->text()<<endl;
    });

点击输出内2容

2、使用cellClicked信号以返回行列

    connect(ui->tableWidget,&QTableWidget::cellClicked,[=](int row,int col)
    {
       qDebug()<<"row:"<<row<<"col"<<col<<endl;
    });

单击输出行列

10.6 其他控件

Qt 中控件的使用方法可参考 Qt 提供的帮助文档

10.7 自定义控件

在搭建 Qt 窗口界面的时候,在一个项目中很多窗口,或者是窗口中的某个模块会被经常性的重复使用。一般遇到这种情况我们都会将这个窗口或者模块拿出来做成一个独立的窗口类,以备以后重复使用。

10.7.1 创建自定义控件类

  • 创建新工程,右击添加新文件,选择QT设计师界面类

image-20230827201131660

  • 下面选择widget即可

image-20230827201214766

  • 随便取个名

image-20230827201241779

  • mywidget就是我们自己创建的ui文件

image-20230827201332700

  • 双击进入我们创建的ui文件,添加常用控件

  • 这里我拖入spin box和水平进度条控件

image-20230827201559649

  • 布局后

image-20230827201746586

然后到其他ui里,准备使用我们的自定义控件:

10.7.2 使用自定义控件类

  • 打开其他ui,先拖入一个widget

image-20230827202232264

  • 在widget里存放控件

  • 右击widget控件,选择提升

在这里插入图片描述

  • 输入我们的自定义控件类

image-20230827202534231

  • 点击添加和提升

此时它属于我们的自定义控件类

image-20230827202610377

  • 运行

image-20230827202641042

10.7.3 添加动作

1、设置spinbox改变,导致slider滑动

spinbox:

头文件

#include <QSpinBox>

发出信号

void textChanged(const QString &text)
void valueChanged(int i)
void valueChanged(const QString &text) // 重载,需要用函数指针匹配

slider:

头文件

#include <QSlider> 

父的槽函数

void setValue(int)

在构造函数实现:且ui属于mywidget,所以要在mywidget.cpp中实现

    //重载valueChanged
    void (QSpinBox:: *p)(int) = &QSpinBox::valueChanged;
    //设置spinbox改变,导致slider滑动
    connect(ui->spinBox,p,ui->horizontalSlider,&QSlider::setValue);

spinbox导致slider

2、设置滑动slider,导致spinbox改变

spinbox:

槽函数

void setValue(int val)

slider:

发出信号

void valueChanged(int value)
    //设置滑动slider,导致spinbox改变
    connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox::setValue);

slider导致spinbox

10.7.4 设置对外接口

在主窗口中设置2个按钮:setHalf、getValue

image-20230827215932076

不过,由于这两个ui分别是不同的文件,无法直接调用

因此需要在自定义控件里设置对外接口

  • 首先,在mywidget.h的public中声明对外接口
public:
    explicit MyWidget(QWidget *parent = nullptr);
    ~MyWidget();
    
    //提供setHalf接口
    void mysetHalf(int value);
    //提供getValue接口
    int mygetValue(void);
  • 然后在mywidget.cpp中实现
void MyWidget::mysetHalf(int value)
{
    ui->horizontalSlider->setValue(value);
}

int MyWidget::mygetValue()
{
    return ui->horizontalSlider->value();
}
  • 最后在widget.cpp中调用
    //调用2个接口
    connect(ui->button_setHalf,&QPushButton::clicked,[=]()
    {
        ui->widget->mysetHalf(50);
    });

    connect(ui->button_getValue,&QPushButton::clicked,[=]()
    {
        qDebug()<<ui->widget->mygetValue()<<endl;
    });

自定义函数接口

11 Qt消息机制和事件

11.1 事件

事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、 敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发 出,如计时器事件。

在前面我们也曾经简单提到,Qt 程序需要在 main()函数创建一个 QApplication 对象,然后调用它的 exec()函数。这个函数就是开始 Qt 的主事件循环。在执行 exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时, Qt 将创建一个事件对象。Qt 中所有事件类都继承于 QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler),关于这一点,会在后边详细说明。

在所有组件的父类 QWidget 中,定义了很多事件处理的回调函数,如

virtual void keyPressEvent(QKeyEvent *event)
virtual void keyReleaseEvent(QKeyEvent *event)
virtual void leaveEvent(QEvent *event)		//鼠标离开事件
virtual void mouseDoubleClickEvent(QMouseEvent *event)
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
virtual void enterEvent(QEvent *event) //鼠标进入事件

这些函数都是 virtual 的,所以我们必须在子类中重新实现这些函数。

11.1.1 重写准备

例:重写label控件的事件,如上面的几个事件

  • 创建工程,添加label,并对label添加边框

image-20230827232608747

注意:label属于系统QLbel类,无法修改;因此如果重写QLabel事件,需要在自定义label控件中实现事件的重写,即自定义一个类,令其继承于该类

  • 所以需要在工程里新建一个类

image-20230827233208509

  • 添加名称mylabel后,父类这里没有QLabel类,因此我们先写QWidget

image-20230827233356025

image-20230827233428463

  • 修改mylabel.h文件中的QWidget为QLabel
#include <QLabel>

class mylabel : public QLabel
{
    Q_OBJECT
public:
    explicit mylabel(QWidget *parent = nullptr);

signals:

};

这时,我们制定的类mylabel继承于QLabel,这样我们才可以在mylabel中重写QLabel的事件

  • 修改mylabel.cpp文件内容
mylabel::mylabel(QWidget *parent) : QLabel(parent)
{

}
  • 注意,此时label属于QLabel,而不是我们需要的mylabel

image-20230827234432882

  • 因此需要将label的类型提升成mylabel,这样label的控件才能使用mylabel重写的事件

  • 添加内容后,添加并提升即可

image-20230827234624999

  • 提升成功

image-20230827234658829

总结:如果想重写某个类的事件,一般情况下需要自定义一个类,继承于该控件的类型,然后将控件的类提升成自定义的类,这样我们就可以在自定义类中重写控件类型事件函数

11.1.2 重写事件

QLabel的一些事件

virtual void keyPressEvent(QKeyEvent *ev) override		//键盘事件
virtual void mouseMoveEvent(QMouseEvent *ev) override //鼠标移动事件
virtual void mousePressEvent(QMouseEvent *ev) override//鼠标按压事件
virtual void mouseReleaseEvent(QMouseEvent *ev) override	//鼠标释放事件
virtual void paintEvent(QPaintEvent *) override			  //绘画事件

QWidget的一些事件

11.1.2.1 重写鼠标进入和离开事件
virtual void leaveEvent(QEvent *event) //鼠标离开事件
virtual void enterEvent(QEvent *event) //鼠标进入事件
  • mylabel.h中声明这两个事件
    virtual void leaveEvent(QEvent *event);	//鼠标离开事件
    virtual void enterEvent(QEvent *event); //鼠标进入事件
  • mylabel.cpp中实现这两个事件
void mylabel::leaveEvent(QEvent *event)
{
    qDebug()<<"mouseLeave"<<endl;
}

void mylabel::enterEvent(QEvent *event)
{
    qDebug()<<"mouseEnter"<<endl;
}

鼠标进入

11.1.2.2 重写鼠标按下和拖动事件
virtual void mouseMoveEvent(QMouseEvent *ev) override //鼠标移动事件
virtual void mousePressEvent(QMouseEvent *ev) override//鼠标按压事件
  • mylabel.h中声明这两个事件
    virtual void mouseMoveEvent(QMouseEvent *ev) override; //鼠标移动事件
    virtual void mousePressEvent(QMouseEvent *ev) override;//鼠标按压事件
  • mylabel.cpp中实现这两个事件
void mylabel::mouseMoveEvent(QMouseEvent *ev)
{
    qDebug()<<"鼠标移动"<<endl;
}

void mylabel::mousePressEvent(QMouseEvent *ev)
{
    qDebug()<<"mousePress"<<endl;
}
11.1.2.3 重写获取鼠标哪个按键按下

查看QMouseEvent类

头文件

#include <QMouseEvent>

公共函数

//判断是鼠标哪个按键
Qt::MouseButton button() const		
Qt::MouseButtons buttons() const

而Qt::MouseButton里

image-20230828003752977

例:

void mylabel::mousePressEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
        qDebug()<<"左键按下"<<endl;
    else if(ev->button() == Qt::RightButton)
        qDebug()<<"右键按下"<<endl;
}
11.1.2.4 重写获取鼠标的坐标
  //全局坐标
int globalX() const
int globalY() const
  
//局部坐标  
int x() const
int y() const

例:

void mylabel::mouseMoveEvent(QMouseEvent *ev)
{
    //默认情况鼠标按下才可移动
    qDebug()<<"鼠标移动 x="<<ev->x()<<"y="<<ev->y()<<endl;
}

void mylabel::mousePressEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
        qDebug()<<"左键按下 x="<<ev->x()<<"y="<<ev->y()<<endl;
    else if(ev->button() == Qt::RightButton)
        qDebug()<<"右键按下 x="<<ev->x()<<"y="<<ev->y()<<endl;
}

鼠标按下输出坐标

不过默认情况下,只有按下才会触发移动函数

设置鼠标跟踪函数

  • 在QWidget公共函数里,只需在mylabel的构造函数中设置
void setMouseTracking(bool enable)
  • mylabel.cpp中加入
mylabel::mylabel(QWidget *parent) : QLabel(parent)
{
    this->setMouseTracking(true);
}

此时不需要按下鼠标就能显示坐标

鼠标移动跟踪

11.2 事件分发器

事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数。 event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。

如上所述,event()函数主要用于事件的分发

image-20230828150552363

因此,如果希望在事件分发之前做一些操作,可以重写这个 event()函数,这个函数有一个 QEvent 对象作为参数,也就是需要转发的事件对象。函数返回值是 bool 类型

  • 如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。 如果返回值是 true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。

  • 在 event()函数中,调用事件对象的 accept()和 ignore()函数是没有作用的,不会影响到事件的传播。

  • 我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件

//QLabel:
virtual bool event(QEvent *e) override
  
//QEvent
Public Types
enum Type { None, ActionAdded, ActionChanged, ActionRemoved, ActivationChange, …, MaxUser }

点击

image-20230828152011433

例:在事件分发器中处理鼠标按压事件

  • 先将原来的没有重写事件分发器的鼠标按压事件加以处理以区分
void mylabel::mousePressEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton)
        qDebug()<<"mousePressEvent左键按下 x="<<ev->x()<<"y="<<ev->y()<<endl;
    else if(ev->button() == Qt::RightButton)
        qDebug()<<"mousePressEvent右键按下 x="<<ev->x()<<"y="<<ev->y()<<endl;
}
image-20230828174302504
  • mylabel.h声明重写事件分发器
    virtual bool event(QEvent *e) override;
  • mylabel.cpp中实现
bool mylabel::event(QEvent *e)
{
    //只关心mouseButtonPress事件
    if(e->type() == QEvent::MouseButtonPress)
    {
        qDebug()<<"事件分发器鼠标按下"<<endl;
        return true;
    }
    //其他事件调用父类QLabel的event处理
    return QLabel::event(e);
}
image-20230828175401222
  • 分发器里加入获取x,y坐标

注意,Event中没有对应的函数,在QMouseEvent中有,因此我们在分发器运行后将其类型转化为QMouseEvent

    if(e->type() == QEvent::MouseButtonPress)
    {
        qDebug()<<"事件分发器鼠标按下"<<endl;

        QMouseEvent *ev = static_cast<QMouseEvent *>(e);
        qDebug()<<"x = "<<ev->x()<<"y = "<<ev->y()<<endl;
        return 0;
    }

image-20230828180158688

11.3 事件过滤器

有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。

通过前面的章节,我们已经知道,Qt 创建了 QEvent 事件对象之后,会调用 QObject 的 event()函数处理事件的分发。显然,我们可以在 event()函数中实现拦截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。 如果组件很多,就需要重写很多个 event()函数。这当然相当麻烦,更不用说重 写 event()函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目 的:事件过滤器。

步骤:

  1. 加载事件过滤器
  2. 重写事件过滤器

image-20230828181831676

QObject 有一个 eventFilter()函数,用于建立事件过滤器。函数原型如下:

Public Functions:
virtual bool eventFilter(QObject *watched, QEvent *event)	//重写事件过滤器
  								//					事件触发的控件		控件产生的具体事件(鼠标按下、移动等)
void installEventFilter(QObject *filterObj)	//加载事件过滤器

这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果将参数 event 过滤出来,不想让它继续转发, 就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的 watched 对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched 对象以及以后所有的事件过滤器根本不会知道这么一个事件。

例:

  • 首先在mylabel.cpp的构造函数中加载事件过滤器
mylabel::mylabel(QWidget *parent) : QLabel(parent)
{
    this->setMouseTracking(true);//鼠标跟踪
    //加载事件过滤器
    this->installEventFilter(this);
}
  • 然后重写事件过滤器
  • 在mylabel.h中声明 事件过滤器函数
    //声明事件过滤器
    virtual bool eventFilter(QObject *watched, QEvent *event);
  • 在mylabel.cpp中实现 事件过滤器函数
//重写事件过滤器
bool mylabel::eventFilter(QObject *watched, QEvent *event)
{
    if(watched == this)
    {
        if(event->type() == QEvent::MouseButtonPress)
        {
            qDebug()<<"事件过滤器鼠标按下"<<endl;

            QMouseEvent *ev = static_cast<QMouseEvent *>(event);
            qDebug()<<"x = "<<ev->x()<<"y = "<<ev->y()<<endl;
            return 1;
        }
    }
    // 其他控件和事件:全部交给父类处理
    return QLabel::eventFilter(watched,event);
}
image-20230828183823093

注意:

事件过滤器的强大之处在于,我们可以为整个应用程序添加一个事件过滤器。 installEventFilter() 函 数 是 QObject 的 函 数 , QApplication 或 者 QCoreApplication 对象都是 QObject 的子类,因此,我们可以向 QApplication 或者 QCoreApplication 添加事件过滤器。

  • 这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个 应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话尽量不使用
  • 事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。 另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效

11.4 总结

Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂。说它复杂,更多是因为它涉及到的函数众多,而处理方法也很多,有时候让人难以选择。现在我们简单总结一下 Qt 中的事件机制。

Qt 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件 等等。如何处理这些事件,实际有两种选择:

  • 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支 语句进行选择,其代表作就是 win32 API
LRESULT CALLBACK WndProc(HWND hWnd,
              UINT message,
              WPARAM wParam,
              LPARAM lParam

在这个函数中,我们需要使用 switch 语句,选择 message 参数的类型进行处理,典型代码是:

switch(message)
{
      case WM_PAINT:
          // ...
          break;
      case WM_DESTROY:
          // ...
          break;
      ...
}
  • 每一种事件对应一个事件处理函数。Qt 就是使用的这么一种机制:

    • mouseEvent()

    • keyPressEvent()

Qt 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则,Qt 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是 event(),显然,当 QMouseEvent 产生之后,event()函数将其分发给 mouseEvent()事件处理器进行处理。

而event()函数会有两个问题:

  • event()函数是一个 protected 的函数,这意味着我们要想重写 event(), 必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有 组件都不允许处理鼠标事件,是不是我得继承所有组件,一一重写其 event() 函数?protected 函数带来的另外一个问题是,如果我基于第三方库进行开 发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么 去继承这种库中的组件呢?
  • event()函数的确有一定的控制,不过有时候我的需求更严格一些:我希望那些组件根本看不到这种事件。event()函数虽然可以拦截,但其实也是接收到了QMouseEvent 对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。这种需求怎么办呢?

这两个问题是 event()函数无法处理的。于是,Qt 提供了另外一种解决方案: 事件过滤器

事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意 QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到 QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用 installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的 eventFilter() 函数进行过滤, 其它对象不受影响。如果给QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter()函数。

事件过滤器可以解决刚刚我们提出的 event()函数的两点不足:

  • 首先,事件过滤器不是 protected 的,因此我们可以向任何 QObject 子类 安装事件过滤器;
  • 其次,事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过 滤掉,目标对象根本不会见到这个事件。

事实上,还有一种方法,我们没有介绍。Qt 事件的调用最终都会追溯到QCoreApplication::notify() 函 数 ,因此,最大的控制权实际上是重写QCoreApplication::notify()。这个函数的声明是:

virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );

该函数会将 event 发送给 receiver,也就是调用 receiver->event(event),其返回值就是来自 receiver 的事件处理器。注意,这个函数为任意线程的任意对象的任意事件调用,因此,它不存在事件过滤器的线程的问题。不过我们并不推荐这么做,因为notify()函数只有一个,而事件过滤器要灵活得多。

现在我们可以总结一下 Qt 的事件处理,实际上是有五个层次:

  • 重写 paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
  • 重写 event()函数。event()函数是所有对象的事件入口,QObject 和 QWidget 中的实现,默认是把事件传递给特定的事件处理函数。
  • 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
  • 在 QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所 有对象的所有事件,因此和 notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
  • 重写 QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器 一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为 QCoreApplication 是单例的)

12 定时器

12.1 触发方式

  • 定时器事件触发
  • 定时器对象(信号)触发
  • 静态成员函数触发

定时器事件函数

QTimer Class:Reimplemented Protected Functions
virtual void timerEvent(QTimerEvent *e) override

启动计时器函数

QObject:Public Functions
int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
int startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer)

注意:这里返回值是int类型,是定时器的ID,即一个进程可以有多个计时器

12.2 实现定时器

12.2.1 定时器事件触发

12.2.1.1 单个定时器
  • 创建工程

  • 先在widget.h中声明定时器事件

    //声明定时器事件
    virtual void timerEvent(QTimerEvent *e) override;
  • 然后在widget.cpp中实现
//重写定时器事件
void Widget::timerEvent(QTimerEvent *e)
{
    static int time =0;
    ui->label->setText(QString::number(time++));
}
  • 然后在widget.cpp的构造函数中启动定时器事件
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //启动定时器事件
    this->startTimer(1000);	//1000ms
}

statr_timer

12.2.2.2 同时启动多个定时器

返回定时器ID函数

QTimerEvent Class:Public Functions
int QTimerEvent::timerId() const
  • 创建工程
  • 在UI里拖出2个label

image-20230828212531303

  • 由于不同定时器有不同的ID,因此我们先在widget.h创建两个ID以存放定时器ID
private:
    Ui::Widget *ui;
    
    //定时器id
    int id1;
    int id2;
  • 在widget.h中声明定时器事件
    //声明定时器事件
    virtual void timerEvent(QTimerEvent *e) override;
  • 在widget.c中重写

  • 先开启2个定时器

    //开始2个定时器
    this->id1 = this->startTimer(1000);
    this->id2 = this->startTimer(2000);
  • 然后实现
//实现定时器事件
void Widget::timerEvent(QTimerEvent *e)
{
    static int num1 = 0 ;
    static int num2 = 0;

    //使用timerID函数
    if(e->timerId() == id1)
        ui->label->setText(QString::number(num1++));
    else if (e->timerId() == id2)
        ui->label_2->setText(QString::number(num2++));
}

multi_timer_1

12.2.2 定时器对象触发

头文件

#include<QTimer>
  • 先向UI中再拖入一个label和2个button_start/button_stop

image-20230828223902291

QTimer Class:Public Slots
void start()
void start(int msec)
void stop()
  
Signals
void timeout()
  • 先在构造函数中创建一个定时器对象
    // 计时器对象创建
    QTimer *time = new QTimer(this);
  • 然后给定时器添加行为:超时记录时间
    // 给定时器添加行为:超时记录时间
    connect(time,&QTimer::timeout,[=]()
    {
        static int num =0 ;
        ui->label_3->setText(QString::number(num++));
    });
  • 最后给2个button添加行为,点击start与stop
    //启动定时器
    connect(ui->button_start,&QPushButton::clicked,[=]()
    {
        time->start(1000);
    });
    //暂停定时器
    connect(ui->button_stop,&QPushButton::clicked,[=]()
    {
        time->stop();
    });

timer_by_duixiang

12.2.3 静态成员函数触发

通过singleShot实现延时功能,做完即结束,而非重复循环某事件

  • 先向UI中再拖入一个label_4

  • 在构造函数中实现

    由于是静态成员函数,所以可以通过类名称访问

//静态成员函数触发
    QTimer::singleShot(3000,[=](){
        ui->label_4->setText("Hello,my pricess.");
    });

timer_by_static_func

13 绘图和绘图设备

13.1 QPainter

Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于 QPainter,QPainterDevice 和 QPaintEngine 三个类。

  • QPainter:画笔:用来执行绘制的操作
  • QPaintDevice:画图设备:一个二维空间的抽象,这个二维空间允许 QPainter 在其上面进行绘制,也就是 QPainter 工作的空间
  • QPaintEngine:画图引擎:提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。

QPaintEngine 类应用于 QPainter 和 QPaintDevice 之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心 QPaintEngine 这个类的。我们可以把 QPainter 理解成画笔;把 QPaintDevice 理解成使用画笔的地方, 比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了 QPaintEngine 类,这个类让不同的纸张、屏幕都能使用一种画笔

下图给出了这三个类之间的层次结构:

image-20230828232319753

即使用 QPainter 在 QPainterDevice 上进行绘制,它们之间使用 QPaintEngine 进行通讯(也就是翻译 QPainter 的指令)。

注意:如果在主窗口上绘画,必须在绘画事件(paintEvent)中完成绘图

[virtual protected] void QWidget::paintEvent(QPaintEvent *event)

头文件

#include <QPaintEvent>
#include <QPainter>

绘画事件调用的时机:

  • 窗口加载
  • update()

绘图函数:

QPainter Class:Public Functions
// Arc弧
void drawArc(const QRectF &rectangle, int startAngle, int spanAngle)
void drawArc(const QRect &rectangle, int startAngle, int spanAngle)
void drawArc(int x, int y, int width, int height, int startAngle, int spanAngle)
  //chord弦
void drawChord(const QRectF &rectangle, int startAngle, int spanAngle)
void drawChord(int x, int y, int width, int height, int startAngle, int spanAngle)
void drawChord(const QRect &rectangle, int startAngle, int spanAngle)
  //ConvexPolygon凸多边形
void drawConvexPolygon(const QPointF *points, int pointCount)
void drawConvexPolygon(const QPolygonF &polygon)
void drawConvexPolygon(const QPoint *points, int pointCount)
void drawConvexPolygon(const QPolygon &polygon)
  //Ellipse椭圆
void drawEllipse(const QRectF &rectangle)
void drawEllipse(const QRect &rectangle)
void drawEllipse(int x, int y, int width, int height)
void drawEllipse(const QPointF &center, qreal rx, qreal ry)
void drawEllipse(const QPoint &center, int rx, int ry)
  //GlyphRun字形运行
void drawGlyphRun(const QPointF &position, const QGlyphRun &glyphs)
  //图像
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags = Qt::AutoColor)
void drawImage(const QRect &target, const QImage &image, const QRect &source, Qt::ImageConversionFlags flags = Qt::AutoColor)
void drawImage(const QPointF &point, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags = ...)
void drawImage(const QPoint &point, const QImage &image, const QRect &source, Qt::ImageConversionFlags flags = ...)
void drawImage(const QRectF &rectangle, const QImage &image)
void drawImage(const QRect &rectangle, const QImage &image)
void drawImage(const QPointF &point, const QImage &image)
void drawImage(const QPoint &point, const QImage &image)
void drawImage(int x, int y, const QImage &image, int sx = 0, int sy = 0, int sw = -1, int sh = -1, Qt::ImageConversionFlags flags = Qt::AutoColor)
  //单条线
void drawLine(const QLineF &line)
void drawLine(const QLine &line)
void drawLine(int x1, int y1, int x2, int y2)
void drawLine(const QPoint &p1, const QPoint &p2)
void drawLine(const QPointF &p1, const QPointF &p2)
  //线
void drawLines(const QLineF *lines, int lineCount)
void drawLines(const QVector<QLineF> &lines)
void drawLines(const QPointF *pointPairs, int lineCount)
void drawLines(const QVector<QPointF> &pointPairs)
void drawLines(const QLine *lines, int lineCount)
void drawLines(const QVector<QLine> &lines)
void drawLines(const QPoint *pointPairs, int lineCount)
void drawLines(const QVector<QPoint> &pointPairs)
  //路径
void drawPath(const QPainterPath &path)
  //图片
void drawPicture(const QPointF &point, const QPicture &picture)
void drawPicture(int x, int y, const QPicture &picture)
void drawPicture(const QPoint &point, const QPicture &picture)
  //饼图,圆
void drawPie(const QRectF &rectangle, int startAngle, int spanAngle)
void drawPie(int x, int y, int width, int height, int startAngle, int spanAngle)
void drawPie(const QRect &rectangle, int startAngle, int spanAngle)
  //像素图
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void drawPixmap(const QRect &target, const QPixmap &pixmap, const QRect &source)
void drawPixmap(int x, int y, int w, int h, const QPixmap &pixmap, int sx, int sy, int sw, int sh)
void drawPixmap(int x, int y, const QPixmap &pixmap, int sx, int sy, int sw, int sh)
void drawPixmap(const QPointF &point, const QPixmap &pixmap, const QRectF &source)
void drawPixmap(const QPoint &point, const QPixmap &pixmap, const QRect &source)
void drawPixmap(const QPointF &point, const QPixmap &pixmap)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawPixmap(int x, int y, const QPixmap &pixmap)
void drawPixmap(const QRect &rectangle, const QPixmap &pixmap)
void drawPixmap(int x, int y, int width, int height, const QPixmap &pixmap)
void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap, QPainter::PixmapFragmentHints hints = PixmapFragmentHints())
  //点
void drawPoint(const QPointF &position)
void drawPoint(const QPoint &position)
void drawPoint(int x, int y)
void drawPoints(const QPointF *points, int pointCount)
void drawPoints(const QPolygonF &points)
void drawPoints(const QPoint *points, int pointCount)
void drawPoints(const QPolygon &points)
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill)
  //Polygon多边形
void drawPolygon(const QPolygonF &points, Qt::FillRule fillRule = Qt::OddEvenFill)
void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill)
void drawPolygon(const QPolygon &points, Qt::FillRule fillRule = Qt::OddEvenFill)
  //Polyline折线
void drawPolyline(const QPointF *points, int pointCount)
void drawPolyline(const QPolygonF &points)
void drawPolyline(const QPoint *points, int pointCount)
void drawPolyline(const QPolygon &points)
  //Rect矩形
void drawRect(const QRectF &rectangle)
void drawRect(int x, int y, int width, int height)
void drawRect(const QRect &rectangle)
void drawRects(const QRectF *rectangles, int rectCount)
void drawRects(const QVector<QRectF> &rectangles)
void drawRects(const QRect *rectangles, int rectCount)
void drawRects(const QVector<QRect> &rectangles)
  //圆角矩形
void drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode = Qt::AbsoluteSize)
void drawRoundedRect(int x, int y, int w, int h, qreal xRadius, qreal yRadius, Qt::SizeMode mode = ...)
void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode = ...)
  //静态文本
void drawStaticText(const QPointF &topLeftPosition, const QStaticText &staticText)
void drawStaticText(const QPoint &topLeftPosition, const QStaticText &staticText)
void drawStaticText(int left, int top, const QStaticText &staticText)
  //文本
void drawText(const QRectF &rectangle, int flags, const QString &text, QRectF *boundingRect = nullptr)
void drawText(const QPointF &position, const QString &text)
void drawText(const QPoint &position, const QString &text)
void drawText(int x, int y, const QString &text)
void drawText(const QRect &rectangle, int flags, const QString &text, QRect *boundingRect = nullptr)
void drawText(int x, int y, int width, int height, int flags, const QString &text, QRect *boundingRect = nullptr)
void drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption())
  //TiledPixmap平铺像素图
void drawTiledPixmap(const QRectF &rectangle, const QPixmap &pixmap, const QPointF &position = QPointF())
void drawTiledPixmap(int x, int y, int width, int height, const QPixmap &pixmap, int sx = 0, int sy = 0)
void drawTiledPixmap(const QRect &rectangle, const QPixmap &pixmap, const QPoint &position = ...)

设置画笔函数

QPainter Class:Public Functions
void setPen(const QPen &pen)
void setPen(const QColor &color) //颜色
void setPen(Qt::PenStyle style)	//样式

13.1.1 窗口加载背景

例:画一个背景图

  • 创建工程
  • 首先,在widget.h声明绘图事件
    //声明绘图事件
    virtual  void paintEvent(QPaintEvent *event);
  • 添加资源图片

image-20230829000550672

  • 在widget.cpp实现绘图事件

  • 定义画笔painter

    QPainter *painter = new QPainter(this);
    
    • 使用pixmap加载资源图片,并修改图片大小
    //定义图片控件
        QPixmap pix;
        pix.load(":/benz.jpg");
        //修改图片大小
        pix.scaled(this->size());
    

    修改大小函数

QPixmap Class:Public Functions
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) const
QPixmap scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) const
  • 画笔在主窗口绘画

        //画笔在主窗口绘画
        painter->drawPixmap(0,0,this->width(),this->height(),pix);
    

    benz_amg_background

13.1.2 update()重新加载

  • 新建工程

  • 头文件

  • #include<QPainter>
    #include<QPixmap>
    
  • 首先,在widget.h声明绘图事件

    //声明绘图事件
    virtual  void paintEvent(QPaintEvent *event);
  • 添加资源图片

  • 在widget.cpp实现绘图事件

  • 定义画笔painter

    QPainter *painter = new QPainter(this);
    
    • 使用pixmap加载资源图片,并修改图片大小
    //定义图片控件
        QPixmap pix;
        pix.load(":/benz.jpg");
        //修改图片大小
        pix.scaled(this->size());
    
    • 画笔在主窗口绘画
            //画笔在主窗口绘画
            painter->drawPixmap(0,0,this->width()*0.75,this->height()*0.75,pix);
    //注意:高和长都缩小到原来的0.75倍
    

    image-20230829005825339

  • 下面添加一个button

image-20230829005900568

  • 修改button位置到中间的下方

首先我们要知道窗口和按钮的宽高

  this->width();this->height();    //窗口的宽高 
  ui->pushButton->width();ui->pushButton->height();    //按钮的宽高 

计算图

image-20230829011547336

    //修改button位置到中间的下方
    ui->pushButton->move((this->width()-ui->pushButton->width())*0.5,\
                         this->height()-ui->pushButton->height());

添加button的动作,使其被点击时图片向右移动10单位,更新显示移动后的图片

  • 修改painter函数
			 static int x =0 ;//静态成员,不会被释放;如果是普通的每次使用都是0
        //画笔在主窗口绘画
        painter->drawPixmap(x,0,this->width()*0.85,this->height()*0.85,pix);
        if(x >= this->width())//超出窗口宽度
            x=0;
        x+=10;
  • 为button按钮添加动作:点击更新图片
    connect(ui->pushButton,&QPushButton::clicked,[=]()
    {
        //按下按钮,update重新绘图
        this->update();
    });
QQ2023829-12521-HD

13.1.3 绘制其他图案

  • 新建工程
  • 首先,在widget.h声明绘图事件
    //声明绘图事件
    virtual  void paintEvent(QPaintEvent *event);
  • 在widget.cpp实现绘图事件

  • 定义画笔painter

    QPainter *painter = new QPainter(this);
    
13.1.3.1 画线
    //画线函数
    painter->drawLine(0,0,400,400);

在这里插入图片描述

13.1.3.2 画矩形rect
void drawRect(int x, int y, int width, int height)
      //画矩形rectangle
    painter->drawRect(0,0,300,300);

image-20230829125711290

13.1.3.3 修改画笔颜色
    //修改画笔颜色
    painter->setPen(Qt::red);
    painter->drawRect(100,100,300,300);//改完再画一次

image-20230829130441548

13.1.3.4 修改画笔样式

在这里插入图片描述

    //修改画笔样式
    painter->setPen(Qt::DashLine);
    painter->drawRect(200,200,200,200);

image-20230829130946569

不过只能设置颜色或样式,无法同时设置

13.1.3.5 画圆
void drawEllipse(int x, int y, int width, int height)
      //画圆
    painter->drawEllipse(300,300,300,300);

image-20230829131401066

13.1.3.6 画椭圆
    void drawEllipse(int x, int y, int width, int height)
    //画椭圆
    painter->drawEllipse(200,200,300,150);

在这里插入图片描述

13.1.3.7 画扇形
    void drawPie(int x, int y, int width, int height, int startAngle, int spanAngle) 
//画扇形
    painter->setPen(Qt::red);
    painter->drawPie(200,200,400,400,0,2000);

在这里插入图片描述

13.2 绘图设备

绘图设备是指继承 QPaintDevice 的子类。Qt 一共提供了四个这样的类,分别 是 QPixmap、QBitmap、QImage 和 QPicture。其中,

  • QPixmap:专门为图像在屏幕上的显示做了优化
  • QBitmap:QPixmap 的一个子类,它的色深限定为 1,可以使用 QPixmap 的 isQBitmap()函数来确定这个 QPixmap 是不是一个 QBitmap。
  • QImage:专门为图像的像素级访问做了优化。
  • QPicture:则可以记录和重现 QPainter 的各条命令

13.2.1 QPixmap、QBitmap、QImage

QPixmap 继承了 QPaintDevice,因此,你可以使用 QPainter 直接在上面绘制图 形。QPixmap 也可以接受一个字符串作为一个文件的路径来显示这个文件,比如你想在程序之中打开 png、jpeg 之类的文件,就可以使用 QPixmap。使用 QPainter 的 drawPixmap()函数可以把这个文件绘制到一个 QLabel、QPushButton 或者其他的设备上面。QPixmap 是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是硬件,而是操作系统提供的原 生的绘图引擎。所以,在不同的操作系统平台下,QPixmap 的显示可能会有所差别。

QBitmap 继承自 QPixmap,因此具有 QPixmap 的所有特性,提供单色图像。QBitmap 的色深始终为 1。色深这个概念来自计算机图形学,是指用于表现颜色的二进制 的位数。我们知道,计算机里面的数据都是使用二进制表示的。为了表示一种颜 色,我们也会使用二进制。比如我们要表示 8 种颜色,需要用 3 个二进制位,这 时我们就说色深是 3。因此,所谓色深为 1,也就是使用 1 个二进制位表示颜色。 1 个位只有两种状态:0 和 1,因此它所表示的颜色就有两种,黑和白。所以说, QBitmap 实际上是只有黑白两色的图像数据

由于 QBitmap 色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷

下面我们来看同一个图像文件在 QPixmap 和 QBitmap 下的不同表现:

void PaintWidget::paintEvent(QPaintEvent *)
{
    QPixmap pixmap(":/Image/butterfly.png");
    QPixmap pixmap1(":/Image/butterfly1.png");
    QBitmap bitmap(":/Image/butterfly.png");
    QBitmap bitmap1(":/Image/butterfly1.png");
    
    QPainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
    painter.drawPixmap(200, 0, pixmap1);
    painter.drawPixmap(0, 130, bitmap);
    painter.drawPixmap(200, 130, bitmap1);
}

image-20230829144533170

这里我们给出了两张 png 图片。butterfly1.png 是没有透明色的纯白背景,而 butterfly.png 是具有透明色的背景。我们分别使用 QPixmap 和 QBitmap 来加载它们。注意看它们的区别:白色的背景在 QBitmap 中消失了,而透明色在 QBitmap 中转换成了黑色;其他颜色则是使用点的疏密程度来体现的。

QPixmap 使用底层平台的绘制系统进行绘制,无法提供像素级别的操作,而 QImage 则是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且能够在不同系统之上提供一个一致的显示形式

QImage 与 QPixmap 的区别

  • QPixmap 主要是用于绘图,针对屏幕显示而最佳化设计,QImage 主要是为图 像 I/O、图片访问和像素修改而设计的
  • QPixmap 依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的 平台上可能会有不同的显示效果,QImage 使用 Qt 自身的绘图引擎,可在不 同平台上具有相同的显示效果
  • 由于 QImage 是独立于硬件的,也是一种 QPaintDevice,因此我们可以在另 一个线程中对其进行绘制,而不需要在 GUI 线程中处理,使用这一方式可以 很大幅度提高 UI 响应速度。
  • QImage 可通过 setPixpel()和pixel()等方法直接存取指定的像素。

QImage 与 QPixmap 之间的转换:

QImage 转 QPixmap :使用 QPixmap 的静态成员函数: fromImage()

QPixmap fromImage(const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor) 

QPixmap 转 QImage: 使用 QPixmap 类的成员函数: toImage()

QImage toImage() const
13.2.1.1 使用QBitmap绘图
  • 在widget.cpp构造函数中
    //定义一个绘图设备QBitmap
    QBitmap bit(800,800);
  • 定义画笔
  //定义画笔
    QPainter painter(&bit);
    painter.drawEllipse(QPoint(400,400),bit.width()/3,bit.height()/3);
  • 保存图片至某目录
    //保存图片
    bit.save("C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\11_painter\\bit01.png");
  • 运行后不是显示到屏幕上,而是保存在文件里,打开文件

在这里插入图片描述

13.2.1.2 使用QImage绘图
  • 在widget.cpp构造函数中
    //定义一个绘图设备QImage,并加载图片(提前添加资源)
    QImage img;
    img.load(":/benz_2.jpg");
  • 定义画笔
    //定义画笔
    QPainter painter2(&img);
    painter2.drawEllipse(QPoint(800,800),bit.width()/2,bit.height()/2);
  • 保存图片至某目录
    //保存图片
    img.save("C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\11_painter\\img_01.png");
  • 运行后不是显示到屏幕上,而是保存到文件里,打开文件

在这里插入图片描述

注意观察可以看到圆的边缘

接下来,我们根据QImage像素级别的操作的特性,更改图片RBG颜色

  • 在加载完图片后
 //定义一个绘图设备QImage
    QImage img;
    img.load(":/benz_2.jpg");
//由于圆心在(400,400),这里我将颜色起点设为(450,450),以便观察
    for(int i=450;i<600;i++)
    {
        for(int j=450;j<600;j++)
        {
            int val = qRgb(233,0,0);
            img.setPixel(i,j,val);
        }
    }
  • 运行

image-20230829152031544

13.2.2 QPicture

可以记录和重现 QPainter 命令的绘图设备。 QPicture 将 QPainter 的命令序列化到一个 IO 设备,保存为一个平台独立的文件格式。这种格式有时候会是“元文件(meta- files)”。Qt 的这种格式是二进制的,不同于某些本地的元文件,Qt 的 pictures 文件没有内容上的限制,只要是能够被 QPainter 绘制的元素,不论是字体还是 pixmap,或者是变换, 都可以保存进一个 picture 中。

QPicture 是平台无关的,因此它可以使用在多种设备之上,比如 svg、pdf、ps、 打印机或者屏幕。回忆下我们这里所说的 QPaintDevice,实际上是说可以有 QPainter 绘制的对象。QPicture 使用系统的分辨率,并且可以调整 QPainter 来消除不同设备之间的显示差异。

如果我们要记录下 QPainter 的命令,首先要使用 QPainter::begin()函数,将 QPicture 实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用 QPainter::end()命令终止。

例:

  • 在widget.cpp构造函数中
    //定义一个绘图设备QPicture
    QPicture pic;
    //定义画笔
    QPainter painter3;
  • 记录绘图指令
    //记录绘图指令
    painter3.begin(&pic);   //开始指令
    painter3.drawEllipse(100,100,100,100);//画图
    painter3.end();     //结束指令
  • 保存图片至某目录
    //保存指令
    pic.save("C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\11_painter\\pic01.cmd");
  • 运行后将把指令保存到文件里

在这里插入图片描述

接下来,重现绘图指令到主窗口(在绘图事件完成)

void Widget::paintEvent(QPaintEvent *event)
{
//重现画图指令:
    //创建绘图设备
    QPicture pic;
    QPainter painter(this);
    //绘图设备pic加载绘图指令
    pic.load("C:\\Users\\HUIO\\Desktop\\Me\\QT\\00\\11_painter\\pic01.cmd");
    //画笔根据绘图指令绘图
    painter.drawPicture(10,10,pic);
}

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值