QT系列(一)

QT开发入门:从安装到信号槽使用

目录

1. Qt概述

1.1 什么是Qt

  • 是一个跨平台的C++应用程序开发框架
    • 具有短平快的优秀特质: 投资少、周期短、见效快、效益高
    • 几乎支持所有的平台, 可用于桌面程序开发以及嵌入式开发
    • 有属于自己的事件处理机制
    • 可以搞效率的开发基于窗口的应用程序
  • Qt是标准 C++ 的扩展, C++的语法在Qt中都是支持的
    • 良好封装机制使得 Qt 的模块化程度非常高,可重用性较好,可以快速上手
    • Qt 提供了一种称为 signals/slots 的安全类型来替代 callback(回调函数),这使得各个元件 之间的协同工作变得十分简单。

1.2 Qt的特点

  • 广泛用于开发GUI程序,也可用于开发非GUI程序
    • GUI = Graphical User Interface
    • 也就是基于窗口的应用程序开发
  • 有丰富的 API
    • Qt 包括多达 250 个以上的 C++ 类
    • 可以处理正则表达式。
    • Qt给程序猿提供了非常详细的官方文档
  • 支持XML,Json
  • 框架底层模块化, 使用者可以根据需求选择相应的模块来使用
  • 可以轻松跨平台
  • 和Java的跨平台方式不同
  • 在不同的平台使用的是相同的上层接口,但是在底层封装了不同平台对应的API(暗度陈仓)

1.3 Qt中的模块

Qt类库里大量的类根据功能分为各种模块,这些模块又分为以下几大类:

  • Qt 基本模块(Qt Essentials):提供了 Qt 在所有平台上的基本功能。
  • Qt 附加模块(Qt Add-Ons):实现一些特定功能的提供附加价值的模块。
  • 增值模块(Value-AddModules):单独发布的提供额外价值的模块或工具。
  • 技术预览模块(Technology Preview Modules):一些处于开发阶段,但是可以作为技术预览使用的模块。 Qt 工具(Qt Tools):帮助应用程序开发的一些工具。

Qt官网或者帮助文档的“All Modules”页面可以查看所有这些模块的信息。以下是官方对Qt基本模块的描述。关于其他模块感兴趣的话可以自行查阅。

模块描述
Qt CoreQt 类库的核心,所有其他模块都依赖于此模块
Qt GUI设计 GUI 界面的基础类,包括 OpenGL
Qt Multimedia音频、视频、摄像头和广播功能的类
Qt Multimedia Widgets实现多媒体功能的界面组件类
Qt Network使网络编程更简单和轻便的类
Qt QML用于 QML 和 JavaScript语言的类
Qt Quick用于构建具有定制用户界面的动态应用程序的声明框架
Qt Quick Controls创建桌面样式用户界面,基于 Qt Quick 的用户界面控件
Qt Quick Dialogs用于 Qt Quick 的系统对话框类型
Qt Quick Layouts用于 Qt Quick 2 界面元素的布局项
Qt SQL使用 SQL 用于数据库操作的类
Qt Test用于应用程序和库进行单元测试的类
Qt Widgets用于构建 GUI 界面的 C++ 图形组件类

1.4 QT的安装

1.4.1 安装包下载

进入官网,QT 下载界面 https://download.qt.io/

进入 archive 目录文件夹
在这里插入图片描述
点击进入 在线安装包,注意,QT在5.15之后就不再支持直接下载安装,需要在线安装 。这里点击进入,选择历史版本,这里选择最新的就好
在这里插入图片描述

1.4.2 QT安装

先切换镜像源,提高下载速度

.\qt-online-installer-windows-x64-4.8.1 --mirror http://mirrors.ustc.edu.cn/qtproject/

在这里插入图片描述

邮箱登录,没有的话注册一下就行:,之后点击下一步
在这里插入图片描述
注册邮箱之后,执行切换镜像源的命令如下:
在这里插入图片描述

阅读协议以及个人用户选择,之后点击下一步
在这里插入图片描述
在这里插入图片描述
隐私与开源协议,点击下一步
加粗样式

注意,下面的安装目录不要带有中文以及空格!!!!
在这里插入图片描述

选择持续支持版本,并且进行筛选

在这里插入图片描述

选择需要的安装组件:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 注意:上面这两个编译套件一定要对应起来,版本要一样!!!
  • QT6.0以上的版本,一定要勾选 CMake xxx 以及 Ninja xxx

在这里插入图片描述

其他的选择默认就可以了

在这里插入图片描述
后面一直点击下一步即可!!!!

切换镜像源下载的很快
在这里插入图片描述

提示:如果下载完发现有些编译套件明明下载了却无法使用,大部分原因是版本不匹配,更换一下Qt的版本

在这里插入图片描述


1.4.3 补充下载

如果需要补充安装一些插件,就可以找到安装目录,如下:

在这里插入图片描述
进入当前命令下的cmd终端

MaintenanceTool --mirror http://mirrors.ustc.edu.cn/qtproject/

在这里插入图片描述
这样就算是补充下载也是使用的镜像源


1.4.4 环境变量配置

进入 QT 的安装目录,如下:在这里插入图片描述

进入 mingw_64 目录,再进入bin 目录
在这里插入图片描述
将其添加至环境变量。这样,这些动态库可以随时的再任何地点被调用。

同理,上面的 msvc2019_64、msvc2019_arm64 同理。
安装路径的根目录下的进入Tools目录,在进入下面的编译套件的bin目录下,将其加入环境变量中
在这里插入图片描述

1.5 QtCreator

  1. QtCreator是编写Qt程序默认使用的一款 IDE,使用VS写Qt程序也是可以的,在此不做介绍。
  2. 使用QtCreator创建的项目目录中不能包含中文
  3. QtCreator默认使用Utf8格式编码对文件字符进行编码
    • 字符必须编码后才能被计算机处理
    • 为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
  • GB2312 支持的汉字太少,1995年的汉字扩展规范GBK1.0,支持了更多的汉字。
  • 2000年的 GB18030取代了GBK1.0成为了正式的国家标准。
  • Unicode 也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案
    • utf8
    • utf16
  • vs写Qt程序默认使用的本地编码 -> gbk
    在这里插入图片描述

在弹出的选项窗口中设置文件编码, 默认为 utf8在这里插入图片描述

QtCreator主界面介绍

在这里插入图片描述

通过QtCreator可以 直接查阅Qt的帮助文档, 里边对Qt框架提供的API做了非常详尽的介绍, 查询方式如下图所示:在这里插入图片描述

默认的编译套件

MinGW -> Minimalist GNU for Windows

  • MinGW 提供了一套简单方便的Windows下的基于GCC 程序开发环境。MinGW 收集了一系列免费的Windows 使用的头文件和库文件;
  • 整合了GNU的工具集,特别是GNU 程序开发工具,如经典gcc, g++, make等。
  • MinGW是完全免费的自由软件,它 在Windows平台上模拟了Linux下GCC的开发环境,为C++ 的跨平台开发提供了良好基础支持

2.第一个Qt项目

2.1 创建项目

在这里插入图片描述

指定项目的存储路径

  • 项目名称根据需求自己指定即可
  • 在指定项目的存储路径的时候, 路径中不能包含中文!!!!!
    在这里插入图片描述

指定默认的窗口类的名字以及窗口的类型
在这里插入图片描述

选择编译套件, 编译套件用于项目文件的编译, 如果安装了多个编译套件, 在这里选择其中一个就可以了。 在这里插入图片描述

选择版本控制工具, 比如: git, svn 等, 可以不指定。 在这里插入图片描述

在创建完项目后如下:在这里插入图片描述

生成文件后,会依次生成 项目文件.pro 、头文件mainwindow.h 、源文件main.cpp 、mainwindow.cpp 、界面文件 mainwindow.ui ,下面会逐一得说明。

2.2 项目文件(.pro)

在创建的Qt项目中自动生成了一个 后缀为 .pro 的项目文件,该文件中记录着项目的一些属性信息,具体信息如下:

# 在项目文件中, 注释需要使用 井号(#)
# 项目编译的时候需要加载哪些底层模块
QT       += core gui 
 
# 如果当前Qt版本大于4, 会添加一个额外的模块: widgets
# Qt 5中对gui模块进行了拆分, 将 widgets 独立出来了
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
   
# 使用c++11新特性
CONFIG += c++11	
 
#如果在项目中调用了废弃的函数, 项目编译的时候会有警告的提示    
DEFINES += QT_DEPRECATED_WARNINGS
 
# 项目中的源文件
SOURCES += \
        main.cpp \
        mainwindow.cpp
        
# 项目中的头文件
HEADERS += \
        mainwindow.h
        
# 项目中的窗口界面文件
FORMS += \
        mainwindow.ui
  • greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    • 在QT4.0 的时候 GUI 和 widgets是一起的,但是在5.0之后就分出来,因此这句话的只要作用就是为了 兼容旧版本

2.3 main.cpp

在这个源文件中 有程序的入口函数 main(),下面给大家介绍下这个文件中自动生成的几行代码:

#include "mainwindow.h"		// 生成的窗口类头文件
#include <QApplication>		// 应用程序类头文件
 
int main(int argc, char *argv[])
{
    // 创建应用程序对象, 在一个Qt项目中实例对象有且仅有一个
    // 类的作用: 检测触发的事件, 进行事件循环并处理
    QApplication a(argc, argv);
    // 创建窗口类对象
    MainWindow w;
    // 显示窗口
    w.show();
    // 应用程序对象开始事件循环, 保证应用程序不退出
    return a.exec();
}

上面中定义了一个应用程序类 QApplication,这个类的使用需要包含头文件 #include <QApplication>

2.4 mainwindow.ui

  • 在Qt中 每一个窗口都对应一个可编辑的可视化界面(*.ui),这个界面对应的是一个xml格式的文件.
  • 一般情况下不需要在xml格式下对这个文件进行编辑, 关于这个文件结构了解即可。
  • 如何打开 mainwindow.ui 这个 UI 文件在这里插入图片描述
  • 这个UI文件以xml的形式打开的内容如下所示:
<!-- 双击这个文件看到的是一个窗口界面, 如果使用文本编辑器打开看到的是一个XML格式的文件 -->
<!-- 看不懂这种格式没关系, 我们不需要在这种模式下操作这个文件。 -->
<!-- 这里只是给大家介绍这个文件的本质 -->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget"/>
  <widget class="QMenuBar" name="menubar"/>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
  • 通过上面的文件内容需要知道一件事,就是当前的UI文件接口也是一个类,这个类的命名就是 MainWindow
    • 上面标签中的 <class>MainWindow</class> 就是证明

2.5 mainwindow.h

  • 这个文件是 窗口界面对应的类的头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>		// Qt标准窗口类头文件

QT_BEGIN_NAMESPACE
// mainwindow.ui 文件中也有一个类叫 MainWindow, 将这个类放到命名空间 Ui 中
namespace Ui { class MainWindow; }	
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT	// 这个宏是为了能够使用Qt中的信号槽机制

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

private:
    Ui::MainWindow *ui;		// 定义指针指向窗口的 UI 对象
};
#endif // MAINWINDOW_H
  • 一定要关注上面的代码注释
  • 上面的代码中需要注意一段代码:
  • 这里也定义了一个类也叫做 MainWindow ,但是这个类实际上是前面 mainwindow.ui 文件中那个UI接口的类
  • 同时为了防止重定义,因此利用宏定义和命名空间,将其定义在了Ui 命名空间中
    在这里插入图片描述
  • 最后在 mainwindow.h 头文件中定义了MainWindow 类,在MainWindow 类中定义了成员变量,也就是 Ui::MainWindow *ui
  • 这样 ui 文件中的 MainWindow 类与 mainwindow.h 头文件中的 MainWindow 类绑定在一起。

2.6 mainwindow.cpp

  • 这个文件是 窗口界面对应的类的源文件。
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)	// 基于mainwindow.ui创建一个实例对象
{
    // 将 mainwindow.ui 的实例对象和 当前类的对象进行关联
    // 这样同名的连个类对象就产生了关联, 合二为一了
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}
  • 在上面的构造函数的初始化列表中,构造了一个无参的 Ui::MainWindow 对象
  • 在构造函数体中还将 this 指针作为参数传入 Ui::MainWindow 对象
  • 也就是说只要构建了 MainWindow 对象,那么就会把这个对象的指针传入 Ui::MainWindow 对象
  • 综上,通过上面的方式,就将ui文件中的MainWindow 类,和继承自QMainWindow 的MainWindow 类绑定了一起。
  • 不仅可以取出ui中的设置的值再操作,也可以向ui设置值
  • 而且通过这种方式就将 窗口的桌面布局ui文件,与 这个显示的窗口界面类变成一个整体

