当前位置: 首页 > news >正文

【MFC】12.双缓冲序列化机制-笔记

双缓冲

双缓冲在之前写字符雨的时候,已经简单介绍过,今天我们来写一个简单的程序来体会双缓冲机制

我们实现一个在屏幕上画直线的功能:

在类中添加变量,保存起点坐标和终点坐标:

//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;CPoint m_begin;
CPoint m_end;

在对话框上添加WM_MOUSEMOVE,WM_LBUTTONDOWM,WM_LBUTTONUP消息处理函数:

void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//画直线pDC->MoveTo(m_begin);pDC->LineTo(m_end);CVIew::OnMouseMove(nFlags,point);
}
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){if(nFlags&MK_LBUTTON){m_end = point;IncalidateRect(NULL,TRUE);}CView::OnMouseMoce(nFlags,point);
}
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){//记录开始坐标CVIew::OnLButtonDown(nFlags,point);m_begin = point;
}
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;CVIew::OnLButtonUp(nFlags,point);ReleaseCapture();
}

这样写完之后,没有反应,这是因为没有无效区域,我们将OnLButtonUp函数中添加无效区域就可以了:

void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;CVIew::OnLButtonUp(nFlags,point);InvalidateRect(NULL,TRUE);
}

只有有了无效区域,绘图消息才会产生

然后我们完善:

//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;pDC->MoveTo(m_begin);pDC->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线pDC->MoveTo(Line.first);pDC->LineTo(line.second);}CVIew::OnMouseMove(nFlags,point);
}
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){if(nFlags&MK_LBUTTON){m_end = point;IncalidateRect(NULL,TRUE);}CView::OnMouseMoce(nFlags,point);
}
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){//记录开始坐标CVIew::OnLButtonDown(nFlags,point);m_begin = point;//捕捉客户区外鼠标消息SetCapture();
}
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;m_list.AddTail(Line(m_begin,m_end));CVIew::OnLButtonUp(nFlags,point);InvalidateRect(NULL,TRUE);ReleaseCapture();
}

这样写完了之后,确实可以画出来直线,但是这是我们直接操作外设的,所以会出现闪屏的情况

这时候就需要我们的双缓冲了

双缓冲就是我们操作内存,将直线画在内存上,然后将内存完整拷贝到外设上,这样就可以避免操作慢,闪屏的问题:

Win32中,能操作设备的,只有设备句柄hdc

而在MFC中封装了:

CDC------->对应Win32::GetDC

CMetaFileDC 矢量图,位图(了解一下就行了)

CPainDC:WM_PAINT::BEGINPAINT

CWindowDC:桌面窗口的HDC


这几种对象,能代表他们各自的东西,肯定是内部有绑定机制Attah()

双缓冲:内存DC,这里的CDC就代表了内存DC

然后我们修改代码:

void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//双缓冲绘图//1.创建内存DCCDC dcMem;dcMem.CreateCompatibleDC(pDC);CRect rect;GetClintRect(rect);//2.创建一张屏幕DC一样的位图CBitmap bitmap;bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//3.送到内存DC中dcMem.SeleObject(bitmap);dcMem.FillSolidRect(rect,RGB(255,255,255));//然后我们使用内存DC绘图dcMem->MoveTo(m_begin);dcMem->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.first);dcMem->LineTo(line.second);}//4.拷贝到设备pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);CVIew::OnMouseMove(nFlags,point);
}

修改完这个函数后,将绘制无效区域的函数,不再擦除背景

  • 画壁画刷位图

    在Win32中都是GDI句柄

    MFC封装了GDI对象

    Win32什么流程

    MFC就还是什么流程

    void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//双缓冲绘图//1.创建内存DCCDC dcMem;dcMem.CreateCompatibleDC(pDC);CRect rect;GetClintRect(rect);//2.创建一张屏幕DC一样的位图CBitmap bitmap;bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());CPen pen;pen.CreatePen(PS_DOT,55,RGB(0,255,255));//把画笔送到内存DCdeMem.SelectObject(pen);//3.送到内存DC中dcMem.SeleObject(bitmap);dcMem.FillSolidRect(rect,RGB(255,255,255));//然后我们使用内存DC绘图dcMem->MoveTo(m_begin);dcMem->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.first);dcMem->LineTo(line.second);}//4.拷贝到设备pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);CVIew::OnMouseMove(nFlags,point);
    }
    

