C++QT

文章目录

1. QT概述

1.1 什么是QT

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

常见GUI:

QT:支持多平台开发;支持css;面向对象特性体现突出;发展趋势良好。

MFC:只能在windows开发;运行程序效率高;库的安全性好;

1.2 QT的发展史

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

1.3 支持的平台

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

1.4 QT版本

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

  • 商业版:商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务。
  • 开源版(LGPL):开源的LGPL 版本,为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU 通用公共许可下,它是免费的。

1.5 Qt 的下载与安装

  • http://www.qt.io/download-open-source/
  • http://download.qt.io/archive/qt/

image-20230617192318611

image-20230617192628524

按照默认安装即可
Qt 对不同的平台提供了不同版本的安装包,可根据实际情况自行下载安装,本文档使用
qt-opensource-windows-x86-mingw530-5.13.0 版本进行讲解(32 位和64 位均可安装)

1.6 QT的优点

  • 跨平台,几乎支持所有的平台

  • 接口简单,容易上手,学习QT 框架对学习其他框架有参考意义。

  • 一定程度上简化了内存回收机制

  • 开发效率高,能够快速的构建应用程序。

  • 有很好的社区氛围,市场份额在缓慢上升。

  • 可以进行嵌入式开发。

1.7 成功案例

  • Linux 桌面环境KDE
  • Skype 网络电话
  • Google Earth 谷歌地图
  • VLC 多媒体播放器
  • VirtualBox 虚拟机软件
  • 咪咕音乐
  • WPS Office
  • 极品飞车

2. 创建QT项目

2.1 使用向导创建

  • 打开Qt Creator 界面选择
  • New Project 或者选择菜单栏【文件】-【新建文件或项目】菜单项
  • 弹出New Project 对话框,选择Qt Widgets Application

image-20230617205216497

image-20230617205254631

image-20230617215755059

2.2 手动创建

  • 添加一个空项目

image-20230617220114633

  • 选择【choose】进行下一步。设置项目名称和路径—> 选择编译套件–> 修改类信息–> 完成(步骤同上),生成一个空项目。在空项目中添加文件:在项目名称上单击鼠标右键弹出右键菜单,选择【添加新文件】

image-20230617220131998

  • 弹出新建文件对话框

image-20230617220150476

2.3 .pro 文件

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

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

.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 的特性
在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那
样替换已经指定的所有选项更安全。
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 重新更新
    this->setWindowTitle("hello world"); // 更改UI内容
    this->resize(600,400);
}

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


2.4 设置父对象

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 重新更新
    this->setWindowTitle("hello world");
    // 设置 完成之后可以拉伸
    this->resize(600,400);
    // 设置固定大小
    this->setFixedSize(QSize(200,200));

    QPushButton *button =  new QPushButton;
    // 设置父亲对象
    button->setParent(this);
}

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


image-20230618001225199

2.5 按钮设置属性

2.5.1 按钮设置文本
button->setText("红红红!");
2.5.2 设置移动
button->move(50,50);
2.5.3 设置固定大小
button->setFixedSize(QSize(400,400));

2.6 另外一种创建按钮方式

2.6.1 构造时创建
      QPushButton *button2 = new QPushButton("第二个按钮",this); // 包括设置文本和父对象
2.6.2 初始化后设置与构造函数设置的区别
  • 初始化后设置,显示为窗口的默认大小
  • 构造函数设置会导致窗口大小为按钮大小,需要重新设置大小。

2.7 对象模型(对象树)

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

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

当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
n 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)

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

QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。

  • 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。

image-20230619095633992

  • Qt 引入对象树的概念,在一定程度上解决了内存问题。
    • 当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
    • 任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。
    • 作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
    • 在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。

2.8 Qt窗口坐标体系

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

image-20230619095748503

3 信号与槽机制

3.1 信号与槽

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

image-20230619100459070

3.2 系统自带的信号与槽

connect()函数最常用的一般形式:connect(sender, signal, receiver, slot);
参数解释:

sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数(槽函数)

3.3 常用的信号

那么系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个

image-20230619101434174