3. Qt中的窗口类

我们在通过Qt向导窗口基于窗口的应用程序的项目过程中倒数第二步让我们选择跟随项目创建的第一个窗口的基类, 下拉菜单中有三个选项, 分别为: QMainWindow、QDialog、QWidget

3.1 基础窗口类

在这里插入图片描述

  • 常用的窗口类有3个
    • 在创建Qt窗口的时候, 需要让自己的窗口类继承上述三个窗口类的其中一个
  • QWidget
    • 所有窗口类的基类
    • Qt中的控件(按钮, 输入框, 单选框…)也属于窗口, 基类都是QWidget
    • 可以内嵌到其他窗口中: 没有边框
    • 可以不内嵌单独显示 : 独立的窗口, 有边框
  • QDialog
    • 对话框类, 后边的章节会具体介绍这个窗口
    • 不能内嵌到其他窗口中
  • QMainWindow
    • 工具栏, 状态栏, 菜单栏, 后边的章节会具体介绍这个窗口
    • 不能内嵌到其他窗口中

3.2 窗口的显示

  • 内嵌窗口
    • 依附于某一个大的窗口, 作为了大窗口的一部分
    • 大窗口就是这个内嵌窗口的父窗口
    • 父窗口显示的时候, 内嵌的窗口也就被显示出来了
  • 不内嵌窗口
    • 这类窗口有边框, 有标题栏
    • 需要调用函数才可以显示
// QWidget是所有窗口类的基类, 调用这个提供的 show() 方法就可以显示将任何窗口显示出来
// 非模态显示
void QWidget::show();	// 显示当前窗口和它的子窗口

// 对话框窗口的非模态显示: 还是调用show() 方法
// 对话框窗口的模态显示
[virtual slot] int QDialog::exec();

3.3 QWidget 使用示例

3.3.1 新建 QWidget 类

