C++(week16): C++提高:(六) Qt提高

文章目录

四、Qt的元对象系统

为了实现信号槽 (目的),所以才设计了元对象系统 (手段)。

元数据:不是对象本身的主要内容,而是对象的额外信息(如文件名、文件路径、修改时间)。

元对象系统(Meta-Object System,简称MOS)是一种编程语言特性,用于支持反射(reflection)和元编程(metaprogramming)。它允许程序在运行时获取关于自身结构和行为的信息,甚至能够动态地修改自己的结构和行为。

在这里插入图片描述


1.元对象和MOC

元对象:保存了对象的额外信息。额外分配了内存。
在这里插入图片描述


(1)自省 和 反射

1.自省
在这里插入图片描述

2.反射
反射:为了实现间接访问。运行时,传入字符串,根据字符串调用函数。
反射:在运行时,可以获取并修改对象的结构


(2)Qt是怎样支持元对象系统的?

标准C++不支持元对象

MOC:元对象编译。

Qt需要两次编译:先

在这里插入图片描述


(3)支持元对象系统的三个要求

1.继承QObject类,且将QObject作为继承的第一个基类。 以确保内存位置是在第一个。
2.使用Q_OBJECT宏 (Q_OBJECT宏添加了一些函数的定义)
在这里插入图片描述

3.使用元对象的类,需要单独使用一个头文件,不能放在main.cpp中
在这里插入图片描述

在这里插入图片描述


(4)元对象系统的功能

1.支持反射,运行时获取和调整对象的结构
2.信号和槽
3.运行时类型转换
4.对象树


(5)动态属性

反射带来的好处:反射没有带来效率,但带来了更好的灵活性、可维护性。 lose函数不需要修改。
可以在运行的过程中,动态地修改。

在这里插入图片描述

反射在工作中的应用场景:
(1)游戏开发:导表工具,转化为python或lua代码
(2)互联网开发:路径路由,根据服务器网页路径调用不同的函数


2.信号和槽机制

信号和槽机制的目的:真正实现对象间的通信。并且是松耦合

通过connect函数实现耦合。slots、signals、emit是Qt的伪关键字,会通过MOC编译转换为标准C++。
而面向对象的标准C++,实现对象间通信的方式还是函数调用:直接调用 或 回调函数。


(1)信号与槽机制的基本原理

一读一写的流式通信很痛苦,收发错位就有bug。想办法把发和收封装为原子操作。

在这里插入图片描述
HTTP协议把四步合为一个事务。发明者参考了函数调用。
在这里插入图片描述

A与B之间进行通信
在这里插入图片描述
当B的数量很多的时候,这样写起来并不优雅。
在这里插入图片描述
在这里插入图片描述


A执行发送信息的代码,不要出现任何B的信息。

发送方,发射信号。接收方,调用槽函数。


(2)自定义信号、自定义槽函数

总结:回调函数、观察者模式

A发,B收
自定义信号、自定义槽、在发射信号之前关联信号和槽

①自定义信号
//自定义信号
signals:  
    void signalA();  

(1)signals:Qt的伪关键字
(2)自定义信号的设计,就是一个普通的成员函数声明
①放在signals访问权限控制符下面
②返回值必须是void
③只写声明不写定义,信号函数的定义由MOC自动添加


②自定义槽
//自定义槽函数
public slots:
    void slotsB();
    void slotB1(int i);
    void slotB2(int i, int j);

(1)槽函数的访问权限控制符 public/protected/private slots
(2)槽函数的书写和普通的成员函数没有任何区别
(3)槽函数的参数和返回值应当和信号保持一致。槽函数的参数个数必须小于等于信号的参数个数,否则会报错。用SIGNAL宏关联会在运行阶段报错,用指向成员函数的指针关联会在编译阶段报错。


关联 connect

connect的两种重载形式:
1.字符串:SIGNAL宏、SLOT宏

QObject::connect(&a, SIGNAL(signalA()), &b, SLOT(slotB()));

SIGNAL():把函数名变为字符串
SLOT():把槽函数变为字符串。


2.指向成员函数的指针