3.4 自定义槽函数
#ifndef WIDGET_H
#define WIDGET_H
#include <QDebug>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots: // 添加槽函数
    void print();

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

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 重新更新
    this->setWindowTitle("hello world");
    // 设置 完成之后可以拉伸
    this->resize(600,400);
    // 设置固定大小
    this->setFixedSize(QSize(600,400));

//    QPushButton *button =  new QPushButton;
    // 设置父亲对象
//    button->setParent(this);
//    button->setText("中国红!");
//    // 按钮移动
//    button->move(50,50);
//    // 设置按钮大小
//    button->setFixedSize(QSize(400,400));
      QPushButton *button2 = new QPushButton("第二个按钮(关闭)",this);
      # 定义信号函数
      connect(button2,&QPushButton::clicked,this,&Widget::print);


}

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

void Widget::print(){
    qDebug()<<"hello world!"; // 槽函数实现
}

image-20230619110112323

3.5 自定义信号

image-20230619164419572

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

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

image-20230619221022227

3.6 信号槽拓展
  • 一个信号可以和多个槽相连
    如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。

  • 多个信号可以连接到一个槽
    只要任意一个信号发出,这个槽就会被调用

  • 一个信号可以连接到另外的一个信号
    当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式
    没有什么区别。

  • 槽可以被取消链接
    这种情况并不经常出现,因为当一个对象delete 之后,Qt 自动取消所有连接到这个对象上面的
    槽。

  • 信号槽可以断开
    利用disconnect 关键字是可以断开信号槽的

  • 使用Lambda 表达式
    在使用Qt5 的时候,能够支持Qt 5 的编译器都是支持Lambda 表达式的。
    在连接信号和槽的时候,槽函数可以使用Lambda 表达式的方式进行处理。

重载信号时需要利用函数指针来确定执行的槽函数与信号函数

 void (Teacher:: *teacherSignal)(QString) = &Teacher::hungry;
 void (Student:: *studentSignal)(QString) = &Student::treat;
3.6 Qt4版本的信号槽写法
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));

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

3.7 Lambda 表达式

C++11 中的Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。

Lambda表达式的基本构成:

[capture](parameters) mutable ->return-type
{
statement
}
  • 函数对象参数;
    [],标识一个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 按值进行传递外,其他参数都按引用进行传递。
  • ② 操作符重载函数参数;
    标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))
    和按引用(如:(&a,&b))两种方式进行传递。
  • 可修改标示符;
    mutable 声明,这部分可以省略。按值传递函数对象参数时,加上mutable 修饰符后,可以修改
    按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
qDebug() << m;
  • 函数返回值;
    ->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return 的地
    方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
  • 是函数体;
    {},标识函数的实现,这部分不能省略,但函数体可以为空。
3.7.1 lambda传参(无参数)
    auto fun=[](){
       qDebug()<<"hello world!222";
    };
    fun();

image-20230620113414659

3.7.2 lambda 传参(有参数)
    auto fun2=[](int a,int b){
       qDebug()<<"hello world!333";
       return a+b;
    };
    ClassIsOver();
    qDebug()<<fun2(2,3);
3.7.3 传值和传址
    int a = 10;
    auto fun3= [](int &a,int b){
       qDebug()<<"hello world!333";
       qDebug()<<"a==="<<a;
       a = 20;
       return a+b;
    };

    ClassIsOver();
    qDebug()<<fun3(a,3);

image-20230620113312085

4. QMainWindow

4.1 介绍

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

image-20230621000022651

分别对应菜单栏,菜单,菜单项

  • 创建菜单栏,通过QMainWindow 类的menubar()函数获取主窗口菜单栏指针
//创建菜单栏:
#include <QMenuBar>
QMenuBar --> QMenuBar(QWidget *parent = Q_NULLPTR)
//添加菜单栏:
QMainWindow --> void setMenuBar(QMenuBar *menuBar)
  • 创建菜单,调用QMenu 的成员函数addMenu 来添加菜单
