梧州网站建设有哪些网上做调查赚钱的网站
文章目录
- 四、Qt的元对象系统
 - 1.元对象和MOC
 - (1)自省 和 反射
 - (2)Qt是怎样支持元对象系统的?
 - (3)支持元对象系统的三个要求
 - (4)元对象系统的功能
 - (5)动态属性
 
- 2.信号和槽机制
 - (1)信号与槽机制的基本原理
 - (2)自定义信号、自定义槽函数
 - ①自定义信号
 - ②自定义槽
 - ③关联 connect
 
- (3)信号与槽的特点
 - ①槽函数的调用是同步的
 - 让信号携带参数
 - 处理重载的函数:qOverload
 - 如果重复connect
 - 交换信号和槽的地位
 
- (4)减少重复槽函数
 - (5)信号与槽的优缺点:信号槽 vs 回调
 
- 3.对象树
 - (1)基础复习:四种指针
 - (2)依赖注入
 - (3)组合模式
 - (4)Qt中的内存申请
 
- 4.事件系统
 - (1)事件和事件循环
 - (2)事件到达,处理事件的三个步骤
 - ①事件过滤器
 - ②event函数
 - ③事件处理函数 event handler
 - 同时使用event和event handler
 - 人造按钮
 - 同时使用event filter、event、event handler
 
- (3)多个对象之间的事件传递
 - 事件传递的过程
 - 事件系统 + 信号槽,让两个组件互动
 
- 五、Qt_Widgets模块
 - 1.QWidget类的概念
 - paintEvent
 - 小项目3:打蚊子
 
- 2.窗口
 - (1)定义
 - (2)成为窗口的条件
 - (3)QWidget当中和窗口相关的属性和方法
 
- 3.对话框 QDialog
 - (1)模态
 - (2)自定义方式
 - (3)子类:标准对话框
 - ①QColorDialog:颜色选择对话框
 - ②QFileDialog:文件选择对话框
 - ③QFontDialog:字体选择对话框
 - ④QMessageBox:信息框
 
- 4.主窗口 QMainWindow
 - (1)菜单栏:QMenubar
 - (2)工具栏:QToolbar
 - (3)状态栏:QStatusBar
 
- 5.常见的简单组件
 - (1)用来显示的组件:Display Widgets
 - Label
 
- (2)Buttons:按钮
 - (3)QComboBox:下拉框
 - (4)QLineEdit:单行输入
 - (5)容器:containers
 
- 6.组合组件和界面布局
 - (1)布局
 
- 7.model/view模型:MVC模型
 - (1)如何描述一个复杂的界面:分层
 - (2)Qt中的MVC:model/view architecture
 - ①model
 - ②view 视图
 - 使用model和view
 
- 六、Qt的其他模块
 - 1.QTimer 定时器
 - 2.Qt文件操作:文件IO
 - 3.Qt网络编程:网络IO
 - 4.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)以前,A构造时,在初始化列表中构造B。耦合性太高。两者
 (2)现在,用setB中才构造B。后面才注入依赖关系。(避免一个构造函数失败导致整体构造失败)

(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:信息框

 

 
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);//modelmodel = new QStandardItemModel(2, 5, this);//viewview = 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多线程
#include <QThread>