QObject::connect(&a, &A::signalA, &b, &B::slotB);

第二种更好。
因为,当槽函数的参数个数大于信号的参数个数时,会报错。
第一种会在运行阶段才报错。第二种在编译阶段就报错。
当然是报错越早越好,所以第二种connect好。


3.第三种重载形式:没有接收者。三个参数是函数对象,不用继承。适合简单功能。
在这里插入图片描述

QObject::connect(ui->pushButton, &QPushButton::clicked,
					[=](){
				 ui->label->setText("world");
			     });

connect相当于在发送方和接收方之间建立了一条通信的信道。
多次connect不会去重。
发射信号,关联的槽函数会自动调用。是同步的。


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


// 字符串形参
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const
char *signal, const QObject *receiver, const char *method, Qt::ConnectionType
type = Qt::AutoConnection)

// 没有接收者的版本
[static] QMetaObject::Connection QObject::connect(const QObject *sender,
PointerToMemberFunction signal, Functor functor)

// 函数指针形参
[static] QMetaObject::Connection QObject::connect(const QObject *sender,
PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction
method, Qt::ConnectionType type)

第一种重载形式
在这里插入图片描述

SIGNAL():把函数名变为字符串
SLOT():把槽函数变为字符串。


connect的另一种重载形式:指向成员函数的指针
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


(3)信号与槽的特点

Qt万能头文件

#include <QtWidgets/QtWidgets>

①槽函数的调用是同步的

槽函数的调用是同步的。emit 信号要等对应的槽函数执行完毕后才能返回。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


让信号携带参数

信号的参数个数要大于等于槽函数的参数个数
最好,信号的参数和槽一致。

指向成员函数的指针 (C++11)。报错在编译阶段。
SIGNAL宏和SLOT宏,报错在运行阶段。
在这里插入图片描述

处理重载的函数:qOverload

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


如果重复connect

connect几次,发射一次就会执行几次槽函数。
即:一次信号,多个槽函数执行。(即使绑定的完全一致)

connect是建立一条连接。

在这里插入图片描述

在这里插入图片描述
应该是可以链式调用。在槽函数中也可以发射新的信号


交换信号和槽的地位

1.槽不能当信号使用。但是槽函数里面可以发射信号。
2.信号可以当槽用。
在这里插入图片描述


支持元对象系统的类的定义
在这里插入图片描述

A信号发射,导致B信号发射,导致C的槽函数执行。链式调用。
在这里插入图片描述


不要循环关联,会栈溢出。
在这里插入图片描述


(4)减少重复槽函数

1.自定义信号,让其有参数


2.自定义槽函数,并在其中获取发射方的信息:sender() + qobject_cast
(1)用sender()获取发射信号对象的地址 QObject *
sender只能在槽函数中调用。

[protected] QObject *QObject::sender() const
//Returns a pointer to the object that sent the signal, if called in a slot activated by a signal

(2)用qobject_cast<>QObject * 向下转型为 自己需要的对应组件的指针,如QPushButtton *
【元对象系统的安全向下转型(基类指针转为派生类指针,如QObject *QPushButton*)】

//QObject *向下转型为QPushButton *
QPushButton * pbtn = qobject_cast<QPushButton *> (sender()); 

举例:

///mainwindow.h
private slots:
    //自定义槽函数
    void do_btn_clicked();
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
    ui->setupUi(this);  //setUi是创建窗口中的组件。在这之后才能进行connect关联
    //多个信号,关联一个槽函数
    QObject::connect(ui->btn1,&QPushButton::clicked,this,&MainWindow::do_btn_clicked);
    QObject::connect(ui->btn2,&QPushButton::clicked,this,&MainWindow::do_btn_clicked);
    QObject::connect(ui->btn3,&QPushButton::clicked,this,&MainWindow::do_btn_clicked);
}

void MainWindow::do_btn_clicked()
{
	//具体实现
}

(5)信号与槽的优缺点:信号槽 vs 回调

