当前位置: 首页 > 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;包括设计符合标准化的…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...