序列化

  • 为什么要有序列化:

    我们在绘图应用程序上绘制的图形,可以保存起来,我们之后还可以打开

    而我们上面写的程序,是不能保存的,这就是序列化的基本功能

  • 简单使用以下序列化:

    1. 首先继承于CObject
    2. 类内添加声明宏DECLARE_SERTAL
    3. 类外实现宏IMPLEMENT_SERIAL
    //直线类
    class Cline:public Cobject{
    public:CLine(){};CLine(int x,int y,CString type):x(x),y(y),str(type){};virtual coid Setialize(CArchice* ar){if(ar,IsLoading()){//加载ar>>x>>y>>str;}else{//保存ar<<x<<y<<str;}}int x;int y;int color;CString str;DECLARE_SERTAL(CLine)
    }
    IMPLEMENT_SERIAL(CLine,Cobject,1)
    
    int main(){int nRetCode = 0;HMODULE hModule = ::GEtModuleHandle(nullptr);AfxWinInit(hModule,nullptr,::GetCommandLine(),0);CLine line(40,40,"直线");CFile file;file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeWrite);CArchive ar(&file,CArchice::store,4096);ar<<&line;ar.Close();file.Close();return nRetCode;
    }
    

我们这样实现后,发现,我们只写了三个成员,但是文件中有很多,我们来简单追踪有一下序列化是如何实现的:

序列化过程
CArchive 归档类对象

CArchive& AFXAPI operator>>(CArchive& ar, CLine* &pOb)
{pOb = (CLine*) ar.ReadObject(RUNTIME_CLASS(CLine)); return ar; 
}CArchive ar(&file, CArchive::store,4096)
{m_nMode = nMode;//模式m_pFile = pFile;//文件句柄m_nBufSize = nBufSize	//缓冲区大小this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}struct 
{类版本类大小x
}void CArchive::WriteObject(const CObject* line)
{//获取类信息地址CRuntimeClass* pClassRef = pOb->GetRuntimeClass();//写类信息WriteClass(pClassRef);((CObject*)pOb)->Serialize(ar对象/*归档类对象*/);{ar << x{return CArchive::operator<<((char)x); {if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) Flush();*(UNALIGNED LONG*)m_lpBufCur = x;m_lpBufCur += sizeof(LONG);return *this; }}AfxWriteStringLength(*this, 长度, 判断字符串是否是多字节{ar<<(BYTE)nLength;}:memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), lpBuf, nTemp)}
}ar,close
{Flush();{	m_pFile->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart){//写文件::WriteFile(m_hFile, lpBuf, nCount, &nWritten, NULL)m_lpBufCur = m_lpBufStart;}}
}

反序列化:

int main(){int nRetCode = 0;HMODULE hModule = ::GEtModuleHandle(nullptr);AfxWinInit(hModule,nullptr,::GetCommandLine(),0);CLine* line;CFile file;file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeRead);CArchive ar(&file,CArchice::store,4096);ar >> line;ar.Close();file.Close();std::cout<<line->str;std::coutd<<line-x;std::cout<<line->y;return nRetCode;
}
CArchive ar(&file, CArchive::store,4096)
{m_nMode = nMode;//模式 读m_pFile = pFile;//文件句柄m_nBufSize = nBufSize	//缓冲区大小this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}ar.ReadObject(直线类信息)
{//读类信息CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);{BYTE* lpTemp = m_lpBufStart + nPreviouslyFilled;do{//读文件 全部读上来nBytesRead = m_pFile->Read(lpTemp, nLeftToRead);lpTemp = lpTemp + nBytesRead;nTotalInBuffer += nBytesRead;nLeftToRead -= nBytesRead;}while (nBytesRead > 0 && nLeftToRead > 0 && nTotalInBuffer < nTotalSizeWanted);m_lpBufCur = m_lpBufStart;m_lpBufMax = m_lpBufStart + nTotalInBuffer;}//动态创建对象pOb = pClassRef->CreateObject();//回到我们代码pOb->Serialize(*this){CArchive::operator>>((LONG&)x);{if (m_lpBufCur + sizeof(LONG) > m_lpBufMax)FillBuffer(UINT(sizeof(LONG) - (m_lpBufMax - m_lpBufCur)));i = *(UNALIGNED LONG*)m_lpBufCur;m_lpBufCur += sizeof(LONG);return *this; }}
}

了解了序列化和反序列化过程,我们就可以将我们画的图保存起来了:

  1. 添加一个直线类
    #pragma once
    #include <afx.h>
    class Cline:public CObject{
    public:DECLARE_SERTAL(CLine)virtual void Serialize(CArchive& ar);CPoint m_begin;CPoint m_end;
    };//在cpp中实现:
    #include "pch.h"
    #include "CLine.h"IMPLEMENT_SERIAL(CLine,CObject,1)void Serialize(CArchive& ar){CObject::Serialize(ae);if(ar.IsLoading()){ar>>m_begin.x>>m_begin.y;ar>>m_end.x>>m_end.y;}else{ar<<m_begin.x<<m_begin.y;ar<<m_end.x<<m_end.y;}
    }我们还需要将直线的链表存储到文档中:
    在文档类.cpp中:
    #include "CLine.h"为文档类添加变量:
    CList<CLine>m_list;
    
  2. 我们将OnDraw消息稍作修改:就是直线的数据获取位置不一样了,我们需要修改:
  auto pos = pDoc->m_list.GetHeadPossition();while(pos){auto Lint = pDoc->m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.m_begin);dcMem->LineTo(line.m_end);}
  1. 这样修改之后,我们还需要将直线类的拷贝构造函数修改,因为默认是浅拷贝,我们在OnDraw函数中使用的时候,显示已经释放:
#pragma once
#include <afx.h>
class Cline:public CObject{
public:CLine(){};CLine& operator_(const& line){this->m_begin = line.m_begin;this->m_end = list.m_red;return *this;}CLine(const CLine& line){this->m_begin = line.m_begin;this->m_end = list.m_red;}CLine(CPoint begin,CPoint end){this->m_begin = begin;this->m_end = end;}DECLARE_SERTAL(CLine)virtual void Serialize(CArchive& ar);CPoint m_begin;CPoint m_end;
};
  1. 然后我们去文档类中处理:
void C...DOC::Serialize(CArchive& ar){if(ar.IsStoring()){ar<<m_list.getSize();auto pos = m_list.GetHeadPosition();while(pos){//ar<<&m_list.GetNext(pos);CLine*p = m_listl.GetNext(pos);ar<<&p;//这里第二种方式,看似跟上面的方式没有什么区别,但是运行发现,只保存了一条直线//这是因为每一次p都是一个地址}else{int nSize;ar>>nSize;CObject* shape;while(nSize--){ar>>shape;m_list.AddTail(*(CLine*)shape);}}}
}

双缓冲

双缓冲在之前写字符雨的时候,已经简单介绍过,今天我们来写一个简单的程序来体会双缓冲机制

我们实现一个在屏幕上画直线的功能:

在类中添加变量,保存起点坐标和终点坐标:

//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;CPoint m_begin;
CPoint m_end;

在对话框上添加WM_MOUSEMOVE,WM_LBUTTONDOWM,WM_LBUTTONUP消息处理函数:

void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//画直线pDC->MoveTo(m_begin);pDC->LineTo(m_end);CVIew::OnMouseMove(nFlags,point);
}
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){if(nFlags&MK_LBUTTON){m_end = point;IncalidateRect(NULL,TRUE);}CView::OnMouseMoce(nFlags,point);
}
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){//记录开始坐标CVIew::OnLButtonDown(nFlags,point);m_begin = point;
}
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;CVIew::OnLButtonUp(nFlags,point);ReleaseCapture();
}