1.优点
(1)松耦合:把发送方和接收方在代码层面上分开了。
在发送方的代码片段中,没有出现任何接收方的信息【信号槽适合复杂的场景,回调适合性能要求高的场景】
(2)类型安全:connect会检查信号的参数个数要大于等于槽的参数个数
(3)关联自由:信号可以当槽用;槽不能当信号用,但可以在槽函数体中发射信号
(4)生态好:有很多Qt框架自带的信号和槽


2.缺点
性能差。信号槽的时间开销是10倍非虚函数。【但是也就是几纳秒慢到了几十纳秒。比起IO的几十毫秒还是可以忽略的。在IO密集型的场景,可以接受。在CPU密集型场景可能不能接受,如金融量化】


3.对象树

对象树系统,为了内存管理
内存管理:标准C++用的是RAII,Qt用的是对象树。

所有对象之间构成树状关系。父节点被销毁,孩子结点被自动回收。

在这里插入图片描述


(1)基础复习:四种指针

对象如果一直存在,可以用引用。引用不能改变指向,指针可以改变指向
对象一开始有可能不存在,用指针。
负责创建和销毁:用智能指针。
所有权独享:unique_ptr,且效率高。共享所有权,shared_ptr (效率略低,因为有引用计数)。

在这里插入图片描述


(2)依赖注入

1.一种比较好的编程方式:依赖注入 (dependency injection,DI)
(1)以前,A构造时,在初始化列表中构造B。耦合性太高。两者
(2)现在,用setB中才构造B。后面才注入依赖关系。(避免一个构造函数失败导致整体构造失败)

2.依赖注入的分类
(1)设值注入:set_xxx()方法
(2)构造注入:
在这里插入图片描述


(3)组合模式

组合模式也是一种设计模式。

1.定义:
(1)所有的对象构成树结构
(2)树中的结点应该拥有公共的类型


2.好处
写一个函数,既可以适用于整棵大树,也可小树、叶子结点

3.适用场景
(1)不适合适用组合模式的场景
叶子和非叶子没有公共的类型
在这里插入图片描述

(2)适合适用组合模式的场景
目录也可以当作目录文件。叶子和非叶子有着公共类型。
公共的基类,用容器保存 vector<Base *>
在这里插入图片描述


4.对象树使用了组合模式
(1)对象树是一个树形结构
(2)对象树中的所有对象都是QObject的子类

在这里插入图片描述


(4)Qt中的内存申请

1.根结点申请在栈上
2.所有的孩子结点可以(最好)申请在堆上。
堆上的数据不需要delete,父结点析构时会自动调用子节点的析构函数。
在这里插入图片描述


4.事件系统

信号与槽:对象间通信
事件系统:外界和进程通信

在这里插入图片描述


(1)事件和事件循环

1.事件:外界发生了事情,某个fd就绪了

while(1){
	select/epoll_wait();
	switch(fd){
		case fd: call event_handler();
		...		
	}
}

2.事件的细节
(1)硬件:硬件产生
(2)操作系统:操作系统采集,原生窗口事件,分配给事件队列
(3)进程:Qt进程一直在执行事件循环,如果有事件到来,相当于文件描述符就绪。将原生窗口事件封装为QEvent,发送给具体的QObjet对象
(4)对象:某个QObject对象收到事件后:先eventfilter,再event,再event handler。若event handler未处理完,再交给对象树上的父亲。
在这里插入图片描述

3.QObject收到事件后:
立即调用QObject::event(),将对应的事件分配给对应的event handlers。(底层就是分支结构,switch case 或 if else,根据事件的不同,调用不同的event handlers)

在这里插入图片描述


(2)事件到达,处理事件的三个步骤

(1)event filter:事件过滤器。
①在不继承的情况下,依然可以添加事件处理的能力。
②制作一个过滤器。继承QObject,再重写eventFilter函数。
③安装过滤器到对象上面。installEventFilter。
(2)event:事件分配。继承并重写event函数,调用基类的event函数(做了switch分配,否则不执行hanler)
(3)event handler:事件处理函数。继承并重写handler。


①事件过滤器

1.定义
事件过滤器:简化事件处理的代码。在不继承的情况下,给对象增加事件处理的能力。

void installEventFilter(QObject *filterObj);