//创建菜单:
#include <QMenu>
QMenu --> QMenu(const QString &title, QWidget *parent = Q_NULLPTR)
//添加菜单:
MenuBar --> QAction *addMenu(QMenu *menu)
  • 创建菜单项,调用QMenu 的成员函数addAction 来添加菜单项
//创建菜单项:
#include <QAction>
QAction --> QAction(const QString &text, QObject *parent = nullptr)
//添加菜单项:
QMenu --> addAction(const QAction *action)

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

#include "mainwindow.h"
#include <QMenuBar> //菜单栏
#include <QMenu> //菜单
#include <QAction> //菜单项
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//**********窗口属性设置*****************
this->setWindowTitle("主窗口");
this->setFixedSize(800, 600);
//***********创建菜单栏******************
//创建菜单栏
//注意:如果只有菜单栏,则在窗口处没有现象
QMenuBar *menu_bar = new QMenuBar(this);
this->setMenuBar(menu_bar);
    千锋智能物联网学院
//创建菜单
QMenu *menu1 = new QMenu("文件", this);
menu_bar->addMenu(menu1);
QMenu *menu2 = new QMenu("编辑", this);
menu_bar->addMenu(menu2);
QMenu *menu3 = new QMenu("构建", this);
menu_bar->addMenu(menu3);
//创建菜单项
QAction *act1 = new QAction("新建文件或项目", this);
menu1->addAction(act1);
QAction *act2 = new QAction("打开文件或项目", this);
menu1->addAction(act2);
//添加分割线
menu1->addSeparator();
QAction *act3 = new QAction("保存", this);
menu1->addAction(act3);
QAction *act4 = new QAction("另存为", this);
menu1->addAction(act4);
QAction *act5 = new QAction("复制", this);
act5->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
menu2->addAction(act5);
QAction *act6 = new QAction("粘贴", this);
act6->setShortcut(QKeySequence(tr("Ctrl+V")));
menu2->addAction(act6);
QAction *act7 = new QAction("剪切", this);
menu2->addAction(act7);
//当单击菜单项时,做出相应的处理
connect(act5, &QAction::triggered, [=]{
qDebug()<<"复制正在执行";
});
connect(act6, &QAction::triggered, [=]{qDebug()<<"粘贴正在执行";
});
}
MainWindow::~MainWindow()
{
}

image-20230621001246177

4.2 工具栏

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

//创建工具栏:
#include <QToolBar>
QToolBar --> QToolBar(QWidget *parent = Q_NULLPTR)
//添加工具栏:
QMainWindow -->
void addToolBar(QToolBar *toolbar)
void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar)
Qt::LeftToolBarArea 左边显示
Qt::RightToolBarArea 右边显示
Qt::TopToolBarArea 上边显示
Qt::BottomToolBarArea 下边显示

直接调用QMainWindow 类的addToolBar()函数获取主窗口的工具条对象,每增加一个工具条
都需要调用一次该函数。

  • 插入属于工具条的动作,即在工具条上添加操作。
    通过QToolBar 类的addAction 函数添加。
  • 工具条是一个可移动的窗口,它的停靠区域由QToolBar 的allowAreas 决定,包括:
  • Qt::LeftToolBarArea 停靠在左侧
  • Qt::RightToolBarArea 停靠在右侧
  • Qt::TopToolBarArea 停靠在顶部
  • Qt::BottomToolBarArea 停靠在底部
  • Qt::AllToolBarAreas 以上四个位置都可停靠
  • 使用setAllowedAreas()函数指定停靠区域:
    setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea)
  • 使用setMoveable()函数设定工具栏的可移动性:
    setMoveable(false)//工具条不可移动, 只能停靠在初始化的位置上
//创建工具栏
//注意:工具栏可以添加多个
QToolBar *toolbar = new QToolBar(this);
//将工具栏添加到窗口
//this->addToolBar(toolbar);//默认在最上面显示
this->addToolBar(Qt::LeftToolBarArea, toolbar); //设置默认在左边显示
//工具栏添加菜单项和分割线
QAction *act_tool1 = new QAction("欢迎", this);
QAction *act_tool2 = new QAction("编辑", this);
toolbar->addAction(act_tool1);
toolbar->addSeparator();
toolbar->addAction(act_tool2);
//设置工具栏的浮动状态,true:可以悬浮在窗口false:不可以
toolbar->setFloatable(false);
//设置运行工具栏的位置,设置为左边或者右边
toolbar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);