这样写完之后,没有反应,这是因为没有无效区域,我们将OnLButtonUp函数中添加无效区域就可以了:

void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;CVIew::OnLButtonUp(nFlags,point);InvalidateRect(NULL,TRUE);
}

只有有了无效区域,绘图消息才会产生

然后我们完善:

//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;pDC->MoveTo(m_begin);pDC->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线pDC->MoveTo(Line.first);pDC->LineTo(line.second);}CVIew::OnMouseMove(nFlags,point);
}
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){if(nFlags&MK_LBUTTON){m_end = point;IncalidateRect(NULL,TRUE);}CView::OnMouseMoce(nFlags,point);
}
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){//记录开始坐标CVIew::OnLButtonDown(nFlags,point);m_begin = point;//捕捉客户区外鼠标消息SetCapture();
}
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){//记录终点坐标m_end = point;m_list.AddTail(Line(m_begin,m_end));CVIew::OnLButtonUp(nFlags,point);InvalidateRect(NULL,TRUE);ReleaseCapture();
}

这样写完了之后,确实可以画出来直线,但是这是我们直接操作外设的,所以会出现闪屏的情况

这时候就需要我们的双缓冲了

双缓冲就是我们操作内存,将直线画在内存上,然后将内存完整拷贝到外设上,这样就可以避免操作慢,闪屏的问题:

Win32中,能操作设备的,只有设备句柄hdc

而在MFC中封装了:

CDC------->对应Win32::GetDC

CMetaFileDC 矢量图,位图(了解一下就行了)

CPainDC:WM_PAINT::BEGINPAINT

CWindowDC:桌面窗口的HDC


这几种对象,能代表他们各自的东西,肯定是内部有绑定机制Attah()

双缓冲:内存DC,这里的CDC就代表了内存DC

然后我们修改代码:

void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//双缓冲绘图//1.创建内存DCCDC dcMem;dcMem.CreateCompatibleDC(pDC);CRect rect;GetClintRect(rect);//2.创建一张屏幕DC一样的位图CBitmap bitmap;bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//3.送到内存DC中dcMem.SeleObject(bitmap);dcMem.FillSolidRect(rect,RGB(255,255,255));//然后我们使用内存DC绘图dcMem->MoveTo(m_begin);dcMem->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.first);dcMem->LineTo(line.second);}//4.拷贝到设备pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);CVIew::OnMouseMove(nFlags,point);
}

修改完这个函数后,将绘制无效区域的函数,不再擦除背景

  • 画壁画刷位图

    在Win32中都是GDI句柄

    MFC封装了GDI对象

    Win32什么流程

    MFC就还是什么流程

    void C双缓冲View::OnDraw(UINT nFlags,CPoint point){C双缓冲View* pDoc = GetDocument();ASSERT_CALID(pDoc);if(!pFoc)return;//双缓冲绘图//1.创建内存DCCDC dcMem;dcMem.CreateCompatibleDC(pDC);CRect rect;GetClintRect(rect);//2.创建一张屏幕DC一样的位图CBitmap bitmap;bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());CPen pen;pen.CreatePen(PS_DOT,55,RGB(0,255,255));//把画笔送到内存DCdeMem.SelectObject(pen);//3.送到内存DC中dcMem.SeleObject(bitmap);dcMem.FillSolidRect(rect,RGB(255,255,255));//然后我们使用内存DC绘图dcMem->MoveTo(m_begin);dcMem->LineTo(m_end);auto pos = m_list.GetHeadPossition();while(pos){auto Lint = m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.first);dcMem->LineTo(line.second);}//4.拷贝到设备pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);CVIew::OnMouseMove(nFlags,point);
    }
    