2.实现事件过滤器
写一个过滤器类,继承QObject,重写 eventFilter()

[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)

//Filters events if this object has been installed as an event filter for the watched object.
class MyFilter : public QObject
{
    Q_OBJECT
public:
    explicit MyFilter(QObject *parent = nullptr);
    bool eventFilter(QObject *watched, QEvent *ev) override
    {
        if(ev->type() == QEvent::MouseButtonPress){
            qDebug() << watched << "is pressed!";
        }
        return false;
    }
signals:

};

每个对象安装事件过滤器

filter = new MyFilter(this);
out->installEventFilter(filter);
mid->installEventFilter(filter);
in->installEventFilter(filter);

3.事件过滤器和事件处理器同时存在:
先event filter,再event,最后event handler。若三者都没有处理完,则交给父亲处理。
父亲亦是event filter、event、event handler的顺序进行处理。


4.若安装了多个过滤器,则倒序生效。最后安装的过滤器先生效。


②event函数

4.派生类中,调用基类的虚函数
在这里插入图片描述

自定义事件处理函数
在这里插入图片描述

坏处是:修改event()可能会对多个事件造成影响,不推荐。


③事件处理函数 event handler

1.event之后,再调用 event handler
2.eventhandler 不是一个函数,而是一堆函数
3.不同类型的事件对应不同的event handler。只修改一个event handler不会影响其他事件。(而修改event函数可能影响其他很多事件)


同时使用event和event handler

event handler 要求调用基类的event()
在这里插入图片描述


人造按钮

QLabel : 继承 + 重写

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


同时使用event filter、event、event handler

代码链接:


(3)多个对象之间的事件传递

(4)事件的传递:当某个对象没有把事件处理完,事件就传递给它对象树上的父亲。
①event filter返回false
②event 返回false
③event handler调用ignore

在这里插入图片描述

move是自己的左上顶点和父亲的左上顶点的距离。
在这里插入图片描述


event()的返回值bool,代表本对象是否完成了该事件的处理。
只要有event handler,就说明事件可以完成了,不必往上传递。
但如果event handler中有调用ignore(),事件就是未完成的,还需要向上传递。

在这里插入图片描述


事件传递的过程

1.鼠标事件先传给离用户最近的对象(z轴)
键盘事件会发给焦点所在的对象

2.对象处理事件:先event(),后event_handler()
(1)event:return true,说明处理完毕,不会调用handler,也不会传给(对象树上的)父亲
(2)event:return false,未处理完毕,不会调用handler,直接传递给父亲
(3)event:调用了基类的event。
①如果handler不存在,会传递给父亲。
②如果handler存在 / accept,不传递给父亲。
③如果handler存在 / ignore,传递给父亲。


事件系统 + 信号槽,让两个组件互动

在这里插入图片描述



五、Qt_Widgets模块

1.QWidget类的概念

1.QWidget类看得见(其上可以绘制图案,支持绘图事件)、摸得着(支持键盘鼠标事件)的组件,是一个ui元素。本质是一个矩形画布。


2.功能:容器、输入内容、显示内容
在这里插入图片描述


3.QWidget的继承关系
本质:QWidget继承:
(1)继承QObject:支持元对象系统、信号和槽、对象树
(2)QPaintDevice:画布、矩形。看得见(使用paintEvent绘制),摸得着(可以响应键鼠事件)。

class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice

4.孩子需要比父亲小,且在父亲的范围之内。否则显示不全或无法显示。

在这里插入图片描述


paintEvent

2.paintEvent是绘图事件的event handler

在这里插入图片描述

void paintEvent(QPaintEvent *event) override{
    qDebug() << mosquito_x << "," << mosquito_y;
    QPainter painter(this);  //构造画笔
    QPixmap pixmap(":/new/prefix1/mosquito.jpeg");   //加载图片到内存
    painter.drawPixmap(mosquito_x, mosquito_y, 40, 40, pixmap);  //绘图:坐标,大小,图
    QWidget::paintEvent(event);  //调用基类的paintEvent,以免漏掉事件
}

小项目3:打蚊子