4.3 状态栏

  • 派生自QWidget 类,使用方法与QWidget 类似,QStatusBar 类常用成员函数:状态栏也只能最多有一个
创建状态栏:
QStatusBar -->
将控件添加到左边栏
void addWidget(QWidget *widget, int stretch = 0)
将控件添加到右边栏
void addPermanentWidget(QWidget *widget, int stretch = 0)
添加状态栏:
QMainWindow --> void setStatusBar(QStatusBar *statusbar)
//创建状态栏
//状态栏只能有一个
QStatusBar *statusbar = new QStatusBar(this);
//添加状态栏
this->setStatusBar(statusbar);
//给状态栏中添加文字信息或者按钮=
QLabel *label1 = new QLabel("左边信息", this);
statusbar->addWidget(label1);
QLabel *label2 = new QLabel("右边信息", this);
statusbar->addPermanentWidget(label2);
QPushButton *button = new QPushButton("设置", this);
statusbar->addWidget(button);

4.4 铆接部件

创建铆接部件:
QDockWidget -->
QDockWidget(const QString &title, QWidget *parent = Q_NULLPTR)
添加铆接部件:
QMainWindow -->
void addDockWidget(Qt::DockWidgetArea area, QDockWidget
*dockwidget)
Qt::LeftDockWidgetArea 左边
Qt::RightDockWidgetArea 右边
Qt::TopDockWidgetArea 上边
Qt::BottomDockWidgetArea 下边
//创建铆接部件
QDockWidget *dockwidget = new QDockWidget("这是一个铆接部件", this);
this->addDockWidget(Qt::TopDockWidgetArea, dockwidget);

4.5 中心部件

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

添加中心部件:
QMainWindow --> void setCentralWidget(QWidget *widget)
QTextEdit *edit = new QTextEdit("文本编辑器", this);
this->setCentralWidget(edit);

4.6 资源文件

Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。使用Qt Creator 可以很方便地创建资源文件。我们可以在工程上点右键,选择“添加新文件…”,可以在Qt 分类下找到“Qt 资源文件”:

image-20230621005359980

4.7 增加背景图

this->setAutoFillBackground(true);
//创建图片控件
QPixmap pix $=$ QPixmap(":/image/butterfly.png"). scaled(this->size());
// pix.load("://image/butterfly.png");
QPalette palette;
palette.setBrush(QPalette:: Background,QBrush(pix));
this->setPalette(palette);
this->setAutoFillBackground(true);
//创建图片控件
QPixmap pix = QPixmap(":/image/butterfly.png"). scaled(this->size());
// pix.load (":/image/butterfly.png");
QPalette palette;
palette.setBrush(QPalette:: Background, QBrush(pix));
this->setPalette(palette);

5. 对话框

5.1 基本概念

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

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

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

5.2 标准对话框

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

  • QColorDialog: 选择颜色;
  • QFileDialog: 选择文件或者目录;
  • QFontDialog: 选择字体;
  • QInputDialog: 允许用户输入一个值,并将其值返回;
  • QMessageBox: 模态对话框,用于显示信息、询问问题等;
  • QPageSetupDialog: 为打印机提供纸张相关的选项;
  • QPrintDialog: 打印机配置;
  • QPrintPreviewDialog:打印预览;
  • QProgressDialog: 显示操作过程。
5.2.1 模态对话框
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
5.2.2 非模态对话框
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();

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

QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();

上面的代码是有问题的:dialog 存在内存泄露!dialog 使用new 在堆上分配空间,却一直没有delete。

