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

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

华为OD机试-最短木板长度-二分法(A卷,100分)

此题是一个最大化最小值的典型例题&#xff0c; 因为搜索范围是有界的&#xff0c;上界最大木板长度补充的全部木料长度&#xff0c;下界最小木板长度&#xff1b; 即left0,right10^6; 我们可以设置一个候选值x(mid)&#xff0c;将木板的长度全部都补充到x&#xff0c;如果成功…...