代码链接:https://github.com/WangEdward1027/Qt/tree/main/kill_mosquitoes

两种方案:
1.QLabel:Label的Pixmap中放蚊子,每次点击移动Label
2.PaintEvent:每次点击事件,event handler之paintEvent临时重绘一次文字图片加载到新的位置。


重写了两个event handler

class MyWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MyWidget(QWidget *parent = nullptr);
    void mousePressEvent(QMouseEvent *event) override{
        //srand(time(nullptr));
        if(event->x() - mosquito_x <= 40 && event->x() - mosquito_x >= 0
                && event->y() - mosquito_y <= 40 && event->y() - mosquito_y >= 0){
            emit addPoint();
            mosquito_x = rand()%560;
            mosquito_y = rand()%360;
            update(); // 强制重绘
        }
        QWidget::mousePressEvent(event);
    }
    void paintEvent(QPaintEvent *event) override{
        qDebug() << mosquito_x << "," << mosquito_y;
        QPainter painter(this);
        QPixmap pixmap(":/new/prefix1/mosquito.jpeg");
        painter.drawPixmap(mosquito_x,mosquito_y,40,40,pixmap);
        QWidget::paintEvent(event);
    }
signals:
    void addPoint();
private:
    int mosquito_x;
    int mosquito_y;
};

2.窗口

(1)定义

没有嵌入到父组件中的组件被称为窗口。
窗口是最大的容器。


(2)成为窗口的条件

(1)没有父节点,调用show()
(2)有父节点,Qt::Window
(3)QDialog、QMainWindow及其子类


如果一个QWidget对象是根结点(没有父结点),调用show方法,则是独立的窗口。任务栏会有两个窗口。

QWidget w1;
QWidget w2;
w1.show();
w2.show();

如果QWidget对象有父亲,则不会是独立窗口,会显示在父窗口里面。

QWidget w1;
QWidget *w2 = new QWidget(&w1);
w1.show();
w2->show();

3.有父亲的QWidget对象,也可以是一个窗口。但任务栏只有一个图标。
父亲窗口销毁,则会将孩子窗口也销毁。

QWidget w1;
QWidget *w2 = new QWidget(&w1, Qt::Window);
w1.show();
w2->show();

4.无边框窗口

QWidget w1;
QWidget *w2 = new QWidget(&w1, Qt::Window | Qt::FramelessWindowHint);
w1.show();
w2->show();

(3)QWidget当中和窗口相关的属性和方法

一、窗口
1.窗口功能:①sho() ②hide(),隐藏为后台进程
2.顶层窗口:最大化、最小化 showMinimized、全屏显示、正常显示
3.窗口内容:update():相当于手动触发绘制事件 (强制重绘)
在这里插入图片描述


二、几何位置、大小
1.move():移动位置
2.resize():改变大小

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


3.对话框 QDialog

QDialog和QMainWindow以及它们的子类对象一定是窗口。

QWidget w1;
QMainWindow *w2 = new QMainWindow(&w1);
QDialog *w3 = new QDialog(&w1);

(1)模态

1.非模态对话框
用show()方法展示。上层的对话框没有退出,不会阻塞整个进程。

2.模态对话框
用exec()。上层的对话框没有退出,会阻塞整个进程。

在这里插入图片描述


QDialog一般不好用。可以选择继承QDialog
(1)ui文件
(2)标准对话

下文介绍

在这里插入图片描述


(2)自定义方式

addNew,设计师界面类。
在这里插入图片描述


(3)子类:标准对话框

在这里插入图片描述


①QColorDialog:颜色选择对话框

用其静态成员函数:getColor()。临时生成。
若要经常使用,还是要构建。
在这里插入图片描述


QFileDialog:文件选择对话框

获取文件路径

QString filepath = QFileDialog::getOpenFileName();
qDebug() << filepath;
QString filepath = QFileDialog::getOpenFileName(this, "title", QStrng(),
					  "source code (*.c *.cc *.cp);; header file(*.h)");
qDebug() << filepath;
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"/home",
                                                tr("Images (*.png *.xpm *.jpg)"));