序列化

  • 为什么要有序列化:

    我们在绘图应用程序上绘制的图形,可以保存起来,我们之后还可以打开

    而我们上面写的程序,是不能保存的,这就是序列化的基本功能

  • 简单使用以下序列化:

    1. 首先继承于CObject
    2. 类内添加声明宏DECLARE_SERTAL
    3. 类外实现宏IMPLEMENT_SERIAL

    ```cpp //直线类 class Cline:public Cobject{ public: CLine(){}; CLine(int x,int y,CString type):x(x),y(y),str(type){}; virtual coid Setialize(CArchice* ar){ if(ar,IsLoading()){ //加载 ar>>x>>y>>str; }else{ //保存 ar<

我们这样实现后,发现,我们只写了三个成员,但是文件中有很多,我们来简单追踪有一下序列化是如何实现的:

序列化过程
CArchive 归档类对象

CArchive& AFXAPI operator>>(CArchive& ar, CLine* &pOb)
{pOb = (CLine*) ar.ReadObject(RUNTIME_CLASS(CLine)); return ar; 
}CArchive ar(&file, CArchive::store,4096)
{m_nMode = nMode;//模式m_pFile = pFile;//文件句柄m_nBufSize = nBufSize	//缓冲区大小this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}struct 
{类版本类大小x
}void CArchive::WriteObject(const CObject* line)
{//获取类信息地址CRuntimeClass* pClassRef = pOb->GetRuntimeClass();//写类信息WriteClass(pClassRef);((CObject*)pOb)->Serialize(ar对象/*归档类对象*/);{ar << x{return CArchive::operator<<((char)x); {if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) Flush();*(UNALIGNED LONG*)m_lpBufCur = x;m_lpBufCur += sizeof(LONG);return *this; }}AfxWriteStringLength(*this, 长度, 判断字符串是否是多字节{ar<<(BYTE)nLength;}:memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), lpBuf, nTemp)}
}ar,close
{Flush();{	m_pFile->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart){//写文件::WriteFile(m_hFile, lpBuf, nCount, &nWritten, NULL)m_lpBufCur = m_lpBufStart;}}
}

反序列化:

int main(){int nRetCode = 0;HMODULE hModule = ::GEtModuleHandle(nullptr);AfxWinInit(hModule,nullptr,::GetCommandLine(),0);CLine* line;CFile file;file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeRead);CArchive ar(&file,CArchice::store,4096);ar >> line;ar.Close();file.Close();std::cout<<line->str;std::coutd<<line-x;std::cout<<line->y;return nRetCode;
}
CArchive ar(&file, CArchive::store,4096)
{m_nMode = nMode;//模式 读m_pFile = pFile;//文件句柄m_nBufSize = nBufSize	//缓冲区大小this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}ar.ReadObject(直线类信息)
{//读类信息CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);{BYTE* lpTemp = m_lpBufStart + nPreviouslyFilled;do{//读文件 全部读上来nBytesRead = m_pFile->Read(lpTemp, nLeftToRead);lpTemp = lpTemp + nBytesRead;nTotalInBuffer += nBytesRead;nLeftToRead -= nBytesRead;}while (nBytesRead > 0 && nLeftToRead > 0 && nTotalInBuffer < nTotalSizeWanted);m_lpBufCur = m_lpBufStart;m_lpBufMax = m_lpBufStart + nTotalInBuffer;}//动态创建对象pOb = pClassRef->CreateObject();//回到我们代码pOb->Serialize(*this){CArchive::operator>>((LONG&)x);{if (m_lpBufCur + sizeof(LONG) > m_lpBufMax)FillBuffer(UINT(sizeof(LONG) - (m_lpBufMax - m_lpBufCur)));i = *(UNALIGNED LONG*)m_lpBufCur;m_lpBufCur += sizeof(LONG);return *this; }}
}

了解了序列化和反序列化过程,我们就可以将我们画的图保存起来了:

  1. 添加一个直线类
    #pragma once
    #include <afx.h>
    class Cline:public CObject{
    public:DECLARE_SERTAL(CLine)virtual void Serialize(CArchive& ar);CPoint m_begin;CPoint m_end;
    };//在cpp中实现:
    #include "pch.h"
    #include "CLine.h"IMPLEMENT_SERIAL(CLine,CObject,1)void Serialize(CArchive& ar){CObject::Serialize(ae);if(ar.IsLoading()){ar>>m_begin.x>>m_begin.y;ar>>m_end.x>>m_end.y;}else{ar<<m_begin.x<<m_begin.y;ar<<m_end.x<<m_end.y;}
    }我们还需要将直线的链表存储到文档中:
    在文档类.cpp中:
    #include "CLine.h"为文档类添加变量:
    CList<CLine>m_list;
    
  2. 我们将OnDraw消息稍作修改:就是直线的数据获取位置不一样了,我们需要修改:
  auto pos = pDoc->m_list.GetHeadPossition();while(pos){auto Lint = pDoc->m_list.GetNext(pos);//画直线dcMem->MoveTo(Line.m_begin);dcMem->LineTo(line.m_end);}
  1. 这样修改之后,我们还需要将直线类的拷贝构造函数修改,因为默认是浅拷贝,我们在OnDraw函数中使用的时候,显示已经释放:
#pragma once
#include <afx.h>
class Cline:public CObject{
public:CLine(){};CLine& operator_(const& line){this->m_begin = line.m_begin;this->m_end = list.m_red;return *this;}CLine(const CLine& line){this->m_begin = line.m_begin;this->m_end = list.m_red;}CLine(CPoint begin,CPoint end){this->m_begin = begin;this->m_end = end;}DECLARE_SERTAL(CLine)virtual void Serialize(CArchive& ar);CPoint m_begin;CPoint m_end;
};
  1. 然后我们去文档类中处理:
void C...DOC::Serialize(CArchive& ar){if(ar.IsStoring()){ar<<m_list.getSize();auto pos = m_list.GetHeadPosition();while(pos){//ar<<&m_list.GetNext(pos);CLine*p = m_listl.GetNext(pos);ar<<&p;//这里第二种方式,看似跟上面的方式没有什么区别,但是运行发现,只保存了一条直线//这是因为每一次p都是一个地址}else{int nSize;ar>>nSize;CObject* shape;while(nSize--){ar>>shape;m_list.AddTail(*(CLine*)shape);}}}
}

相关文章:

【MFC】12.双缓冲序列化机制-笔记

双缓冲 双缓冲在之前写字符雨的时候&#xff0c;已经简单介绍过&#xff0c;今天我们来写一个简单的程序来体会双缓冲机制 我们实现一个在屏幕上画直线的功能&#xff1a; 在类中添加变量&#xff0c;保存起点坐标和终点坐标&#xff1a; //定义一个容器&#xff0c;保存每…...

Linux 终端会话中,启动任务并放到后台运行

一、需求 linux要执行一个脚本&#xff0c;耗时很长&#xff0c;想要脚本在后台运行&#xff0c;用户注销或终端软件关闭时也可以继续运行。 二、实现 1、nohup命令 脚本在后台运行 nohup 是在 Linux 和类 Unix 系统中使用的一个命令&#xff0c;用于在后台运行程序&#x…...

软考笔记——10.项目管理

进度管理 进度管理就是采用科学的方法&#xff0c;确定进度目标&#xff0c;编制进度计划和资源供应计划&#xff0c;进行进度控制&#xff0c;在与质量、成本目标协调的基础上&#xff0c;实现工期目标。 具体来说&#xff0c;包括以下过程&#xff1a; (1) 活动定义&#…...

算法与数据结构(二十四)最优子结构原理和 dp 数组遍历方向

注&#xff1a;此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 本文是两年前发的 动态规划答疑篇open in new window 的修订版&#xff0c;根据我的不断学习总结以及读者的评论反馈&#xff0c;我给扩展了更多…...

Java Vue Uniapp MES生产执行管理系统

本MES系统是一款B/S结构、通用的生产执行管理系统&#xff0c;功能强大&#xff01; 系统基于多年离散智造行业的业务经验组建&#xff0c;主要目的是为国内离散制造业的中小企业提供一个专业化、通用性、低成本的MES系统解决方案。 联系作者获取...

深入探究Socks5代理与IP代理在网络安全与爬虫中的应用

1. Socks5代理&#xff1a;打开网络隧道的多功能工具 Socks5代理是一种流行的代理协议&#xff0c;它在传输层为数据包提供了隧道。相较于之前的版本&#xff0c;Socks5不仅支持TCP连接&#xff0c;还可以处理UDP流量&#xff0c;使其在需要实时数据传输的应用中表现出色。在网…...

Vue使用jspdf和html2canvas组件库结合导出PDF文件

效果图&#xff1a; 1、安装依赖&#xff1a; npm install html2canvas --save npm install jspdf --save 或 yarn add html2canvas --save yarn add jspdf --save 2、封装全局调用方法&#xff1a;this.$exportPDF(#id,文件名) 新建js文件&#xff1a;/utils/html2Pdf.js&am…...

7. 实现 API 自动生成

目录 1. pom.xml中引用依赖 2. 引入相关的依赖 3. 编写配置类 4. application.yml 中添加配置 5. API 常用注解 6. 访问 API 列表 7. API 导入 Postman 使用 Springfox Swagger生成 API&#xff0c;并导入 Postman&#xff0c;完成API单元测试。 Swagger 简介&#xff1a;Swag…...

使用Druid解析SQL,获取SQL中所有使用的表

一、sqlParse组成 Druid SQL Parser分三个模块&#xff1a; - Parser - AST - Visitor 1.1 Parser parser是将输入文本转换为ast&#xff08;抽象语法树&#xff09;&#xff0c;parser有包括两个部分&#xff0c;Parser和Lexer&#xff0c;其中Lexer实现词法分析&#x…...

公司内部测试团队可以替代专业的软件检测机构吗,性能测试怎么收费?

第三方软件测试 尽管软件测试是伴随着软件开发的发展而产生的&#xff0c;但是在信息技术日新月异的今天&#xff0c;软件测试逐渐走出开发附庸的定位。 一方面&#xff0c;很多大型企业都在内部设置了专门的测试团队以承接软件系统的测试工作&#xff0c;为产品质量把关。另…...

Three.js之相机、渲染器、光源、动画、性能监测

参考资料 第一个3D案例—透视投影相机第一个3D案例—渲染器…Canvas画布布局和全屏 知识点 透视投影相机PerspectiveCameraWebGL渲染器WebGLRenderer辅助观察坐标系AxesHelper漫反射网格材质MeshLambertMaterial点光源PointLight点光源辅助观察PointLightHelper环境光Ambien…...

Seaborn图表使用指南!

目录 介绍线图散点图直方图概率密度函数 &#xff08;PDF&#xff09;箱线图小提琴剧情配对图热图关节图地毯图 一、介绍 数据科学已成为一个突出的领域&#xff0c;近年来呈爆炸性增长。对精通从数据中获取见解并应用这些见解来解决现实世界问题的数据科学家的需求从未增加。…...

[C++ 网络协议编程] TCP/IP协议

目录 1. TCP/IP协议栈 2. TCP原理 2.1 TCP套接字中的I/O缓冲 2.2 TCP工作原理 2.2.1 三次握手&#xff08;连接&#xff09; 2.2.2 与对方主机的数据交换 2.2.3 四次握手&#xff08;断开与套接字的连接&#xff09; TCP&#xff08;Transmission Control Protocol传输控…...

Unity用NPOI创建Exect表,保存数据,和修改删除数据。以及打包后的坑——无法打开新创建的Exect表

先说坑花了一下午才找到解决方法解决&#xff0c; 在Unity编辑模式下点击物体创建对应的表&#xff0c;获取物体名字与在InputText填写的注释数据。然后保存。创建Exect表可以打开&#xff0c;打包PC后&#xff0c;点击物体创建的表&#xff0c;打不开文件破损 解决方法&#…...

记一次fegin调用的媒体类型问题

1.问题&#xff1a;分页查询&#xff0c;分页参数传递不生效 2.开发环境&#xff1a;fegin接口 开发环境&#xff1a;调用接口 3.修改后&#xff1a;fegin接口不变 调用接口 前端媒体类型&#xff1a; 问题解决&#xff01;&#xff01;&#xff01; 4.原因分析&…...

在Hive/Spark上运行执行TPC-DS基准测试 (ORC和TEXT格式)

目前,在Hive/Spark上运行TPC-DS Benchmark主要是通过早期由Hortonworks维护的一个项目:hive-testbench 来完成的。本文我们以该项目为基础介绍一下具体的操作步骤。不过,该项目仅支持生成ORC和TEXT格式的数据,如果需要Parquet格式,请参考此文《在Hive/Spark上执行TPC-DS基…...

如何仿写简易tomcat 实现思路+代码详细讲解

仿写之前&#xff0c;我们要搞清楚都要用到哪些技术 自定义注解&#xff0c;比如Tomcat使用的是Servlet&#xff0c;我们可以定义一个自己的MyServlet构造请求体和返回体&#xff0c;比如tomcat使用HttpRequest&#xff0c;我们可以自己定义myHttpRequestjava去遍历一个指定目…...

如何提高深度学习性能

可用于 对抗过度拟合并获得更好泛化能力的20 个提示、技巧和技术 如何从深度学习模型中获得更好的性能? 这是我最常被问到的问题之一。 可能会被问为: 如何提高准确率? ……或者可以反过来说: 如果我的神经网络表现不佳该怎么办? 我经常回答说:“我不太清楚,但我有很…...

ECMAScript版本对比:从ES1到ES2021

引言 ECMAScript&#xff08;简称ES&#xff09;是一种用于编写Web前端JavaScript的标准化语言。自1997年发布第一版&#xff08;ES1&#xff09;以来&#xff0c;ECMAScript已经经历了多个版本的更新和演进。每个版本都引入了新的语法和功能&#xff0c;为开发人员提供了更强…...

设计HTML5表格

在网页设计中&#xff0c;表格主要用于显示包含行、列结构的二维数据&#xff0c;如财务表格、调查数据、日历表、时刻表、节目表等。在大多数情况下&#xff0c;这类信息都由列标题或行标题及数据构成。本章将详细介绍表格在网页设计中的应用&#xff0c;包括设计符合标准化的…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...