解决方案也很简单:将MainWindow 的指针赋给dialog 即可。还记得我们前面说过的Qt 的对象系统吗?不过,这样做有一个问题:如果我们的对话框不是在一个界面类中出现呢?由于QWidget 的parent必须是QWidget 指针,那就限制了我们不能将一个普通的C++ 类指针传给Qt 对话框。另外,如果对内存占用有严格限制的话,当我们将主窗口作为parent 时,主窗口不关闭,对话框就不会被销毁,所以会一直占用内存。在这种情景下,我们可以设置dialog 的WindowAttribute:

QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose); // 在这里
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();

setAttribute()函数设置对话框关闭时,自动销毁对话框。

5.3 消息对话框

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

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

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

  • 显示关于Qt 对话框。该对话框用于显示有关Qt 的信息。
void aboutQt(QWidget * parent, const QString & title = QString()):
  • 与QMessageBox::critical()类似,不同之处在于这个对话框提供一个普通信息图标。
StandardButton information(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton
  • 与QMessageBox::critical ()类似,不同之处在于这个对话框提供一个问号图标,并且其显示的按钮是“是”和“否”。
StandardButton question(QWidget * parent,const QString & title,
const QString & text,
StandardButtons buttons = StandardButtons( Yes | No ),
StandardButton defaultButton = NoButton)
  • 与QMessageBox::critical()类似,不同之处在于这个对话框提供一个黄色叹号图标。
StandardButton warning(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton)

QMessageBox演示

if (QMessageBox::Yes == QMessageBox::question(this,
tr("Question"), tr("Are you OK?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes))
{
QMessageBox::information(this, tr("Hmmm..."),
tr("I'm glad to hear that!"));
}
else
{
QMessageBox::information(this, tr("Hmmm..."),
tr("I'm sorry!"));
}

这个对话框的父窗口是this。QMessageBox 是QDialog 的子类,这意味着它的初始显示位置将会是在parent 窗口的中央。 第二个参数是对话框的标题。第三个参数是我们想要显示的内容。 第四个参数是关联的按键类型,我们可以使用或运算符(|)指定对话框应该出现的按钮。比如我们希望是一个Yes 和一个No。 最后一个参数指定默认选择的按钮。这个函数有一个返回值,用于确定用户点击的是哪一个按钮。按照我们的写法,应该很容易的看出,这是一个模态对话框,因此我们可以直接获取其返回值。QMessageBox 类的static 函数优点是方便使用,缺点也很明显:非常不灵活。

我们只能使用简单的几种形式。为了能够定制QMessageBox 细节,我们必须使用QMessageBox 的属性设置API。如果我们希望制作一个询问是否保存的对话框,我们可以使用如下的代码:

QMessageBox msgBox;
msgBox.setText(tr("The document has been modified."));
msgBox.setInformativeText(tr("Do you want to save your changes?"));
msgBox.setDetailedText(tr("Differences here..."));
msgBox.setStandardButtons(QMessageBox::Save
| QMessageBox::Discard
| QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret)
{
case QMessageBox::Save:
qDebug() << "Save document!";
break;
case QMessageBox::Discard:
qDebug() << "Discard changes!";
break;
case QMessageBox::Cancel:
qDebug() << "Close document!";
break;
}

msgBox 是一个建立在栈上的QMessageBox 实例。我们设置其主要文本信息为“The document has beenmodified.”,informativeText 则是会在对话框中显示的简单说明文字。下面我们使用了一个detailedText,也就是详细信息,当我们点击了详细信息按钮时,对话框可以自动显示更多信息。我们自己定义的对话框的按钮有三个:保存、丢弃和取消。然后我们使用了exec()是其成为一个模态对话框,根据其返回值进行相应的操作。

6.布局管理器

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

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

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

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

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

6.1 系统布局

系统给我们提供的布局的控件

image-20230621211019387

6.2 利用widget 做布局

第二种布局方式是利用控件里的widget 来做布局,在Containers 中

image-20230621211211302

在widget 中的控件可以进行水平、垂直、栅格布局等操作,比较灵活。再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观,下面是一个登陆窗口,利用widget 可以搭建出如下登陆界面:

image-20230621211307819

7.常用控件

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

7.1 QLabel

QLabel 是我们最常用的控件之一,其功能很强大,我们可以用来显示文本,图片和动画等。

7.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 则会自动打开。

7.1.2 显示图片

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

void setPixmap(const QPixmap &)

首先定义QPixmap 对象,加载图片,设置到QLabel

QPixmap pixmap;
pixmap.load(":/Image/boat.jpg");
QLabel *label = new QLabel;
label.setPixmap(pixmap);
7.1.3 显示动画

可以使用QLabel 的成员函数setMovie 加载动画,可以播放gif 格式的文件

void setMovie(QMovie * movie)

首先定义QMovied 对象,并初始化:

QMovie *movie = new QMovie(":/Mario.gif");
//播放加载的动画:
movie->start();
//将动画设置到QLabel 中:
QLabel *label = new QLabel;
label->setMovie(movie);

7.2 QLineEdit

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

7.2.1 设置/获取内容

获取编辑框内容使用text(),函数声明如下:

QString text() const
  • 设置编辑框内容
void setText(const QString &)
7.2.2 设置显示模式

使用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)
    

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

// smallwidget.h
class SmallWidget : public QWidget
{
Q_OBJECT
public:
explicit SmallWidget(QWidget *parent = 0);
signals:
public slots:
private:
QSpinBox* spin;
QSlider* slider;
};
// smallwidget.cpp
SmallWidget::SmallWidget(QWidget *parent) : QWidget(parent)
{
spin = new QSpinBox(this);
slider = new QSlider(Qt::Horizontal, this);
// 创建布局对象
QHBoxLayout* layout = new QHBoxLayout;
// 将控件添加到布局中
layout->addWidget(spin);
layout->addWidget(slider);
// 将布局设置到窗口中
setLayout(layout);
// 添加消息响应
connect(spin,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),slider, &QSlider::setValue);
connect(slider, &QSlider::valueChanged,
spin, &QSpinBox::setValue);
}

image-20230621225927166

8. Qt 消息机制和事件

8.1 事件处理过程

事件(event)是由系统或者Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。

一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。事件处理过程:

  1. 在Qt 内部,Qt 通过QApplication::exec()启动的主事件循环不停的抓取事件队列中的事件。
  2. 当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent。
  3. 在事件对象创建完毕后,Qt 将这个事件对象传递给QObject 的event()函数。event()函数并不直接处理事件,
    而是按照事件对象的类型分派给特定的事件处理函数(event handler)。
    event()函数主要用于事件的分发:

8.2 常用事件处理

在所有控件的父类QWidget 中,定义了很多事件处理的回调函数。这些函数都是protected virtual 的,也就是说,我们可以在子类中重新实现这些函数。实现函数的要遵循虚函数的语法规则,自定义的类中保证函数名、参数的一致性。

8.2.1 鼠标事件
//重写父类的虚函数
protected:
virtual void mousePressEvent(QMouseEvent *); //鼠标点击事件
virtual void mouseReleaseEvent(QMouseEvent *); //鼠标抬起事件
virtual void mouseDoubleClickEvent(QMouseEvent *); //鼠标双击事件
virtual void mouseMoveEvent(QMouseEvent *); //鼠标移动事件
8.2.2 滚轮事件(QWheelEvent)
//virtual void wheelEvent(QWheelEvent *); //鼠标滚轮滑动
//鼠标滚轮滑动
void Widget::wheelEvent(QWheelEvent *e)
{
if(e->orientation()== Qt::Vertical) //如果方向是垂直
{
QPoint point = e->angleDelta();
QString str = QString("滚轮垂直滑动(%1, %2)")
.arg( point.x() ).arg(point.y());
ui->label->setText(str);
}
}
8.2.3 键盘事件(QKeyEvent)
//virtual void keyPressEvent(QKeyEvent *); //键盘按下事件
//virtual void keyReleaseEvent(QKeyEvent *); //键盘抬起事件
//键盘按下事件
void Widget::keyPressEvent(QKeyEvent *e)
{
QString str;
    switch(e->modifiers()) //修饰键盘
{
case Qt::ControlModifier:
str = "Ctrl+";
break;
case Qt::AltModifier:
str = "Alt+";
break;
}
switch(e->key()) //普通键
{
case Qt::Key_Left:
str += "Left_Key Press";
break;
case Qt::Key_Right:
str += "Rigth_Key Press";
break;
case Qt::Key_Up:
str += "Up_Key Press";
break;
case Qt::Key_Down:
str += "Down_Key Press";
break;
case Qt::Key_Z:
        str += "Z_Key Press";
break;
}
ui->label->setText(str);
}
8.2.4 大小改变事件(QResizeEvent)
当窗口大小发生变化时被调用,参考代码:
//virtual void resizeEvent(QResizeEvent *); //大小改变事件
//大小改变事件
void Widget::resizeEvent(QResizeEvent *e)
{
//变化前的窗口大小
qDebug() << "e->oldSize() = " << e->oldSize();
//变化后的窗口大小
qDebug() << "e->size() = " << e->size();
}
8.2.5 进入离开区域事件(enterEvent、leaveEvent)
//virtual void enterEvent(QEvent *); //进入事件
//virtual void leaveEvent(QEvent *); //离开事件
//进入事件
void Widget::enterEvent(QEvent *)
{qDebug() << "enterEvent";
}
//离开事件
void Widget::leaveEvent(QEvent *)
{
qDebug() << "leaveEvent";
}

9 绘图

9.1 Qt绘图机制

Qt 的绘图机制为屏幕显示和打印显示提供了统一的API 接口,主要有三部分组成:QPainter 类、QPaintDevice
类和QPaintEngine 类
l QPainter 类提供了画图操作的各种接口,可方便地绘制各种各样的图形。
l QPaintDevice 类提供可用于画图的空间,及画图容器。
lPaintEngine 类是抽象类,提供了QPainter 如何在指定的平台上给指定的设备绘画的抽象接口,对开发者而言,
一般不会用到。

image-20230621233533906

9.2 绘图事件

void Widget::paintEvent(QPaintEvent *e)
{
qDebug()<<"in paintEvent"<<"recet"<<e->rect(); //得到需要重新绘制的区域
QPainter paint(this); //定义画家(请了一个画家来画画),画在主窗口上
//画家画图片,作为背景图片
paint.drawPixmap(0,0,this->width(),this->height(),
QPixmap("../Image/bk.jpg"));
QPen pen; //定义一只画笔
pen.setColor(QColor(255,0,255)); //设置画笔的颜色
pen.setWidth(5); //设置画笔的宽度
pen.setStyle(Qt::DashDotLine); //设置画笔线条的风格
paint.setPen(pen); //画家使用这只画笔
paint.drawLine(30,30,500,30);//画家画水平的线条
paint.drawLine(30,30,30,500);//画家画垂直的线条
pen.setColor(Qt::yellow);//设置画笔的颜色
pen.setStyle(Qt::SolidLine);//设置画笔线条的风格
pen.setWidth(3);//设置线条的宽度
paint.setPen(pen);//画家使用这只画笔
QBrush brush(Qt::blue);//定义一把蓝色的画刷
brush.setStyle(Qt::DiagCrossPattern);//设置画刷的风格
paint.setBrush(brush);//画家使用这把画刷
paint.drawRect(50,50,150,200);//画家画矩形
paint.setPen(QPen());
paint.setBrush(QBrush(QColor(255,187,255),Qt::Dense3Pattern));
paint.drawEllipse(280,60,180,250);//画家画椭圆
}

9.3 刷新绘图区域

//绘图事件
void Widget::*paintEvent*(QPaintEvent *)
 {
QPainter p(this); //在窗口上绘图
 p.drawPixmap(x,200,100,100,QPixmap("../Image/face.png"));
 p.drawPixmap(i,j,100,100,QPixmap("../Image/face.png"));
}
void Widget::on_pushButton_clicked()
{
 x += 20;
 if(x > this->width())
 {
 x = 0;
 }
 this->update(); //刷新绘图区
}
void Widget::*mousePressEvent*(QMouseEvent *e)
{i = e->pos().x();
j = e->pos().y();
this->update(); //刷新绘图区域
}

参考学习视频

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