文件选择对话框
在这里插入图片描述


③QFontDialog:字体选择对话框

静态函数
在这里插入图片描述


QMessageBox:信息框

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


举例:

void MainWindow::on_pushButton_clicked()
{
    //    QDialog dialog(this);
    //    dialog.setWindowTitle("模态对话框");
    //    dialog.exec();  //弹出模态对话框
    QMessageBox::StandardButton btn = QMessageBox::information(
                this,                              // 父窗口
                "模态对话框",                       // 标题
                "看看QTimer时间是否有卡住?",        // 消息内容
                QMessageBox::Ok | QMessageBox::No // 按钮类型
                );
}

在这里插入图片描述


4.主窗口 QMainWindow

标题栏、菜单栏、工具栏、中央区域、状态栏

主窗口比一般窗口多出的部分:菜单栏、工具栏、状态栏
在这里插入图片描述


(1)菜单栏:QMenubar

1.组成
菜单栏QMenubar由一个个菜单QMenu组成。
菜单由QAction组成。

在这里插入图片描述

2.QAction的信号,是triggered()

用ActionEditor进行编辑
在这里插入图片描述


3.QAction和QPushButton的区别:
QActon是一个动作,QPushButton是一个实体
同一个QAction可以出现在不同的位置(菜单栏和工具栏),而QPushButton只能出现在一个位置
【ui编辑器中的Action编辑器】


(2)工具栏:QToolbar

在这里插入图片描述

加入icon


(3)状态栏:QStatusBar


5.常见的简单组件

(1)用来显示的组件:Display Widgets

在这里插入图片描述


Label

QLabel -> QFrame(设置边框) -> Widget

1.设置边框
在这里插入图片描述
在这里插入图片描述


2.Label显示markdown格式的数据
在这里插入图片描述
在这里插入图片描述


(2)Buttons:按钮

在这里插入图片描述

①QPush Button:文字,clicked()
②QTool Button:图像,clicked()
③QRadio Buttion:单选 (同一容器中的多个Radio Buttion只能有一个被选中),toggled(bool)
④QCheck Box:复选,clicked(bool)

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


按钮的信号:
在这里插入图片描述

在这里插入图片描述


在centralwidget中加新的widget作为容器
在这里插入图片描述


(3)QComboBox:下拉框

#include <QComboBox>

QComboBox *comboBox = new QComboBox(&w);

在这里插入图片描述

加选项
在这里插入图片描述
在这里插入图片描述

信号
在这里插入图片描述

处理重载:QOverload<>
在这里插入图片描述

设置为可编辑
在这里插入图片描述

限制用户的输入:验证器
在这里插入图片描述
在这里插入图片描述

举例:Int

#include <QIntValidator>

在这里插入图片描述

但有漏洞
在这里插入图片描述

再加强,精确验证:validator->validate()
在这里插入图片描述


(4)QLineEdit:单行输入

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

分别对应推荐和搜索

在这里插入图片描述


(5)容器:containers

容器:一个QWidget,它的对象树中的孩子是其他ui元素

带边框的容器:QGroupBox
在这里插入图片描述


6.组合组件和界面布局

(1)布局

1.定义
布局:容器里面的元素的大小位置会随着容器大小的改变而自动改变


2.分类
水平布局、垂直布局、栅格布局
在这里插入图片描述

在这里插入图片描述


3.布局的使用
(1)先创建容器和里面的元素 【给容器、元素、布局 申请内存】
(2)创建布局:new QHBoxLayout / QVBoxLayout / QGridLayout
(3)把元素加入到布局当中:addWidget
(4)把布局设置为容器的属性:setLayout
在这里插入图片描述

栅格布局:addWidget有两种重载形式。可以占多行 (2,2,4,4)

在这里插入图片描述


4.弹簧:Spacer
在这里插入图片描述
在这里插入图片描述


7.model/view模型:MVC模型

模型-视图-控制器(MVC)是一种设计模式,在构建用户界面时经常使用。

MVC由三种对象组成:
①Mode l模型:应用对象,是一个抽象的数据结构,用户无法感知
②View 视图:屏幕表示,是用户看到的界面。
③Controller 控制器:定义了用户界面对用户输入的反应。用户编辑,直接修改了model