具体的添加 QWidget 的步骤如下图所示:

  1. 选中项目,右键、选中添加新文件
    在这里插入图片描述
  2. 选择QT,在窗口右侧选` QT设计师界面类 或者对应的英文在这里插入图片描述
  • 这里需要注意,一定要选择红线框得, 带头文件和源文件得设计师窗户口类
  1. 选择 Widget 窗口
    在这里插入图片描述

这里说明一下,上图中:

  • Dialog with Buttons Bottorn 代表 对话框带有底部按钮
  • Dialog with Buttons Right 代表 对话框带有右侧按钮
  • Dialog without Buttons 代表对话框没有按钮
  • Main Window 显示框,一开始创建QT工程得时候就是
  • Widget 上面Dialog 和MainWinow 的父类
  1. 给新建的Widget 窗口类起名字,这里暂且叫做 testwidget ,只要改了类名,头文件和源文件以及界面文件ui 的名称都会自动一起修改
    在这里插入图片描述
  2. 这里是将新建的 testwidget 类的源文件、头文件以及界面ui文件 一起加入 HelloQt 的工程中
    在这里插入图片描述

在添加完 widget 界面类后,逐个看一下生成的文件

3.3.2 widget 界面UI 文件

将 widget 界面UI 文件选择并右击以普通文件格式打开,会发现其实和前面的 mainwinows.ui 文件一样都是一个 xml 格式的文件
在这里插入图片描述

  • 从上图可以看到,类名就是创建时自定义的类名testwidget
  • 也就是说 这个 widget 的ui 界面,在QT中实际上也是一个类

3.3.3 widget 头文件

在这里插入图片描述
头文件代码

#ifndef TESTWIDGET_H
#define TESTWIDGET_H

#include <QWidget>

namespace Ui {
class testwidget;
}

class testwidget : public QWidget
{
    Q_OBJECT

public:
    explicit testwidget(QWidget *parent = nullptr);
    ~testwidget();

private:
    Ui::testwidget *ui;
};

#endif // TESTWIDGET_H
  • testwidget 类是 QWidget 的子类
  • 同样的为了把UI界面的testwidget 类 与当前窗口的 testwidget绑定,也是再UI空间中定义了 testwidget 类
    • 这里的绑定的方式,和前面介绍的mainwindow的绑定方式是一样的 ,详细说明请看前面的2.4、2.5、2.6小节

3.3.4 widget 源文件

源文件
在这里插入图片描述
源文件的代码

#include "testwidget.h"
#include "ui_testwidget.h"

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

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

3.3.5 widget 窗口类的使用

  • widget 窗口可以作为独立的窗口使用、也可以内嵌再mianwindow窗口中使用

主窗口中源文件的代码如下(头文件没有变动)
在这里插入图片描述

  • 注意这个代码是在创建工程时自动创建的 mainwindow 源文件中定义的,也就是如下图所示的线框框住的源文件中定义
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "testwidget.h"

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

#if 0
    // 一般在qt的构造函数中进行初始化操作(窗口、数据·····)
    // 显示在当前窗口的时候,会显示另外一个widget 窗口 ,就是刚才创建的 testwidget
    // 创建窗口对象,,没有给w对象指定父对象,这个窗口就是一个独立的窗口
    // 要显示这个窗口就必须进行 show() 操作
    testwidget * w = new testwidget;
    w->show();
}
#else

    //explicit testwidget(QWidget *parent = nullptr);
    //注意,这里相当于父类的指针指向子类的对象,这里子类对象是MainWindow的实例化对象this
    // 父类指针是QWidget *parent ,只是类似于多态,但是实际上不是多态
    // 如果创建一个窗口对象的时候,指定了父类对象,这个窗口就不是一个独立窗口
    // 这样的话当前父窗口显示的时候,子窗口就一并显示出来了
    // 在main函数中,有
        //MainWindow w;
        //w.show();
    //两句代码,就会一起调用?
    // 这时候子窗口是没有边框的,只有独立的窗口才有边框!!!!!!
    testwidget * w = new testwidget(this);


#endif

}

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

  • 在使用内嵌窗口的时候,需要注意,这里只是父类指针指向子类对象,但是并不一定时多态(具体的作者没有进行求证)
  • 在main 函数中进行调用
MainWindow w;
w.show();

注意,这里的 MainWindow对象 w 是子类的对象,不是父类的指针调用,算不上是多态的调用

  • 但是在
testwidget * w = new testwidget(this);

内部可能是使用了多态,具体作者并没有求证

独立窗口使用效果

在这里插入图片描述

内嵌窗口使用效果

在这里插入图片描述

3.4 QDialog 使用示例

  • Dialog 是对话窗口,但是它不能作为独立窗户使用,必须内嵌使用
  • 分为模态和非模态两种形式,分别对应的函数是 exec()、show()

3.4.1 Dialog ui文件

  • 这个Dialog ui 文件本质上也是一个类,这个类名和ui文件名是同名的,下图所示的例子中,这个文件叫做 testdialog
    在这里插入图片描述

3.4.2 Dialog 头文件

  • 头文件中主要负责定义,这里一定的方式和前面差不多,都是为了将 ui中的 testdialog 类和 对话框口类的testdialog(ui文件、头文件、源文件)绑定在一起

在这里插入图片描述

#ifndef TESTWIDGET_H
#define TESTWIDGET_H

#include <QWidget>

namespace Ui {
class testwidget;
}

class testwidget : public QWidget
{
    Q_OBJECT

public:
    explicit testwidget(QWidget *parent = nullptr);
    ~testwidget();

private:
    Ui::testwidget *ui;
};

#endif // TESTWIDGET_H

3.4.3 Dialog 源文件文件

  • 源文件
  • 继承自 QWidget 类
    在这里插入图片描述
  • 源代码如下
#include "testdialog.h"
#include "ui_testdialog.h"

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

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

3.4.4 使用(两种模态)

mainwindow.cpp中的代码:
在这里插入图片描述

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "testwidget.h"
#include "testdialog.h"

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


#if 0
    //创建对话框
    //explicit testdialog(QWidget *parent = nullptr);
    //这里构造函数是有参的,对照前面的类构造函数
    //可以不传入this对象,但是又内存泄漏的风险,因为这里是dlg 是指针
    testdialog * dlg = new testdialog(this);
    // 非模态
    dlg->show();

#else
     //创建对话窗口
    testdialog * dlg = new testdialog(this);
    //模态,exec();
    //阻塞程序的运行
    // dialog 是不能作为独立窗口的
    // 这是在mainwindow中的构造函数中,所以必须是先执行完dlg->exec()函数后,构造函数才算执行完
    //之后才能继续在main函数中执行 w->show()函数    
    dlg->exec();


#endif

}

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

非模态

在这里插入图片描述

模态
  1. 运行后会先展示 Dialog 窗口
    在这里插入图片描述
  2. 点击关闭Dialog 窗口后才会出现 MainWindow 窗口
    在这里插入图片描述
  • 使用模态的函数exec()的时候,该程序会阻塞在这里

    • 因为代码是出现在 MainWindow类的构造函数中
    • 相当于在MainWindow类的构造函数还没有执行完,就因为exec()函数阻塞
    • 只有关闭 Dialog对话框后才会显示MainWindow窗口
  • 也就是说,在main函数中执行 MainWindow w; 语句的时候,就已经出现了Dialog 窗口,因为此时就会调用构造函数

  • 使用w->show();函数的时候就会显示 MainWindow窗口,但是前提是已经关闭 Dialog对话框,结束exec()阻塞函数

3.4.5 主函数入口

  • 主函数中的代码不变,就是自动创建工程文件时生成的代码

在这里插入图片描述

3.5 QMainWindow 使用示例

这个在前面第二章创建QT工程的时候就已经解释过了,因此这里不在进行详细的叙述,就需要重复一下几点:

  • 这个类也是继承自 QWidget 类
  • 工具栏, 状态栏, 菜单栏 , 后边的章节会具体介绍这个窗口
  • 不能内嵌到其他窗口中

4. 坐标体系

在Qt关于窗口的显示是需要指定位置的,这个位置是通过坐标来确定的,所有坐标的选取又都是基于坐标原点来确定的,关于这些细节的确定,下面依次给大家进行讲解。

4.1 窗口的坐标原点

所有坐标的确定都需要先找到坐标原点, Qt的坐标原点在窗口的左上角

  • x轴向右递增
  • y轴向下递增
    在这里插入图片描述

4.2 窗口的相对坐标

  • 在一个Qt窗口中一般都有很多子窗口内嵌到这个父窗口中

  • 其中每个窗口都有自己的坐标原点,坐标原和图像坐标系是一样的,都选是左上角为原点在这里插入图片描述

  • 子窗口的位置(位置原点)也就是其使用的坐标点是它的父窗口坐标体系中的坐标点
    在这里插入图片描述

  • 在Qt的某一个窗口中有可能有若干个控件, 这个控件都是嵌套的关系
    • A窗口包含B窗口, B窗口包含C窗口
  • 每个窗口都有坐标原点, 在左上角
    • 子窗口的位置是基于父窗口的坐标体系来确定的, 也就是说通过父窗口左上角的坐标点来确定自己的位置
  • Qt中窗口显示的时候使用的相对坐标, 相对于自己的父窗口
  • 将子窗口移动到父窗口的某个位置

4.2.1 举例

  • 举例说明:新建一个QT工程,如下图所示:
    在这里插入图片描述
  • 在mainwindow.cpp 文件中 定义三个按钮对象,如下所示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

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

    //最外层的主窗口的位置,是相对于屏幕的坐标为(100,100)
    this->move(100,100);

    //创建一个按钮,让这个按钮为当前创建的窗口的子控件
    QPushButton* btnA = new QPushButton(this);
    //移动按扭键
    btnA->move(10,10);
    //设置这个按钮的大小
    btnA->setFixedSize(200,200);


    //创建第二个按钮,让这个按钮为前面按钮的子按钮
    QPushButton* btnB = new QPushButton(btnA);
    //移动按扭键
    btnB->move(10,10);
    //设置这个按钮的大小
    btnB->setFixedSize(100,100);


    //创建第三个按钮,让这个按钮为前面按钮btnB的子按钮
    QPushButton* btnC = new QPushButton(btnB);
    //移动按扭键
    btnC->move(10,10);
    //设置这个按钮的大小
    btnC->setFixedSize(50,50);

}

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

编译后运行如图所示:
在这里插入图片描述

5. 内存回收

5.1 对象树

在Qt中创建对象的时候会提供一个 Parent对象指针 (可以查看类的构造函数),下面来解释这个parent到底是干什么的。

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

    • 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针
    • 这相当于,在创建QObject对象时,可以提供一个其父对象,创建的这个QObject对象会自动添加到其父对象的children()列表
    • 父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!
  • QWidget是能够在屏幕上显示的一切组件的父类QWidget继承自QObject,因此也继承了这种对象树关系

    • 一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。
    • 例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
  • Qt 引入对象树的概念,在一定程度上解决了内存问题。

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

综上所述, 我们可以得到一个结论: Qt中有内存回收机制, 但是不是所有被new出的对象被自动回收, 满足条件才可以回收, 如果想要在Qt中实现内存的自动回收, 需要满足以下两个条件:

  • 创建的对象必须是QObject类的子类(间接子类也可以)
    • QObject类是没有父类的, Qt中有很大一部分类都是从这个类派生出去的
      • Qt中使用频率很高的窗口类和控件都是 QObject直接或间接的子类
      • 其他的类可以自己查阅Qt帮助文档
  • 创建出的类对象, 必须要指定其父对象是谁, 一般情况下有两种操作方式:
// 方式1: 通过构造函数
// parent: 当前窗口的父对象, 找构造函数中的 parent 参数即可
QWidget::QWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
QTimer::QTimer(QObject *parent = nullptr);

// 方式2: 通过setParent()方法
// 假设这个控件没有在构造的时候指定符对象, 可以调用QWidget的api指定父窗口对象
void QWidget::setParent(QWidget *parent);
void QObject::setParent(QObject *parent);

5.2 举例说明

  1. 创建一个新的QT工程如下图所示,subwindow窗口其实是对话窗口 Dialog 窗口
    在这里插入图片描述
  2. MainWindow.cpp 源文件中定义的内容如下
    • 在文件中定义 subwindow 对象类,并且指定父对象是实例化出的MainWindow对象 this指针
    • 表明this是此时创建出的subwindow 对象sub 的父类对象树
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "subwindow.h"

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

    //传入的this指针,实际上是实例化出的MainWindow对象
    //同时传入this,也是表明this是此时创建出的subwindow 对象的sub 的父类对象树
    subwindow * sub = new subwindow(this);

    sub->show(); // 非模态

}

MainWindow::~MainWindow()
{
    delete ui;
}
  1. 为了验证,最后在 subwindow 类的析构函数中添加提示信息,如下图所示:
    在这里插入图片描述
#include "subwindow.h"
#include "ui_subwindow.h"
#include <QDebug>

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

subwindow::~subwindow()
{
    qDebug()<<"我是subwindow,我被析构了·····";
    delete ui;
}
  1. 验证:当我们关闭父类窗口也就是Mainwindow窗口的时候,内嵌窗口Dialog也会关闭
    在这里插入图片描述
    并在应用程序输出哪里会显示析构信息
    在这里插入图片描述

文章中主要介绍了Qt中常用的数据类型, 主要内容包括: 基础数据类型、 Log日志输出、字符串类型、 QVariant、 位置和尺寸相关类型、 日期和时间相关类型

6. 基础数据类型

因为Qt是一个C++ 框架, 因此C++中所有的语法和数据类型在Qt中都是被支持的, 但是Qt中也定义了一些属于自己的数据类型, 下边给大家介绍一下这些基础的数类型。

QT基本数据类型定义在#include <QtGlobal> 中,QT基本数据类型有:

类型名称注释备注
qint8signed char有符号8位数据(1字节)
qint16signed short16位数据类型(2字节)
qint32signed int32位有符号数据类型(4字节)
qint64long long int 或(__int64)64位有符号数据类型,Windows中定义为__int64
qintptrqint32 或 qint64根据系统类型不同而不同,32位系统为qint32、64位系统为qint64,注意他不是一个指针
qlonglonglong long int 或(__int64)Windows中定义为__int64
qptrdiffqint32 或 qint64根据系统类型不同而不同,32位系统为qint32、64位系统为qint64
qrealdouble 或 float除非配置了-qreal float选项,否则默认为double
quint8unsigned char无符号8位数据类型
quint16unsigned short无符号16位数据类型
quint32unsigned int无符号32位数据类型
quint64unsigned long long int 或 (unsigned __int64)无符号64比特数据类型,Windows中定义为、unsigned __int64
quintptrquint32 或 quint64根据系统类型不同而不同,32位系统为quint32、64位系统为quint64
qulonglongunsigned long long int 或 (unsigned __int64)Windows中定义为__int64
ucharunsigned char无符号字符类型
uintunsigned int无符号整型
ulongunsigned long无符号长整型
ushortunsigned short无符号短整型
  • qintptr 实际上不是指针数据类型,如下代码所示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>

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

    int a = 20;
    qintptr q = a;
    qDebug()<<"qintptr 的值是"<<q; }

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

在这里插入图片描述

  • 虽然在Qt中有属于自己的整形或者浮点型, 但是在编程过程中这些一般不用,只要知道认识即可
  • 常用的类型关键字还是 C/C++中的 int, float, double 等。

7. log输出

7.1 在调试窗口中输入日志

在Qt中进行log输出, 一般不使用c中的printf, 也不是使用C++中的cout, Qt框架提供了专门用于日志输出的类, 头文件名为 QDebug, 使用方法如下:

// 包含了QDebug头文件, 直接通过全局函数 qDebug() 就可以进行日志输出了
qDebug() << "Date:" << QDate::currentDate();
qDebug() << "Types:" << QString("String") << QChar('x') << QRect(0, 10, 50, 40);
qDebug() << "Custom coordinate type:" << coordinate;

// 和全局函数 qDebug() 类似的日志函数还有: qWarning(), qInfo(), qCritical()
int number = 666;
float i = 11.11;
qWarning() << "Number:" << number << "Other value:" << i;
qInfo() << "Number:" << number << "Other value:" << i;
qCritical() << "Number:" << number << "Other value:" << i;

qDebug() << "我是要成为海贼王的男人!!!";
qDebug() << "我是隔壁的二柱子...";
qDebug() << "我是鸣人, 我擅长嘴遁!!!";

日志信息在IDE的调试窗口输出
在这里插入图片描述

7.2 帮助文档的使用

在这里插入图片描述

7.3 在终端窗口中输出日志

  • 使用上面的方法只能在项目调试过程中进行日志输出, 如果不是通过IDE进行程序调试, 而是直接执行可执行程序.exe文件在这种情况下是没有日志输出窗口的, 因此也就看不到任何的日志输出。
  • 默认情况下日志信息是不会打印到终端窗口的, 如果想要实现这样的效果, 必须在项目文件中添加相关的属性信息

打开项目文件(*.pro)找到配置项 config, 添加 console 控制台属性

CONFIG += c++11 console

属性信息添加完毕, 重新编译项目 日志信息就可以打印到终端窗口了

  1. 项目工程如下:在这里插入图片描述
  2. 重新构建项目:在这里插入图片描述
  3. 打开exe所在的路径,双击执行在这里插入图片描述
  4. 效果展示在这里插入图片描述

8. 字符串类型

在Qt中不仅支持C, C++中的字符串类型, 而且还在框架中定义了专属的字符串类型, 我们必须要掌握在Qt中关于这些类型的使用和相互之间的转换

语言类型字符串类型
Cchar*
C++std::string, char*
QtQByteArray, QString 等

8.1 QByteArray

  • 在Qt中QByteArray可以看做是c语言中 char*的升级版本。我们在使用这种类型的时候可通过这个类的构造函数申请一块动态内存,用于存储我们需要处理的字符串数据。
  • 下面给大家介绍一下这个类中常用的一些API函数,大家要养成遇到问题主动查询帮助文档的好习惯,不需要刻意记住下面的函数api
  • 构造函数
// 构造空对象, 里边没有数据
QByteArray::QByteArray();

// 将data中的size个字符进行构造, 得到一个字节数组对象
// 如果 size==-1 函数内部自动计算字符串长度, 计算方式为: strlen(data)
QByteArray::QByteArray(const char *data, int size = -1);

// 构造一个长度为size个字节, 并且每个字节值都为ch的字节数组
QByteArray::QByteArray(int size, char ch);
  • 数据操作
// 在尾部追加数据
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::append(const QByteArray &ba);
void QByteArray::push_back(const QByteArray &other);

// 头部添加数据
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::prepend(const QByteArray &ba);
void QByteArray::push_front(const QByteArray &other);

// 插入数据, 将ba插入到数组第 i 个字节的位置(从0开始)
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::insert(int i, const QByteArray &ba);

// 删除数据
// 从大字符串中删除len个字符, 从第pos个字符的位置开始删除
QByteArray &QByteArray::remove(int pos, int len);
// 从字符数组的尾部删除 n 个字节
void QByteArray::chop(int n);
// 从字节数组的 pos 位置将数组截断 (前边部分留下, 后边部分被删除)
void QByteArray::truncate(int pos);
// 将对象中的数据清空, 使其为null
void QByteArray::clear();

// 字符串替换
// 将字节数组中的 子字符串 before 替换为 after
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::replace(const QByteArray &before, const QByteArray &after);
  • 子字符串查找和判断
// 判断字节数组中是否包含子字符串 ba, 包含返回true, 否则返回false
bool QByteArray::contains(const QByteArray &ba) const;
bool QByteArray::contains(const char *ba) const;
// 判断字节数组中是否包含子字符 ch, 包含返回true, 否则返回false
bool QByteArray::contains(char ch) const;

// 判断字节数组是否以字符串 ba 开始, 是返回true, 不是返回false
bool QByteArray::startsWith(const QByteArray &ba) const;
bool QByteArray::startsWith(const char *ba) const;
// 判断字节数组是否以字符 ch 开始, 是返回true, 不是返回false
bool QByteArray::startsWith(char ch) const;

// 判断字节数组是否以字符串 ba 结尾, 是返回true, 不是返回false
bool QByteArray::endsWith(const QByteArray &ba) const;
bool QByteArray::endsWith(const char *ba) const;
// 判断字节数组是否以字符 ch 结尾, 是返回true, 不是返回false
bool QByteArray::endsWith(char ch) const;
  • 遍历
// 使用迭代器
iterator QByteArray::begin();
iterator QByteArray::end();

// 使用数组的方式进行遍历
// i的取值范围 0 <= i < size()
char QByteArray::at(int i) const;
char QByteArray::operator[](int i) const;
  • 查看字节数
// 返回字节数组对象中字符的个数
int QByteArray::length() const;
int QByteArray::size() const;
int QByteArray::count() const;

// 返回字节数组对象中 子字符串ba 出现的次数
int QByteArray::count(const QByteArray &ba) const;
int QByteArray::count(const char *ba) const;
// 返回字节数组对象中 字符串ch 出现的次数
int QByteArray::count(char ch) const;
  • 类型转换
// 将QByteArray类型的字符串 转换为 char* 类型
char *QByteArray::data();
const char *QByteArray::data() const;

// int, short, long, float, double -> QByteArray
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::setNum(int n, int base = 10);
QByteArray &QByteArray::setNum(short n, int base = 10);
QByteArray &QByteArray::setNum(qlonglong n, int base = 10);
QByteArray &QByteArray::setNum(float n, char f = 'g', int prec = 6);
QByteArray &QByteArray::setNum(double n, char f = 'g', int prec = 6);
[static] QByteArray QByteArray::number(int n, int base = 10);
[static] QByteArray QByteArray::number(qlonglong n, int base = 10);
[static] QByteArray QByteArray::number(double n, char f = 'g', int prec = 6);

// QByteArray -> int, short, long, float, double
int QByteArray::toInt(bool *ok = Q_NULLPTR, int base = 10) const;
short QByteArray::toShort(bool *ok = Q_NULLPTR, int base = 10) const;
long QByteArray::toLong(bool *ok = Q_NULLPTR, int base = 10) const;
float QByteArray::toFloat(bool *ok = Q_NULLPTR) const;
double QByteArray::toDouble(bool *ok = Q_NULLPTR) const;

// std::string -> QByteArray
[static] QByteArray QByteArray::fromStdString(const std::string &str);
// QByteArray -> std::string
std::string QByteArray::toStdString() const;

// 所有字符转换为大写
QByteArray QByteArray::toUpper() const;
// 所有字符转换为小写
QByteArray QByteArray::toLower() const;

8.2 QString

  1. QString 也是封装了字符串, 但是内部的编码为utf8, UTF-8属于Unicode字符集, 它固定使用多个字节(window为2字节, linux为3字节)来表示一个字符,这样可以将世界上几乎所有语言的常用字符收录其中。
  2. QString 实际上是基于 QByteArray 的封装,而QByteArray 是基于char* 的封装

下面给大家介绍一下这个类中常用的一些API函数。

  • 构造函数
// 构造一个空字符串对象
QString::QString();
// 将 char* 字符串 转换为 QString 类型
QString::QString(const char *str);
// 将 QByteArray 转换为 QString 类型
QString::QString(const QByteArray &ba);
// 其他重载的同名构造函数可参考Qt帮助文档, 此处略
  • 数据操作
// 尾部追加数据
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString &QString::append(const QString &str);
QString &QString::append(const char *str);
QString &QString::append(const QByteArray &ba);
void QString::push_back(const QString &other);

// 头部添加数据
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString &QString::prepend(const QString &str);
QString &QString::prepend(const char *str);
QString &QString::prepend(const QByteArray &ba);
void QString::push_front(const QString &other);

// 插入数据, 将 str 插入到字符串第 position 个字符的位置(从0开始)
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString &QString::insert(int position, const QString &str);
QString &QString::insert(int position, const char *str);
QString &QString::insert(int position, const QByteArray &str);

// 删除数据
// 从大字符串中删除len个字符, 从第pos个字符的位置开始删除
QString &QString::remove(int position, int n);

// 从字符串的尾部删除 n 个字符
void QString::chop(int n);
// 从字节串的 position 位置将字符串截断 (前边部分留下, 后边部分被删除)
void QString::truncate(int position);
// 将对象中的数据清空, 使其为null
void QString::clear();

// 字符串替换
// 将字节数组中的 子字符串 before 替换为 after
// 参数 cs 为是否区分大小写, 默认区分大小写
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString &QString::replace(const QString &before, const QString &after, Qt::CaseSensitivity cs = Qt::CaseSensitive);
  • 子字符串查找和判断
// 参数 cs 为是否区分大小写, 默认区分大小写
// 其他重载的同名函数可参考Qt帮助文档, 此处略

// 判断字符串中是否包含子字符串 str, 包含返回true, 否则返回false
bool QString::contains(const QString &str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;

// 判断字符串是否以字符串 ba 开始, 是返回true, 不是返回false
bool QString::startsWith(const QString &s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;

// 判断字符串是否以字符串 ba 结尾, 是返回true, 不是返回false
bool QString::endsWith(const QString &s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
  • 遍历
// 使用迭代器
iterator QString::begin();
iterator QString::end();

// 使用数组的方式进行遍历
// i的取值范围 0 <= position < size()
const QChar QString::at(int position) const
const QChar QString::operator[](int position) const;
  • 查看字节数
// 返回字节数组对象中字符的个数 (字符个数和字节个数是不同的概念)
int QString::length() const;
int QString::size() const;
int QString::count() const;

// 返回字节串对象中 子字符串 str 出现的次数
// 参数 cs 为是否区分大小写, 默认区分大小写
int QString::count(const QStringRef &str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
  • 类型转换
// 将int, short, long, float, double 转换为 QString 类型
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString &QString::setNum(int n, int base = 10);
QString &QString::setNum(short n, int base = 10);
QString &QString::setNum(long n, int base = 10);
QString &QString::setNum(float n, char format = 'g', int precision = 6);
QString &QString::setNum(double n, char format = 'g', int precision = 6);
[static] QString QString::number(long n, int base = 10);
[static] QString QString::number(int n, int base = 10);
[static] QString QString::number(double n, char format = 'g', int precision = 6);

// 将 QString 转换为 int, short, long, float, double 类型
int QString::toInt(bool *ok = Q_NULLPTR, int base = 10) const;
short QString::toShort(bool *ok = Q_NULLPTR, int base = 10) const;
long QString::toLong(bool *ok = Q_NULLPTR, int base = 10) const
float QString::toFloat(bool *ok = Q_NULLPTR) const;
double QString::toDouble(bool *ok = Q_NULLPTR) const;

// 将标准C++中的 std::string 类型 转换为 QString 类型
[static] QString QString::fromStdString(const std::string &str);
// 将 QString 转换为 标准C++中的 std::string 类型
std::string QString::toStdString() const;

// QString -> QByteArray
// 转换为本地编码, 跟随操作系统
QByteArray QString::toLocal8Bit() const;
// 转换为 Latin-1 编码的字符串 不支持中文
QByteArray QString::toLatin1() const;
// 转换为 utf8 编码格式的字符串 (常用)
QByteArray QString::toUtf8() const;

// 所有字符转换为大写
QString QString::toUpper() const;
// 所有字符转换为小写
QString QString::toLower() const;
  • 上面转换的函数中注意一下toLocal8Bit、toLatin1、toUtf8这三个编码函数
  • 直接将 QString 转换成 char *做不到的,必须是先将QString ==> QByteArray ==> char *
  • 字符串格式
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString QString::arg(const QString &a, 
          int fieldWidth = 0, 
          QChar fillChar = QLatin1Char( ' ' )) const;
QString QString::arg(int a, int fieldWidth = 0, 
          int base = 10, 
          QChar fillChar = QLatin1Char( ' ' )) const;

示例程序1:

	// 示例程序1
    //(唐僧)有(3)徒弟,分别是(孙悟空), (猪八戒), (沙僧)
    QString str = QString("(%1)有(%2)徒弟,分别是(%3), (%4), (%5)")
                      .arg("唐僧").arg("3").arg("孙悟空").arg("猪八戒").arg("沙僧");
    qDebug()<<str;

	// 示例程序2
    //(唐僧)有(3)徒弟,分别是(孙悟空), (猪八戒), (沙僧)
    QString str1 = QString("(%1)有(%2)徒弟,分别是(%3), (%5), (%4)")
                      .arg("唐僧").arg("3").arg("孙悟空").arg("猪八戒").arg("沙僧");
    qDebug()<<str1;
    //从上面例子中可以看到,所谓的 %5 就是代表第五arg(".....") 参数

	//示例程序3
    int i = 20;                // 假设该变量表示当前文件的编号
    int total = 2000;            // 假设该变量表示文件的总个数
    QString fileName = "QT file";     // 假设该变量表示当前文件的名字
    // 使用以上三个变量拼接一个动态字符串
    QString status = QString("Processing file %1 of %2: %3")
                         .arg(i).arg(total).arg(fileName);

    qDebug()<<status;

结果如图所示:在这里插入图片描述

  • 注意示例程序2与示例程序1的区别,占位符 %5 就是代表第五arg(“…”) 参数 ,即使%5 在 %4 之前

示例程序2:

    //两种api计算字符串的长度
    QString tmp = "我很帅呦, 哈哈哈...";
    QByteArray tmp1 = "我很帅呦, 哈哈哈...";
    // 长度是字符的个数,一个汉字算一个字符
    qDebug()<<"QString 字符的个数,length"<<tmp.length();

    //长度是字符串所占用的字节个数、一个汉字3个字节
    qDebug()<<"QByteArray 所占字节个数,length"<<tmp1.length();

在这里插入图片描述

9. QVariant

  • QVariant这个类很神奇,或者说方便。很多时候,需要几种不同的数据类型需要传递,如果用结构体,又不大方便,容器保存的也只是一种数据类型,而QVariant则可以统统搞定。

  • QVariant 这个类型充当着最常见的数据类型的联合QVariant 可以保存(表示)很多Qt的数据类型,包括QBrush、QColor、QCursor、QDateTime、QFont、QKeySequence、 QPalette、QPen、QPixmap、QPoint、QRect、QRegion、QSize和QString,并且还有C++基本类型,如 int、float等。

  • 实际上就把QVariant 当成是一个可以表示很多数据类型的模板

9.1 标准类型

常用API函数

  • 将标准类型转换为QVariant类型
// 这类转换需要使用QVariant类的构造函数, 由于比较多, 大家可自行查阅Qt帮助文档, 在这里简单写几个
QVariant::QVariant(int val);
QVariant::QVariant(bool val);
QVariant::QVariant(double val);
QVariant::QVariant(const char *val);
QVariant::QVariant(const QByteArray &val);
QVariant::QVariant(const QString &val);
......
    
// 使用设置函数也可以将支持的类型的数据设置到QVariant对象中
// 这里的 T 类型, 就是QVariant支持的类型
void QVariant::setValue(const T &value);
// 该函数行为和 setValue() 函数完全相同
[static] QVariant QVariant::fromValue(const T &value);


// 例子:
#if 1
QVariant v;
v.setValue(5);
#else
QVariant v = QVariant::fromValue(5);
#endif

int i = v.toInt();          // i is now 5
QString s = v.toString();   // s is now "5"
  • 判断 QVariant中封装的实际数据类型
// 该函数的返回值是一个枚举类型, 可通过这个枚举判断出实际是什么类型的数据
//  QT6 之后可以使用typeid函数代替,具体的可以见示例程序
Type QVariant::type() const;

返回值Type的部分枚举定义, 全部信息可以自行查阅Qt帮助文档

在这里插入图片描述

  • 将QVariant对象转换为实际的数据类型
// 如果要实现该操作, 可以使用QVariant类提供的 toxxx() 方法, 全部转换可以参考Qt帮助文档
// 在此举列举几个常用函数:
bool QVariant::toBool() const;
QByteArray QVariant::toByteArray() const;
double QVariant::toDouble(bool *ok = Q_NULLPTR) const;
float QVariant::toFloat(bool *ok = Q_NULLPTR) const;
int QVariant::toInt(bool *ok = Q_NULLPTR) const;
QString QVariant::toString() const;
......

示例程序

  1. 首先创建一个工程并且在头文件中定义一个函数,用于相加两个标准类型的QVariant在这里插入图片描述
  • 这里注意学会快捷键的使用 使用alt+ 回车可以直接切换在源文件中定义在这里插入图片描述
  1. 在源文件中定义 dataPlus 函数
    在这里插入图片描述
    源码如下:
QVariant MainWindow::dataPlus(QVariant a, QVariant b)
{
    //首先先定义一个null的QVariant
    QVariant ret;
    //这里获取 QVariant 变量的数据类型,之前是 type()函数,现在已经被弃用使用typeid()函数即可
    // typeid()函数的返回值是 QVariant::type ,但是实际底层是 QMetaType类型
    if(a.typeId() == QVariant::Int && b.typeId() == QVariant::Int){

        ret = QVariant(a.toInt()+b.toInt());

    }
    else if(a.typeId() == QVariant::String && b.typeId() == QVariant::String){

        ret.setValue(a.toString()+b.toString());

    }
    return ret;
}

主程序使用,实际上是在窗口的构造函数中使用的:

    //示例程序
    //整形
    //toInt方法就是还原成原始数据int类型
    int value = dataPlus(10,20).toInt();
    qDebug()<<"value = "<<value;

    //字符串类型
    //toString方法就是还原成原始数据QString类型
    QString str = dataPlus("wo","是").toString();
    qDebug()<<"str = "<<str;

在这里插入图片描述

9.2 自定义类型

  • 除了标准类型, 我们自定义的类型也可以使用QVariant类进行封装。
    • QVariant存储的数据类型需要有一个默认的构造函数和一个拷贝构造函数。
    • 为了实现这个功能,首先必须使用Q_DECLARE_METATYPE()宏。通常会将这个宏放在类的声明所在头文件的下面, 原型为:
      Q_DECLARE_METATYPE(Type)

示例程序

使用的具体步骤如下:

  1. 第一步: 在头文件中声明
// *.h 
struct MyTest {
  int id;
  QString name;
};
// 自定义类型注册 
Q_DECLARE_METATYPE(MyTest) 
  1. 第二步: 在源文件中定义
    MyTest t;
    t.name = "张三丰";
    t.id = 666;

    //两种封装自定义数据类型的方式
    QVariant vt;

#if 1
    //方式一:通过setvalue设置
    vt.setValue(t);

#else
    //方式二:使用静态方法 fromValue
    // 值的封装
    vt = QVariant::fromValue(t);

#endif

    // 值的读取
    //  canConvert<MyTest>()模板函数,当前的QVariant对象是否是MyTest类型
    if(vt.canConvert<MyTest>())
    {
    	   //vt.value<MyTest>(); 将当前QVariant对象转换为实际的 MyTest 类型
        MyTest t = vt.value<MyTest>();
        qDebug() << "name: " << t.name << ", id: " << t.id;
    }

以上操作用到的QVariant类的API如下:

// 如果当前QVariant对象可用转换为对应的模板类型 T, 返回true, 否则返回false
bool QVariant::canConvert() const;
// 将当前QVariant对象转换为实际的 T 类型
T QVariant::value() const;

9.3 总结

  • 其实数据类型转换成 QVariant 有三种方法
    • 构造函数 (不适用于自定义数据类型)
    • null QVariant 对象然后通过setValue方法设置
    • 通过静态方法进行设置QVariant::fromValue(t);
  • 如何将自定义的数据类型再包装成QVariant 对象后取出来
    • 对于标准数据类型
      • 可以直接针对 QVariant 对象使用 toInt()、tostring() 函数
    • 对于自定义的数据类型
      • canConvert<MyTest>()模板函数判断数据类型
      • value<MyTest>(); 将当前QVariant对象转换为实际的 MyTest 类型

这些具体的使用方法再前面的示例程序中都有提及!!!!!

10. 位置和尺寸

10.1 QPoint

QPoint类封装了我们常用用到的坐标点 (x, y), 常用的 API如下:

// 构造函数
// 构造一个坐标原点, 即(0, 0)
QPoint::QPoint();
// 参数为 x轴坐标, y轴坐标
QPoint::QPoint(int xpos, int ypos);

// 设置x轴坐标
void QPoint::setX(int x);
// 设置y轴坐标
void QPoint::setY(int y);

// 得到x轴坐标
int QPoint::x() const;
// 得到x轴坐标的引用
int &QPoint::rx();
// 得到y轴坐标
int QPoint::y() const;
// 得到y轴坐标的引用
int &QPoint::ry();

// 直接通过坐标对象进行算术运算: 加减乘除
QPoint &QPoint::operator*=(float factor);
QPoint &QPoint::operator*=(double factor);
QPoint &QPoint::operator*=(int factor);
QPoint &QPoint::operator+=(const QPoint &point);
QPoint &QPoint::operator-=(const QPoint &point);
QPoint &QPoint::operator/=(qreal divisor);

// 其他API请自行查询Qt帮助文档, 不要犯懒哦哦哦哦哦......

10.2 QLine

  • QLine是一个直线类, 封装了两个坐标点 (两点确定一条直线)
  • 常用API如下:
// 构造函数
// 构造一个空对象
QLine::QLine();
// 构造一条直线, 通过两个坐标点
QLine::QLine(const QPoint &p1, const QPoint &p2);
// 从点 (x1, y1) 到 (x2, y2)
QLine::QLine(int x1, int y1, int x2, int y2);

// 给直线对象设置坐标点
void QLine::setPoints(const QPoint &p1, const QPoint &p2);
// 起始点(x1, y1), 终点(x2, y2)
void QLine::setLine(int x1, int y1, int x2, int y2);
// 设置直线的起点坐标
void QLine::setP1(const QPoint &p1);
// 设置直线的终点坐标
void QLine::setP2(const QPoint &p2);

// 返回直线的起始点坐标
QPoint QLine::p1() const;
// 返回直线的终点坐标
QPoint QLine::p2() const;
// 返回值直线的中心点坐标, (p1() + p2()) / 2
QPoint QLine::center() const;

// 返回值直线起点的 x 坐标
int QLine::x1() const;
// 返回值直线终点的 x 坐标
int QLine::x2() const;
// 返回值直线起点的 y 坐标
int QLine::y1() const;
// 返回值直线终点的 y 坐标
int QLine::y2() const;

// 用给定的坐标点平移这条直线
void QLine::translate(const QPoint &offset);
void QLine::translate(int dx, int dy);
// 用给定的坐标点平移这条直线, 返回平移之后的坐标点
QLine QLine::translated(const QPoint &offset) const;
QLine QLine::translated(int dx, int dy) const;

// 直线对象进行比较
bool QLine::operator!=(const QLine &line) const;
bool QLine::operator==(const QLine &line) const;

// 其他API请自行查询Qt帮助文档, 不要犯懒哦哦哦哦哦......

10.3 QSize

  • 在QT中QSize类用来形容长度和宽度, 常用的API如下:
// 构造函数
// 构造空对象, 对象中的宽和高都是无效的
QSize::QSize();
// 使用宽和高构造一个有效对象
QSize::QSize(int width, int height);

// 设置宽度
void QSize::setWidth(int width)
// 设置高度
void QSize::setHeight(int height);

// 得到宽度
int QSize::width() const;
// 得到宽度的引用
int &QSize::rwidth();
// 得到高度
int QSize::height() const;
// 得到高度的引用
int &QSize::rheight();

// 交换高度和宽度的值
void QSize::transpose();
// 交换高度和宽度的值, 返回交换之后的尺寸信息
QSize QSize::transposed() const;

// 进行算法运算: 加减乘除
QSize &QSize::operator*=(qreal factor);
QSize &QSize::operator+=(const QSize &size);
QSize &QSize::operator-=(const QSize &size);
QSize &QSize::operator/=(qreal divisor);

// 其他API请自行查询Qt帮助文档, 不要犯懒哦哦哦哦哦......

10.4 QRect

在Qt中使用 QRect类来描述一个矩形, 常用的API如下:

// 构造函数
// 构造一个空对象
QRect::QRect();
// 基于左上角坐标, 和右下角坐标构造一个矩形对象
QRect::QRect(const QPoint &topLeft, const QPoint &bottomRight);
// 基于左上角坐标, 和 宽度, 高度构造一个矩形对象
QRect::QRect(const QPoint &topLeft, const QSize &size);
// 通过 左上角坐标(x, y), 和 矩形尺寸(width, height) 构造一个矩形对象
QRect::QRect(int x, int y, int width, int height);

// 设置矩形的尺寸信息, 左上角坐标不变
void QRect::setSize(const QSize &size);
// 设置矩形左上角坐标为(x,y), 大小为(width, height)
void QRect::setRect(int x, int y, int width, int height);
// 设置矩形宽度
void QRect::setWidth(int width);
// 设置矩形高度
void QRect::setHeight(int height);

// 返回值矩形左上角坐标
QPoint QRect::topLeft() const;
// 返回矩形右上角坐标
// 该坐标点值为: QPoint(left() + width() -1, top())
QPoint QRect::topRight() const;
// 返回矩形左下角坐标
// 该坐标点值为: QPoint(left(), top() + height() - 1)
QPoint QRect::bottomLeft() const;
// 返回矩形右下角坐标
// 该坐标点值为: QPoint(left() + width() -1, top() + height() - 1)
QPoint QRect::bottomRight() const;
// 返回矩形中心点坐标
QPoint QRect::center() const;

// 返回矩形上边缘y轴坐标
int QRect::top() const;
int QRect::y() const;
// 返回值矩形下边缘y轴坐标
int QRect::bottom() const;
// 返回矩形左边缘 x轴坐标
int QRect::x() const;
int QRect::left() const;
// 返回矩形右边缘x轴坐标
int QRect::right() const;

// 返回矩形的高度
int QRect::width() const;
// 返回矩形的宽度
int QRect::height() const;
// 返回矩形的尺寸信息
QSize QRect::size() const;
  • 上面的函数中,只强调top、bottom、left、right这四个函数,它们返回的是矩形上、下、左、右四条边的y坐标和x坐标
    • 以 top 函数为例,它返回的是矩形上边缘的y坐标,因为矩形的宽是x坐标变化,但是y坐标是固定的

11. 日期和时间

11.1 QDate

API 函数

QDate类可以封装日期信息也可以通过这个类得到日期相关的信息, 包括:年, 月, 日

// 构造函数
QDate::QDate();
QDate::QDate(int y, int m, int d);

// 公共成员函数
// 重新设置日期对象中的日期
bool QDate::setDate(int year, int month, int day);
// 给日期对象添加 ndays 天,负数就是为了将时间提前回溯
QDate QDate::addDays(qint64 ndays) const;
// 给日期对象添加 nmonths 月,负数就是为了将时间提前回溯
QDate QDate::addMonths(int nmonths) const;
// 给日期对象添加 nyears 月,负数就是为了将时间提前回溯
QDate QDate::addYears(int nyears) const;

// 得到日期对象中的年/月/日
int QDate::year() const;
int QDate::month() const;
int QDate::day() const;
void QDate::getDate(int *year, int *month, int *day) const;

// 日期对象格式化
/*
    d    - The day as a number without a leading zero (1 to 31)
    dd   - The day as a number with a leading zero (01 to 31)
    ddd	 - The abbreviated localized day name (e.g. 'Mon' to 'Sun'). Uses the system locale to localize the name, i.e. QLocale::system().
    dddd - The long localized day name (e.g. 'Monday' to 'Sunday'). Uses the system locale to localize the name, i.e. QLocale::system().
    M    - The month as a number without a leading zero (1 to 12)
    MM   - The month as a number with a leading zero (01 to 12)
    MMM	 - The abbreviated localized month name (e.g. 'Jan' to 'Dec'). Uses the system locale to localize the name, i.e. QLocale::system().
    MMMM - The long localized month name (e.g. 'January' to 'December'). Uses the system locale to localize the name, i.e. QLocale::system().
    yy   - The year as a two digit number (00 to 99)
    yyyy - The year as a four digit number. If the year is negative, a minus sign is prepended, making five characters.
*/
QString QDate::toString(const QString &format) const;

// 操作符重载 ==> 日期比较
bool QDate::operator!=(const QDate &d) const;
bool QDate::operator<(const QDate &d) const;
bool QDate::operator<=(const QDate &d) const;
bool QDate::operator==(const QDate &d) const;
bool QDate::operator>(const QDate &d) const;
bool QDate::operator>=(const QDate &d) const;

// 静态函数 -> 得到本地的当前日期
[static] QDate QDate::currentDate();

示例程序

    //获取当前的系统时间、根据电脑的系统获取的
    QDate d = QDate::currentDate();

    //第一种输出方式
    qDebug()<<"year:"<<d.year()<<",month:"<<d.month()<<",day:"<<d.day();

    //第二种方式:格式化输出 2000-01-31
    //注意:其中的符号 '-' 可以有自己主动决定,唯一的规则就是格式化的字符串 yyyy、MM、dd
    QString str = d.toString("yyyy-MM-dd");
    qDebug()<<str;

在这里插入图片描述

  • 同时注意上面格式化输出的时候 ,yyyy、yy、M、MM、MMM、d、dd、ddd、dddd 分别代表什么意思,前面的API注释中有解释

11.2 QTime

QTime类可以封装时间信息也可以通过这个类得到时间相关的信息, 包括:时, 分, 秒, 毫秒

API 函数

// 构造函数
QTime::QTime();
/*
    h 		==> 取值范围: 0 ~ 23
    m and s 	==> 取值范围: 0 ~ 59
    ms 		==> 取值范围: 0 ~ 999
*/ 
QTime::QTime(int h, int m, int s = 0, int ms = 0);

// 公共成员函数
// Returns true if the set time is valid; otherwise returns false.
bool QTime::setHMS(int h, int m, int s, int ms = 0);
QTime QTime::addSecs(int s) const;
QTime QTime::addMSecs(int ms) const;

// 示例代码
  QTime n(14, 0, 0);                // n == 14:00:00
  QTime t;
  t = n.addSecs(70);                // t == 14:01:10
  t = n.addSecs(-70);               // t == 13:58:50,负数代表回溯
  t = n.addSecs(10 * 60 * 60 + 5);  // t == 00:00:05
  t = n.addSecs(-15 * 60 * 60);     // t == 23:00:00  ,这里是毫秒

// 从时间对象中取出 时/分/秒/毫秒
// Returns the hour part (0 to 23) of the time. Returns -1 if the time is invalid.
int QTime::hour() const;
// Returns the minute part (0 to 59) of the time. Returns -1 if the time is invalid.
int QTime::minute() const;
// Returns the second part (0 to 59) of the time. Returns -1 if the time is invalid.
int QTime::second() const;
// Returns the millisecond part (0 to 999) of the time. Returns -1 if the time is invalid.
int QTime::msec() const;


// 时间格式化
/*
    -- 时 --
    h	==>	The hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)
    hh	==>	The hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)
    H	==>	The hour without a leading zero (0 to 23, even with AM/PM display)
    HH	==>	The hour with a leading zero (00 to 23, even with AM/PM display)
    -- 分 --
    m	==>	The minute without a leading zero (0 to 59)
    mm	==>	The minute with a leading zero (00 to 59)
    -- 秒 --
    s	==>	The whole second, without any leading zero (0 to 59)
    ss	==>	The whole second, with a leading zero where applicable (00 to 59)
    -- 毫秒 --
    zzz	==>	The fractional part of the second, to millisecond precision, 
			including trailing zeroes where applicable (000 to 999).
    -- 上午或者下午
    AP or A	==>	使用AM/PM(大写) 描述上下午, 中文系统显示汉字
    ap or a	==>	使用am/pm(小写) 描述上下午, 中文系统显示汉字
*/
QString QTime::toString(const QString &format) const;

// 阶段性计时
// 过时的API函数 QT5中可以使用,QT6已经没有了
// 开始计时
void QTime::start();
// 计时结束
int QTime::elapsed() const;
// 重新计时
int QTime::restart();

// 推荐使用的API函数
// QElapsedTimer 类
void QElapsedTimer::start();
qint64 QElapsedTimer::restart();
qint64 QElapsedTimer::elapsed() const;


// 操作符重载 ==> 时间比较
bool QTime::operator!=(const QTime &t) const;
bool QTime::operator<(const QTime &t) const;
bool QTime::operator<=(const QTime &t) const;
bool QTime::operator==(const QTime &t) const;
bool QTime::operator>(const QTime &t) const;
bool QTime::operator>=(const QTime &t) const;

// 静态函数 -> 得到当前时间
[static] QTime QTime::currentTime();

示例程序1

// 示例代码
  QTime n(14, 0, 0);                // n == 14:00:00
  QTime t;
  t = n.addSecs(70);                // t == 14:01:10
  t = n.addSecs(-70);               // t == 13:58:50,负数代表回溯
  t = n.addSecs(10 * 60 * 60 + 5);  // t == 00:00:05
  t = n.addSecs(-15 * 60 * 60);     // t == 23:00:00  ,这里是毫秒

示例程序2

快捷键的使用
在这里插入图片描述

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDate>
#include<QTime>
#include<QDebug>
#include<QElapsedTimer>


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

    //获取当前的系统时间、根据电脑的系统获取的
    QDate d = QDate::currentDate();

    //第一种输出方式
    qDebug()<<"year:"<<d.year()<<",month:"<<d.month()<<",day:"<<d.day();

    //第二种方式:格式化输出 2000-01-31
    //注意:其中的符号 '-' 可以有自己主动决定,唯一的规则就是格式化的字符串 yyyy、MM、dd
    QString str = d.toString("yyyy-MM-dd");
    qDebug()<<str;


    //获取当前时间
    QTime curtime = QTime::currentTime();
    //方式一、输出方式
    qDebug()<<"hour:"<<curtime.hour()<<",minutes:"<<curtime.minute()<<",seconds:"<<curtime.second()<<",milliseconds:"<<curtime.msec();
    //方式二,格式化输出
    QString str1 = curtime.toString("hh.mm.ss.zzz");
    qDebug()<<str1;


    //计时器功能,QT5中是可以使用QTime类中的 QTime::start()
    //但是QT6中,只能使用QElapsedTimer::start,已经没有 QTime::start()方法了
    QElapsedTimer tt;
    tt.start();
    randNumbers(1000);
    int ms1 = tt.elapsed();  //返回的是毫秒
    qDebug()<<"函数所执行的时间的毫秒为:"<<ms1;

}

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

void MainWindow::randNumbers(int count)
{
    srand(time(NULL));
    for(int i = 0;i<count;i++){

        int num = rand()%1000;
        qDebug()<<num;
    }
}

在这里插入图片描述
在这里插入图片描述

11.3 QDateTime

QDateTime类可以封装日期和时间信息也可以通过这个类得到日期和时间相关的信息, 包括:年, 月, 日, 时, 分, 秒, 毫秒。其实这个类就是QDateQTime两个类的结合体

API函数

// 构造函数
QDateTime::QDateTime();
QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec = Qt::LocalTime);

// 公共成员函数
// 设置日期
void QDateTime::setDate(const QDate &date);
// 设置时间
void QDateTime::setTime(const QTime &time);
// 给当前日期对象追加 年/月/日/秒/毫秒, 参数可以是负数
QDateTime QDateTime::addYears(int nyears) const;
QDateTime QDateTime::addMonths(int nmonths) const;
QDateTime QDateTime::addDays(qint64 ndays) const;
QDateTime QDateTime::addSecs(qint64 s) const;
QDateTime QDateTime::addMSecs(qint64 msecs) const;

// 得到对象中的日期
QDate QDateTime::date() const;
// 得到对象中的时间
QTime QDateTime::time() const;

// 日期和时间格式, 格式字符参考QDate 和 QTime 类的 toString() 函数
QString QDateTime::toString(const QString &format) const;


// 操作符重载 ==> 日期时间对象的比较
bool QDateTime::operator!=(const QDateTime &other) const;
bool QDateTime::operator<(const QDateTime &other) const;
bool QDateTime::operator<=(const QDateTime &other) const;
bool QDateTime::operator==(const QDateTime &other) const;
bool QDateTime::operator>(const QDateTime &other) const;
bool QDateTime::operator>=(const QDateTime &other) const;

// 静态函数
// 得到当前时区的日期和时间(本地设置的时区对应的日期和时间)
[static] QDateTime QDateTime::currentDateTime();

示例程序:

    //获取当前的时间日期
    QDateTime dt = QDateTime::currentDateTime();

    //格式化时间
    //格式化形式一: ap代表的是半日制,分上下午  2025/3/16 8:39:40  上午/下午
    QString strdt = dt.toString("yyyy/MM/dd  HH:mm:ss ap");
    qDebug()<<"当前的日期和时间是:"<<strdt;

    //格式化形式二:24小时制
    QString sdt = dt.toString("yyyy/MM/dd  HH:mm:ss ");
    qDebug()<<"当前的日期和时间是:"<<sdt;


    // 先去出日期
    QDate d = dt.date();
    //API函数取出
    qDebug()<<"year:"<<d.year()<<",month:"<<d.month()<<",day:"<<d.day();
    //格式化输出
    //注意:其中的符号 '-' 可以有自己主动决定,唯一的规则就是格式化的字符串 yyyy、MM、dd
    QString str = d.toString("yyyy-MM-dd");
    qDebug()<<str;

    //再取出时间
    QTime tt = dt.time();
    //方式一、输出方式
    qDebug()<<"hour:"<<tt.hour()<<",minutes:"<<tt.minute()<<",seconds:"<<tt.second()<<",milliseconds:"<<tt.msec();
    //方式二,格式化输出
    QString str1 = tt.toString("hh.mm.ss.zzz");
    qDebug()<<str1;

在这里插入图片描述

12. 信号和槽概述

  • 信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式(发布-订阅模式)。
  • 当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)这种发出是没有目的的,类似广播
  • 如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。
  • 也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
  • 强调一点,所谓的信号槽connect连接,并不会使程序阻塞在connect的槽函数这里
  • connect本质上就是在注册回调函数(也就是定义函数指针),等到if条件触发的时候就会调用这个函数指针!!!!

12.1 信号的本质

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt对应的窗口类会发出某个信号,以此对用户的挑选做出反应。

  • 因此根据上述的描述我们得到一个结论 – 信号的本质就是事件,比如:

    • 按钮单击、双击
    • 窗口刷新
    • 鼠标移动、鼠标按下、鼠标释放
    • 键盘输入
  • 那么在Qt中信号是通过什么形式呈现给使用者的呢?

    • 我们对哪个窗口进行操作, 哪个窗口就可以捕捉到这些被触发的事件。
    • 对于使用者来说触发了一个事件我们就可以得到Qt框架给我们发出的某个特定信号。
    • 信号的呈现形式就是函数, 也就是说某个事件产生了, Qt框架就会调用某个对应的信号函数, 通知使用者
  • 在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。
  • 这个实例化的对象可以是我们添加的窗口,如:窗口类发出信号

12.2 槽的本质

  • 在Qt中槽函数是一类特殊的功能的函数,在编码过程中也可以作为类的普通成员函数来使用。
  • 之所以称之为槽函数是因为它们还有一个职责就是对Qt框架中产生的信号进行处理。
举个简单的例子:

    女朋友说:“我肚子饿了!”,于是我带她去吃饭。

上边例子中相当于女朋友发出了一个信号, 我收到了信号并其将其处理掉了。

实例对象角色描述
女朋友信号发出者信号携带的信息:我饿了
信号接收者处理女朋友发射的信号: 带他去吃饭

在Qt中槽函数的所有者也是某个类的实例对象。

12.3 信号和槽的关系

  • 在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起。
  • 在Qt中我们需要使用QOjbect类中的connect函数进二者的关联。

连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针

QMetaObject::Connection QObject::connect(
    	const QObject *sender, PointerToMemberFunction signal, 
        const QObject *receiver, PointerToMemberFunction method, 
		Qt::ConnectionType type = Qt::AutoConnection);
参数:
  - sender:   发出信号的对象
  - signal:   属于sender对象, 信号是一个函数, 这个参数的类型是函数
              指针, 信号函数地址
  - receiver: 信号接收者
  - method:   属于receiver对象, 当检测到sender发出了signal信号, 
              receiver对象调用method方法,信号发出之后的处理动作
 
//  参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

参数:

  • sender: 发出信号的对象
  • signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数指针, 信号函数地址
  • receiver: 信号接收者
  • method: 属于receiver对象, 当检测到sender发出了signal信号, receiver对象调用method方法,信号发出之后的处理动作

使用connect()进行信号槽连接的注意事项:

  • connect函数相对于做了信号处理动作的注册
  • 如果调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用
  • method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的
  • connect中的senderrecever两个指针必须被实例化了, 否则conenct不会成功

13. 标准信号槽使用

13.1 标准信号/槽

  • 在Qt提供的很多标准类中都可以对用户触发的某些特定事件进行检测, 因此当用户做了这些操作之后, 事件被触发类的内部就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号
  • 同样的,在Qt的很多类内部为我了提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数
  • 系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档,比如在帮助文档中查询按钮的点击信号,那么需要在帮助文档中输入QPushButton
    在这里插入图片描述
  • 强调一点,所谓的信号槽connect连接,并不会使程序阻塞在connect的槽函数这里
  • connect本质上就是在注册回调函数(也就是定义函数指针),等到if条件触发的时候就会调用这个函数指针!!!!
  1. 首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该看当前类从父类继承下来了哪些信号在这里插入图片描述
  2. 因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个在这里插入图片描述

13.2 使用

掌握标准信号、槽的查找方式之后以及connect()函数的作用之后, 下面通过一个简单的例子:

  • 功能描述:在窗口中添加一个按钮,只要点击按钮就可以关闭整个窗口
功能实现: 点击窗口上的按钮, 关闭窗口
功能分析:
	- 按钮: 信号发出者          -> QPushButton 类型
	- 窗口: 信号的接收者和处理者  -> QWidget 类型
  • 需要使用的标准信号槽函数
// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close();
  • 信号的本质就是事件
  • 对于上边的需求只需要一句代码, 只需要写一句代码就能实现了
// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);
  • connect()操作一般写在窗口的构造函数中, 相当于在事件产生之前在qt框架中先进行注册.
  • 这样在程序运行过程中假设产生了按钮的点击事件, 框架就会调用信号接收者对象对应的槽函数了, 如果信号不产生, 槽函数也就一直不会被调用。

实际示例程序:

  1. 首先创建一个工程如图:在这里插入图片描述

  2. MainWindow的设计窗口中添加一个QPushButton 按钮,并且修改name在这里插入图片描述

  • 需要注意的是,在右下角的属性编辑器中修改QPushButton 按钮的值,实际上就是在修改这个类对象的名称,这个会在ui文件(xml形式中)体现出来
  1. 查看对应的MainWindow.ui文件里的xml文件内容,发现有一个QPushButton 按钮类,而这个按钮的名称就叫做closeBtn ,与步骤2中属性编辑器中修改的值是一样的 在这里插入图片描述 4. 在MainWindow.cpp中添加connect函数
#include "mainwindow.h"
#include "ui_mainwindow.h"

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

    //使用connect 函数
    //这里的ui对象就是刚才的mainwindow.ui的文件对象。
    //就可以通过ui对象调用刚才创建的QPushButton类的clsoeBtn对象
    // this就是实例化的MainWindow对象
    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

}

MainWindow::~MainWindow()
{
    delete ui;
}
  1. 这个例子中信号函数和槽函数都是标准类型,是QT框架提供的,我们只需要调用即可

在这里插入图片描述

14. 自定义信号槽使用

Qt框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的,因此我们还设计自己需要的的信号和槽,同样还是使用connect()自定义的信号槽进行连接。

  • 强调一点,所谓的信号槽connect连接,并不会使程序阻塞在connect的槽函数这里
  • connect本质上就是在注册回调函数(也就是定义函数指针),等到if条件触发的时候就会调用这个函数指针!!!!

如果想要在QT类中自定义信号槽, 需要满足一些条件, 并且有些事项也需要注意:

  • 要编写新的类并且让其继承Qt的某些标准类
  • 这个新的子类必须从QObject类或者是QObject子类进行派生
  • 在定义类的头文件中加入 Q_OBJECT
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
class MyMainWindow : public QWidget
{
    Q_OBJECT
    ......
}

14.1 自定义信号

  • 在Qt中信号的本质是事件, 但是在框架中也是以函数的形式存在的, 只不过信号对应的函数只有声明, 没有定义
  • 如果Qt中的标准信号不能满足我们的需求,可以在程序中进行信号的自定义,当自定义信号对应的事件产生之后,认为的将这个信号发射出去即可(其实就是调用一下这个信号函数)

下边给大家阐述一下, 自定义信号的要求和注意事项:

  • 信号是类的成员函数
  • 返回值必须是 void 类型
  • 信号的名字可以根据实际情况进行指定
  • 参数可以随意指定, 信号也支持重载
  • 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
  • 信号函数只需要声明, 不需要定义(没有函数体实现)
  • 在程序中发射自定义信号: 发送信号的本质就是调用信号函数
  • 习惯性在信号函数前加关键字: emit, 但是可以省略不写
  • emit只是显示的声明一下信号要被发射了, 没有特殊含义
    • 底层 emit == #define emit

举例:

// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{
    Q_OBJECT
//signals 关键字,表明是信号函数
signals:
    void testsignal();
	// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
	// 实参最终会被传递给槽函数
    void testsignal(int a);
};

14.2 自定义槽

  • 槽函数就是信号的处理动作,在Qt中槽函数可以作为普通的成员函数来使用。
  • 如果标准槽函数提供的功能满足不了需求,可以自己定义槽函数进行某些特殊功能的实现。
  • 自定义槽函数和自定义的普通函数写法是一样的。

下边给大家阐述一下, 自定义槽的要求和注意事项:

  1. 返回值必须是 void 类型
  2. 槽也是函数, 因此也支持重载
  3. 槽函数需要指定多少个参数, 需要看连接的信号的参数个数
  4. 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
    • 举例:
      • 信号函数: void testsig(int a, double b);
      • 槽函数: void testslot(int a, double b);
    • 总结:
      • 槽函数的参数应该和对应的信号的参数个数, 从左到右类型依次对应
      • 信号的参数可以大于等于槽函数的参数个数 == 信号传递的数据被忽略了
        • 信号函数: void testsig(int a, double b);
        • 槽函数: void testslot(int a);
  5. Qt中槽函数的类型是多样的
    • Qt中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
  6. 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
    • public slots:
    • private slots: –> 这样的槽函数不能在类外部被调用
    • protected slots: –> 这样的槽函数不能在类外部被调用
// 槽函数书写格式举例
// 类中的这三个函数都可以作为槽函数来使用
class Test : public QObject
{
    public:
    void testSlot();
    static void testFunc();

    public slots:
    void testSlot(int id);
};

示例程序

根据特定场景自定义信号槽:

还是上边的场景: 	  
	  女朋友说:“我肚子饿了!”,于是我带她去吃饭。
  1. 首先,根据上面的场景,需要在工程中添加两个常规不带窗口的类,分别是朋友类、"我"类,添加方式如下图所示:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 最后的新建的测试工程目录如下图所示,新增了gfriend 类以及 me 类
    在这里插入图片描述
  3. mainwindow.ui设计界面上,添加 QPushButton 类按钮,并且在右下角的属性编辑器中修改值为 Hungry
    在这里插入图片描述
  4. gfriend 的头文件中定义信号函数 , gfriend 源文件不需要做任何修改
#ifndef GFRIEND_H
#define GFRIEND_H

#include <QObject>

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

signals:
    //注意,这里加上了signals关键字声明
    //信号函数可以只声明,不去定义
    void hungry();	            // 不能表达出想要吃什么



};

#endif // GFRIEND_H
  • 首先在声明信号函数的时候,需要加上signal关键字
  • 信号函数可以只声明,不去定义

gfriend 源文件如下:

#include "gfriend.h"

gfriend::gfriend(QObject *parent)
    : QObject{parent}
{

}
  1. me 类的头文件内容,在这里需要声明槽函数
#ifndef ME_H
#define ME_H

#include <QObject>

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

public slots:
    //槽函数
    void eat();

};

#endif // ME_H

  • me类 源文件中进行槽函数的定义,如下:
#include "me.h"
#include<QDebug>
Me::Me(QObject *parent)
    : QObject{parent}
{

}

// 槽函数的定义
void Me::eat()
{
    qDebug()<<"我带你去吃麻辣烫";
}
  1. 主窗口头文件 mainwindow.h 中定义
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "me.h"
#include "gfriend.h"


QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void hungrySlot();

private:
    Ui::MainWindow *ui;

    Me* me;
    gfriend* gfr;
};
#endif // MAINWINDOW_H
  • 首先包含 gfriend类与me 类的头文件,并且定义这两个类的 对象指针
  • 其次这里也需要声明一个槽函数,因为我们最后需要的效果是: 点击主窗口的按钮,然后就会发出槽信号,输出“我带你去吃麻辣烫”
  1. 主窗口源文件 mainwindow.cpp 中定义
#include "mainwindow.h"
#include "ui_mainwindow.h"

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

    //使用connect 函数
    //这里的ui对象就是刚才的mainwindow.ui的文件对象。
    //就可以通过ui对象调用刚才创建的QPushButton类的clsoeBtn对象
    // this就是实例化的MainWindow对象
    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

	//===================================本节自定义信号槽的使用===================================
    //初始化两个对象
    me = new Me;
    gfr = new gfriend;

	//将gfriend的hungry信号与 me的 eat槽函数连接
	//其中gfr是信号发射者,me是信号接收者
    connect(gfr,&gfriend::hungry,me,&Me::eat);
    
    //将主窗口的 QPushButton类按钮的clicked信号与主窗口的hungrySlot槽函数想连接
    // 这里主窗口中的Hungry按钮是信号发射者、MainWindow类的实例化对象this是信号接收者
    // 实际上,hungrySlot槽函数就是用于发射gfriend 类的hungry信号的
    connect(ui->Hungry,&QPushButton::clicked,this, &MainWindow::hungrySlot);

}

MainWindow::~MainWindow()
{
    delete ui;
}
//定义主窗口按钮点击之后的槽函数
//这个槽函数就是用于发射gfriend类中的hungry信号的
void MainWindow::hungrySlot()
{
    //发射自定义的信号
    emit gfr->hungry();

}

  1. main函数中的内容不变
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

  1. 运行效果,点击上面的Hungry按钮,控制台就会打印输出"我带你吃麻辣烫"
    在这里插入图片描述
    在这里插入图片描述
  • 总结:
  • 我们需要明白上面的操作是存在两个connect信号槽完成的
  • 总体信号槽的逻辑如下:在这里插入图片描述

15. 信号槽拓展

  • 强调一点,所谓的信号槽connect连接,并不会使程序阻塞在connect的槽函数这里
  • connect本质上就是在注册回调函数(也就是定义函数指针),等到if条件触发的时候就会调用这个函数指针!!!!

15.1 信号槽使用拓展

  • 一个信号可以连接多个槽函数, 发送一个信号有多个处理动作
    • 需要写多个connect()连接
    • QT4中,槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系
    • QT5之后中槽函数的调用顺序与信号的发射顺序相同
    • 信号的接收者可以是一个对象, 也可以是多个对象
  • 一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的
    • 需要写多个connect()连接
  • 信号可以连接信号
    • 信号接收者可以不处理接收的信号, 而是继续发射新的信号,这相当于传递了数据, 并没有对数据进行处理,相当于进行了信号的转发
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::siganl-new);
  • 信号槽是可以断开的
disconnect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

示例程序1:一个信号连接多个槽函数

  • 接前面自定义信号槽的示例程序,在mainwindow.h 头文件中新声明一个槽函数用于响应 gfriend::hungry 函数
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "me.h"
#include "gfriend.h"


QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void hungrySlot();
    void eatSlot();

private:
    Ui::MainWindow *ui;

    Me* me;
    gfriend* gfr;
};
#endif // MAINWINDOW_H

源文件中加上连接函数和 void eatSlot() 函数定义

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>

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

    //使用connect 函数
    //这里的ui对象就是刚才的mainwindow.ui的文件对象。
    //就可以通过ui对象调用刚才创建的QPushButton类的clsoeBtn对象
    // this就是实例化的MainWindow对象
    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

    //初始化两个对象
    me = new Me;
    gfr = new gfriend;
	
	//使得主窗口中的按钮进行 ‘点击‘ 操作后就会使得实例化的窗口对象this 调用MainWindow::hungrySlot函数
	//MainWindow::hungrySlot函数是用来发出gfriend类中的信号函数hungry的
    connect(ui->Hungry,&QPushButton::clicked,this, &MainWindow::hungrySlot);

	//这里进行槽函数与gfriend类中的信号函数hungry进行连接
	// 1.与Me类中的eat函数链接
    connect(gfr,&gfriend::hungry,me,&Me::eat);
    // 2. 与 MainWindow窗口中的eatSlot函数连接
    connect(gfr,&gfriend::hungry,this,&MainWindow::eatSlot);

}

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

void MainWindow::hungrySlot()
{
    //发射自定义的信号
    emit gfr->hungry();

}
//主窗口类中的eatSlot槽函数定义
void MainWindow::eatSlot()
{
    qDebug()<<"我带你去吃海鲜";
}

实际效果如下:只要点击Hungry按钮,那么就会在程序窗口输出两行内容,实际上就是对应前面的两个槽函数

  • MainWindow::eatSlot
  • Me::eat
    在这里插入图片描述

示例程序2:信号连接信号

  • 接前面自定义信号槽的示例程序
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>

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

    //使用connect 函数
    //这里的ui对象就是刚才的mainwindow.ui的文件对象。
    //就可以通过ui对象调用刚才创建的QPushButton类的clsoeBtn对象
    // this就是实例化的MainWindow对象
    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

    //初始化两个对象
    me = new Me;
    gfr = new gfriend;

    connect(gfr,&gfriend::hungry,me,&Me::eat);
    connect(gfr,&gfriend::hungry,this,&MainWindow::eatSlot);

    //信号链接信号
    connect(ui->Hungry,&QPushButton::clicked,gfr,&gfriend::hungry);
}

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

void MainWindow::hungrySlot()
{
    //发射自定义的信号
    emit gfr->hungry();

}

void MainWindow::eatSlot()
{
    qDebug()<<"我带你去吃海鲜";
}

在这里插入图片描述

15.2 信号槽的连接方式

  • Qt5的连接方式
// 语法:
QMetaObject::Connection QObject::connect(
    	const QObject *sender, PointerToMemberFunction signal, 
        const QObject *receiver, PointerToMemberFunction method, 
		Qt::ConnectionType type = Qt::AutoConnection);

// 信号和槽函数也就是第2,4个参数传递的是地址, 编译器在编译过程中会对数据的正确性进行检测
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);
  • Qt4的连接方式
  1. 这种旧的信号槽连接方式在Qt5中是支持的, 但是不推荐使用, 因为这种方式在进行信号槽连接的时候, 信号槽函数 通过宏SIGNALSLOT转换为字符串类型
  2. 因为信号槽函数的转换是通过宏来进行转换的,因此传递到宏函数内部的数据不会被进行检测, 如果使用者传错了数据,编译器也不会报错,但实际上信号槽的连接已经不对了,只有在程序运行起来之后才能发现问题,而且问题不容易被定位。
// Qt4的信号槽连接方式
[static] QMetaObject::Connection QObject::connect(
    const QObject *sender, const char *signal, 
    const QObject *receiver, const char *method, 
    Qt::ConnectionType type = Qt::AutoConnection);

connect(const QObject *sender,SIGNAL(信号函数名(参数1, 参数2, ...)),
        const QObject *receiver,SLOT(槽函数名(参数1, 参数2, ...)));

Qt4中声明槽函数必须要使用 slots 关键字, 不能省略。

示例程序

  • 下面的例子说明了:QT4与QT5的连接方式区别、信号槽函数的重载函数情况
  • 重点关注 mainwindow.cpp中的内容以及注释
  1. gfriend.h 头文件中声明了两个信号函数,都是重载的,源文件是没有任何添加
#ifndef GFRIEND_H
#define GFRIEND_H

#include <QObject>

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

signals:
    //注意,这里加上了signals关键字声明
    //信号函数可以只声明,不去定义
    void hungry();	            // 不能表达出想要吃什么
    void hungry(QString msg);	            // 表达出想要吃什么

};

#endif // GFRIEND_H

#include "gfriend.h"

gfriend::gfriend(QObject *parent)
    : QObject{parent}
{}

  1. me.h 头文件中也增加了一个重载的有参槽函数void eat(QString msg);
#ifndef ME_H
#define ME_H

#include <QObject>

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

public slots:
    //槽函数
    void eat();

    void eat(QString msg);

};

#endif // ME_H

#include "me.h"
#include<QDebug>
Me::Me(QObject *parent)
    : QObject{parent}
{

}

void Me::eat()
{
    qDebug()<<"我带你去吃麻辣烫";
}
//槽函数重载
void Me::eat(QString msg)
{
    qDebug()<<"我带你去吃 "<<msg;
}
  1. mainwindow.h 主窗口头文件中内容不变
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "me.h"
#include "gfriend.h"


QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void hungrySlot();
    void eatSlot();

private:
    Ui::MainWindow *ui;

    Me* me;
    gfriend* gfr;
};
#endif // MAINWINDOW_H

主要是的应用都在mainwindow.cpp文件中

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>

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

    //使用connect 函数
    //这里的ui对象就是刚才的mainwindow.ui的文件对象。
    //就可以通过ui对象调用刚才创建的QPushButton类的clsoeBtn对象
    // this就是实例化的MainWindow对象
    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

    //初始化两个对象
    me = new Me;
    gfr = new gfriend;


    //定义函数指针
    void(gfriend::*girl1)()= &gfriend::hungry;
    void(gfriend::*girl2)(QString)= &gfriend::hungry;

    void(Me::*m1)()= &Me::eat;
    void(Me::*m2)(QString)= &Me::eat;

    //使用QT5的方式对信号槽函数的重载形式进行链接就必须使用函数指针
    connect(gfr,girl1,me,m1);
    connect(gfr,girl2,me,m2);
    //这里槽函数的参数个数是小于信号函数的参数个数的,因此直接可以忽略多余的信号函数的参数
    connect(gfr,girl2,this,&MainWindow::eatSlot);


    //使用QT4的连接方式
    connect(gfr,SIGNAL(hungry()),me,SLOT(eat()));
    connect(gfr,SIGNAL(hungry(QString)),me,SLOT(eat(QString)));



    //信号链接信号
    //connect(ui->Hungry,&QPushButton::clicked,gfr,&gfriend::hungry);


	//将按钮的点击事件与 主窗口函数中的hungrySlot函数绑定
	//这个hungrySlot函数主要用于进行发射 gfriend 类中的信号函数hungry以及其重载形式
    connect(ui->Hungry,&QPushButton::clicked,this, &MainWindow::hungrySlot);
	//在QT4中,槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系
	//在QT5之后中槽函数的调用顺序与信号的发射顺序相同
}

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

void MainWindow::hungrySlot()
{
    //发射自定义的信号
    emit gfr->hungry();
    emit gfr->hungry("意大利面");
}

void MainWindow::eatSlot()
{
    qDebug()<<"我带你去吃海鲜";
}

注意事项

  • 在使用QT5的信号槽连接方式的时候,需要注意,当遇到有函数重载情况下的信号函数、槽函数的时候需要使用函数指针指定
  • 信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址了, 所以如果我们在这种时候通过以上方式进行信号槽连接, 编译器就会报错。
  1. 主函数入口以及实际效果
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;
   w.show();
   return a.exec();
}

这里点击一次 Hungry 按钮,会有5次输出,通过源码可以分析这五次输出的过程,这里不多叙述但是需要注意的就是:

  • QT4中,槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系
  • QT5之后中槽函数的调用顺序与信号的发射顺序相同
    在这里插入图片描述

接下来分析一下,这五次输出的顺序:

  1. 首先点击按钮,在QT框架检测到点击事件发生之后,会立即调用MainWindow::hungrySlot中的函数
  2. MainWindow::hungrySlot函数中首先会发出无参版本的信号函数emit gfr->hungry();
  3. 之后,按照下面的定义的顺序,分别执行 两次无参版本的槽函数eat()
connect(gfr,girl1,me,m1);
connect(gfr,SIGNAL(hungry()),me,SLOT(eat())); 

4.再后来, MainWindow::hungrySlot函数中有参版本的信号函数 emit gfr->hungry("意大利面");
按照下面的定义的顺序,分别执行三次次有参数版本的槽函数eat()

connect(gfr,girl2,me,m2);
connect(gfr,girl2,this,&MainWindow::eatSlot);
connect(gfr,SIGNAL(hungry(QString)),me,SLOT(eat(QString))); 

5.但是请注意下面这行代码,这个就是信号函数的参数个数大于槽函数的参数个数以槽函数参数个数为基准,将多以的信号函数参数自动忽略

connect(gfr,girl2,this,&MainWindow::eatSlot);

总结

  • Qt4的信号槽连接方式因为使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug
  • Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生
  • 信号槽函数被重载之后, Qt4的信号槽连接方式不受影响
  • 信号槽函数被重载之后, Qt5中需要给被重载的信号或者槽定义函数指针
  • 强调一点,所谓的信号槽connect连接,并不会使程序阻塞在connect的槽函数这里
  • connect本质上就是在注册回调函数(也就是定义函数指针),等到if条件触发的时候就会调用这个函数指针!!!!

16.Qt定时器类QTimer

  • 在进行窗口程序的处理过程中, 经常要周期性的执行某些操作, 或者制作一些动画效果,看似比较复杂的问题使用定时器就可以完美的解决这些问题。
  • Qt中提供了两种定时器方式
    1. 一种是使用Qt中的事件处理函数(后续介绍)
    2. 另一种就是Qt中的定时器类 QTimer 的使用方法。

QTimer使用步骤:

  1. 只需创建一个QTimer类对象,
  2. 然后调用其 start() 函数开启定时器
  3. 此后QTimer对象就会周期性的发出 timeout() 信号。

16.1. 公共函数/槽函数

// 构造函数
// 如果指定了父对象, 创建的堆内存可以自动析构
QTimer::QTimer(QObject *parent = nullptr);

// 设置定时器时间间隔为 msec 毫秒
// 默认值是0,一旦窗口系统事件队列中的所有事件都已经被处理完,一个时间间隔为0的QTimer就会触发
void QTimer::setInterval(int msec);
// 获取定时器的时间间隔, 返回值单位: 毫秒
int QTimer::interval() const;

// 根据指定的时间间隔启动或者重启定时器, 需要调用 setInterval() 设置时间间隔
[slot] void QTimer::start();
// 启动或重新启动定时器,超时间隔为msec毫秒。
[slot] void QTimer::start(int msec);
// 停止定时器。
[slot] void QTimer::stop();

// 设置定时器精度
/*
参数: 
    - Qt::PreciseTimer -> 精确的精度, 毫秒级
    - Qt::CoarseTimer  -> 粗糙的精度, 和1毫秒的误差在5%的范围内, 默认精度
    - Qt::VeryCoarseTimer -> 非常粗糙的精度, 精度在1秒左右
*/
void QTimer::setTimerType(Qt::TimerType atype);
Qt::TimerType QTimer::timerType() const;	// 获取当前定时器的精度

// 如果定时器正在运行,返回true; 否则返回false。
bool QTimer::isActive() const;

// 判断定时器是否只触发一次
bool QTimer::isSingleShot() const;
// 设置定时器是否只触发一次, 参数为true定时器只触发一次, 为false定时器重复触发, 默认为false
void QTimer::setSingleShot(bool singleShot);

注意:

  • 当开始启动计时器的时候调用start()函数并没有设置时间间隔的时候即没有往start函数中传入参数,那么需要使用setInterval() 设置时间间隔

16.2. signals

这个类的信号只有一个, 当定时器超时时,该信号就会被发射出来。给这个信号通过conect()关联一个槽函数, 就可以在槽函数中处理超时事件了。

[signal] void QTimer::timeout();

16.3. static public function

// 其他同名重载函数可以自己查阅帮助文档
/*
功能: 在msec毫秒后发射一次信号, 并且只发射一次
参数:
	- msec:     在msec毫秒后发射信号
	- receiver: 接收信号的对象地址
	- method:   槽函数地址
*/
[static] void QTimer::singleShot(
        int msec, const QObject *receiver, 
        PointerToMemberFunction method);

示例程序

需求说明:

  • 点击按钮一直持续显示当前时间、再次点击此按钮就停止显示
  • 创建第二个按钮,点击一次按钮显示2s后的时间,再点击一次按钮再显示一次2s后时间
  1. 首先创建工程,并且在主窗口中添加组件
    在这里插入图片描述

  2. 创建的工程项目如下图所示
    在这里插入图片描述

  3. MainWindow.cpp 源文件中的代码如图所示

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QTime>
#include<QTimer>

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


    //持续的显示时间,也就是定时器持续的发出信号
    //创建定时器对象
    QTimer *timer = new QTimer(this);

    //修改定时器的精度
    timer->setTimerType(Qt::PreciseTimer);

    // 按钮点击显示时间
    connect(ui->loopBtn,&QPushButton::clicked,this,[=]
    {

        // 启动计时器
        if(timer->isActive()){

            timer->stop(); //关闭计时器
            ui->loopBtn->setText("开始");
        }
        else{
            ui->loopBtn->setText("关闭");
            //每1s发出一个定时器信号
            timer->start(1000);
        }

    });

    connect(timer,&QTimer::timeout,ui->curtime,[=]{
        QTime tm = QTime::currentTime();
        //将当前的时间格式化成系统时间,为字符串格式
        QString tim = tm.toString("hh:mm:ss:zzz");
        //设置要显示的时间
        ui->curtime->setText(tim);

    });


    //点击一次按钮显示当前的时间,定时器就是一次性的
    connect(ui->onceBtn,&QPushButton::clicked,this,[=]()
    {
            //获取2s以后的系统时间
        QTimer::singleShot(2000,this,[=](){
            QTime tm = QTime::currentTime();
            //将当前的时间格式化成系统时间,为字符串格式
            QString tim = tm.toString("hh:mm:ss:zzz");
            //设置要显示的时间
            ui->onceTime->setText(tim);
        });
    });

}

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

  1. 效果展示

在这里插入图片描述
在这里插入图片描述
部分来源参考网址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值