在MVC出现之前,用户界面设计往往倾向于将这些对象合并在一起。MVC将它们解耦以增加灵活性和重用性。并降低人思考的复杂度


(1)如何描述一个复杂的界面:分层

在这里插入图片描述

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

为MVC提出了23种设计模式。
数据变了,视图跟着变。就是观察者模式。


(2)Qt中的MVC:model/view architecture

Qt简化了mvc,控制器controller不是常驻的,而是用户交互时临时生成,称为委托delegate


①model

model是抽象的数据结构,如:①一维数组 ②二维表格 ③树

项item:二维表格的每一个格子称为项,每个项的孩子还是一个二维表格。这样的数据结构称为表格树
在这里插入图片描述


常见的三种模型:对表格树进行限制:①每一个item不准有孩子 ②item只有一列
(1)List Model (线性表):①②
(2)Table Model (二维数组,表格):①
(3)Tree Model (树):②
在这里插入图片描述


model的具体类型:
QAbstractItemModel (所有模型的抽象基类,表格树),加限制后退化为QAbstractItemModel、QAbstractTableModel
在这里插入图片描述
在这里插入图片描述


②view 视图

用户可以看见view。
用户操作view时,会临时生成一个controller,称为委托 Delegate。

在这里插入图片描述


QAbstractItemView
在这里插入图片描述


使用model和view

1.创建model view
2.根据model设置view
3.model进行增删改,view会自动跟着变


在这里插入图片描述

在这里插入图片描述


在这里插入图片描述


model变了,view自动跟着变

初始化

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

    //model
    model = new QStandardItemModel(2, 5, this);

    //view
    view = new QTableView(this);
    view->setModel(model);  //接下来,view的内容,由model决定

    btn = new QPushButton("add row", this);
    layout = new QHBoxLayout;
    layout->addWidget(view);
    layout->addWidget(btn);
    ui->centralwidget->setLayout(layout);

    QStringList headers = {"birth", "name", "age", "job", "image"};
    model->setHorizontalHeaderLabels(headers);

    //第一行
    QStandardItem *birth= new QStandardItem("1027");
    QStandardItem *name = new QStandardItem("Edward");
    QStandardItem *age = new QStandardItem("25");
    QStandardItem *job = new QStandardItem("Research & Development");
    QStandardItem *image = new QStandardItem(QIcon(":/new/prefix1/images/photo1.jpg"), "pthot1");
    model->setItem(0, 0, birth);
    model->setItem(0, 1, name);
    model->setItem(0, 2, age);
    model->setItem(0, 3, job);
    model->setItem(0, 4, image);

    //第二行
    birth = new QStandardItem("1202");
    name = new QStandardItem("Amber");
    age = new QStandardItem("25");
    job = new QStandardItem("Product Manager");
    image = new QStandardItem(QIcon(":/new/prefix1/images/photo2.jpg"), "photo2");
    model->setItem(1, 0, birth);
    model->setItem(1, 1, name);
    model->setItem(1, 2, age);
    model->setItem(1, 3, job);
    model->setItem(1, 4, image);

    QObject::connect(btn ,&QPushButton::clicked, this, &MainWindow::addRow);
}

点击按钮的槽函数

void MainWindow::addRow()
{
    QStandardItem *birth = new QStandardItem("0811");
    QStandardItem *name = new QStandardItem("Sam");
    QStandardItem *age = new QStandardItem("25");
    QStandardItem *job = new QStandardItem("Quality Assurance");
    QStandardItem *image = new QStandardItem(QIcon(":/new/prefix1/images/photo3.jpg"),"photo3");
    QList<QStandardItem *> list;
    list << birth << name << age << job << image;
    model->appendRow(list);
}

效果:
在这里插入图片描述



六、Qt的其他模块

1.QTimer 定时器

1.QTimer的函数接口 (F1查看帮助手册)

//Public Functions
QTimer(QObject *parent = nullptr); //构造函数

void setInterval(int msec);  //设置超时间隔

//Public Slots
void start();
void stop()

2.举例:一个定时器,每3秒打印一次时间。
在这里插入图片描述

#include <QTime>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    timer = new QTimer(this);
    timer->setInterval(3000);
    timer->start();
    QObject::connect(timer, &QTimer::timeout, this, &MainWindow::showTime);
    showTime(); //先手动调用一次槽函数,以在刚开始时就显示时间
}

void MainWindow::showTime()
{
    QString text = QString("current time is %1").arg(QTime::currentTime().toString());
    ui->textBrowser->setText(text);
}

2.Qt文件操作:文件IO

1.文本文件:人能看懂的,ASCII码序列,数据全为字符串,内存中用string类型
二进制文件:只要数据不是全为字符串,就是二进制文件


2.文件IO的操作:将数据从磁盘读取到内存
①open
②read
③write
④close


3.磁盘IO是非阻塞的,网络IO是阻塞的 (改为非阻塞要配合while循环)。


4.QByteArray
封装char数组,避免了QString进行编码转换,会改变存储的数据。
只要数据在两个设备之间交互,用QByteArray。【内存→磁盘,内存→网卡】


5.QFile
(1)继承关系:QObject -> QIODevice -> QFileDevice -> QFile
①QIODevice:open、read、write、close (非阻塞)
②QFile:二级功能
在这里插入图片描述


6.举例:
①读文件内容:TextBrowser
②编辑文本内容:TextEdit

void MainWindow::on_pushButton_clicked()
{
    QString filepath = QFileDialog::getOpenFileName(this, "open file");
    file = new QFile(filepath, this);
    file->open(QIODevice::ReadWrite);
    QByteArray array = file->readAll();
    ui->textBrowser->setText(QString(array));  //仅浏览
    ui->textEdit->setText(QString(array));     //可浏览,可编辑
}

3.Qt网络编程:网络IO

只介绍客户端。

1.注意:
①测试时要关代理。
②pro文件中,QT += network

QT       += core gui network

2.QTcpSocket
(1)头文件

#include <QTcpSocket>

QTcpSocket *socket;
socket = new QTcpSocket(this);

(2)继承关系
QObject -> QIODevice -> QAbstractSockt -> QTcpSocket
①由于QIODevice的open/close/read/write是非阻塞的。希望在可读的时候才读。Qt框架会在可读的时候发射信号,所以将read写在槽函数里。【分离了发射和接受,不需要考虑什么时候才能接收】
在这里插入图片描述

②QAbstractSockt的connectToHost()是非阻塞的
在这里插入图片描述
在这里插入图片描述


3.举例:自己写网络客户端
代码链接:https://github.com/WangEdward1027/Qt/tree/main/QTcpSocket

socket = new QTcpSocket(this);

//连接状态一改变,就打印一次
QObject::connect(socket, &QTcpSocket::stateChanged, this, &MainWindow::do_status_changed);
//连接完成,通知可以进行写操作
QObject::connect(socket, &QTcpSocket::connected, this, &MainWindow::do_connected_send);
//可读状态,调用读取的槽函数
QObject::connect(socket, &QTcpSocket::readyRead, this, &MainWindow::do_readyRead_show);
//on是框架生成的,do是自定义的
void MainWindow::on_pushButton_clicked()
{
    socket->connectToHost("www.baidu.com" ,80);
}

void MainWindow::do_status_changed(QAbstractSocket::SocketState socketState)
{
    qDebug() << "current state = " << socketState;
}

void MainWindow::do_connected_send()
{
    qDebug() << "send request";
    QByteArray request("GET / HTTP/1.1\r\n\r\n");
    socket->write(request);
}

void MainWindow::do_readyRead_show()
{
    qDebug() << "show response";
    QByteArray response = socket->readAll();
    content += QString(response);
    ui->textBrowser->setText(content);
    //ui->webEngineView->setHtml(content);
}

4.Qt多线程:QThread

#include <QThread>

代码链接:https://github.com/WangEdward1027/Qt/tree/main/QThread

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员爱德华

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

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

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

打赏作者

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

抵扣说明:

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

余额充值