QQ登录

只需一步,快速开始

上位机MFC增强版画图板源代码下载

[ 复制链接 ]

上位机MFC增强版画图板源代码下载

上位机MFC增强版画图板源代码下载

例程运行界面如上图。
可以任意的绘制图形,
图形绘制后,也可以通过鼠标拖动更改图形的外观属性。
有很好的参考价值,
可以下载源代码参考学习。
下载地址:
请点击此处下载

请先注册会员后在进行下载

已注册会员,请先登录后下载

文件名称:上位机MFC增强版画图板源代码下载.rar 
文件大小:344.67 KB  售价:1金币
下载权限: 不限 以上或 VIP会员   [购买捐助会员]   [充值积分]   有问题联系我


  

上位机VC MFC程序开发精典实例大全源码与视频讲解配套下载408例

  

经历1年的编程与录制点击进入查看

  

halcon从自学到接项目视频教程,另外再赠送全网最全资源  

  

欢迎围观我录制的一套halcon自学视频教程(进入)



以下为作者对例程的使用介绍
下面是其更新日志:
============================================================
v3.0
自画直线,矩形,园
============================================================
v2.8-2.9
完成所有功能,包括全选,拖动等,参见文档HDraw2.9.doc
============================================================
v2.7
修正画布随滚动条滚动的Bug
完善MouseMove的直线,矩形和椭圆识别
============================================================
v2.5
增强版
增加相应菜单
把画图的Toolbar单独分离出来
在画布上对单张图片操作
============================================================
v2.4
完结版
画布,ScrollView, CRectTracker,调色板,保存成位图
加入中间层View的步骤
1.创建MFC类CHDrawPView:CScrollView
2.修改CHDrawApp的InitInstance方法,将CHDrawView改为CHDrawPView,并修改include
加入CRectTracker的步骤
1.增加成员变量CRectTracker m_tracker
2.在CHDrawPView的构造函数中设置Tracker的大小
3.在CHDrawPView的OnDraw中调用Tracker的Draw函数
4.override CHDrawPView的SetCursor方法 GetCursorPos->ScreenToClient->SetCursor
5.override CHDrawPView的LButtonDown方法 HitTest->Track
将CHDrawView加入CHDrawPView的
使用自定义消息防止ActiveView从画布跑到背景View
============================================================
v2.3
增加Bitmap背景功能
============================================================
v2.2
完成颜色和线宽功能
============================================================
v2.1
增加文本图形类型,增加删除功能,增加打开保存功能
============================================================
v2.0
完成所有基本功能
下面是介绍文档:
1.        概述
1.1.        简介
使用VC开发平台,MFC框架实现一个画图程序,尽可能多的实现Windows自带的画图功能,并扩展其功能。
1.2.        功能需求
1.2.1.        基本绘图功能:(必须全部实现)
(1)        能够用鼠标操控方式,绘制直线、矩形、椭圆。
(2)        在绘图时,选择绘制某种图像后(如直线),在画布中按住鼠标左键后移动鼠标,在画布中实时的根据鼠标的移动显示相应的图形。在松开鼠标左键后,一次绘图操作完成。
(3)        能够在绘制一图形(如一条直线)前设置线的粗细、颜色。(以菜单方式)
(4)        可以以矢量图方式保存绘制的图形。
(5)        可以读取保存的矢量图形文件,并显示绘图的结果。

界面友好的要求:
(6)        有画直线、矩形、椭圆的工具箱。
(7)        有颜色选择工具箱。
(8)        对于当前选中的绘图工具,以“下沉”的形式显示。
(9)        在状态栏中显示鼠标的位置。
(10)        在鼠标移向一工具不动时,有工具的功能提示。
(11)        在菜单上有当前选中的菜单项标识(即前面有小钩)
(12)        可以用鼠标操作方式,通过“拖拽”方式,改变画布的大小。
(13)        在画布大而外框小时,应有水平或垂直方向的滚动条。
1.2.2.        高级编辑功能:
(1)        具有Undo功能。
(2)        可以用鼠标选中绘制的某一图形。被选中的图形符号有标识(参见Word,如一直线段,其两端点上加了两个小框;矩形上有8个小框点)。
(3)        当鼠标靠近某一目标时,鼠标的形状发生改变
(4)        修改被选中的图形。通过鼠标的“拖拽”,可以改变图形的位置、或大小。
(5)        修改被选中图形的颜色、笔划的粗细。
(6)        删除被选中的图形。
(7)        可以使用鼠标“拖拽”一个虚矩形框,一次选择多个图形。
(8)        可以使用 Ctrl 或Shift加鼠标左键选择多个图形对象。
1.2.3.        附加功能:
(1)        可选择打开或关闭工具栏。
(2)        应用程序的标题栏上有程序的图标。
(3)        将图形转换成位图文件的形式保存。
(4)        在选择一个图形元素后(如直线),会有进一步选择线型或线宽的界面。
(5)        仿Word,选择“线型”、“粗细”图标后,会出现进一步选择的选项卡。
1.3.        未实现的功能

2.        主要功能描述

上位机MFC增强版画图板源代码下载

上位机MFC增强版画图板源代码下载

右键修改选中图形的颜色,粗细,线型,删除选中图形
右键和鼠标调整图形大小
对话框矢量修改所有图形
3.        技术细节
3.1.        代码结构
3.1.1.        代码文件
MFC自动生成的文件
1个CHDrawPView
1个HStroke
2个Dialog(HStrokeEditDlg+HStrokeTextDlg)
1个ToolBar(HColorBar)
3.1.2.        代码类
HDrawPView文件只有一个类:CHDrawPView,该类集成自MFC的CScrollView,主要实现维护画布类CHDrawView和滚动功能
HStroke文件里包含目前所有的图形类信息,包括集成与MFC的CObject类的基类HStroke,以及集成自HStroke的具体图形类HStrokeLine(直线),HStrokeRect(矩形),HStrokeEllipse(椭圆),HStrokeText(文本),HStrokePoly(曲线)。
HStrokeEditDlg文件只有一个类:HStrokeEditDlg,该类集成自MFC的CDialog类,主要用来编辑已有图形类

HStrokeTextDlg文件只有一个类:HStrokeTextDlg,该类集成自MFC的CDialog类,主要用来画文本时输入文本信息
HColorBar类只有一个类:HColorBar类,该类集成自MFC的CToolBar类,呈现一个颜色框,方便用户在绘图时选择不同的颜色。
3.2.        SetROP2实现重绘
在画图状态下,鼠标移动时既要擦除旧图形,又要绘制新图形。这里主要有两种实现方法:一是全部重绘,二是先擦除旧图形。
如果使用矢量图全部重绘,频繁的绘图动作消耗很大,很容易造成屏幕闪动。但是如果将已有图形保存为位图,然后重绘的时候只要绘制位图即可,这样能避免闪动。
第二种方法要考虑的就是擦除旧图形的问题,本程序使用SetROP2函数设置MASK的方式,每次绘图时采用非异或运算的方式擦除旧图形:
        pDC->SetROP2(R2_NOTXORPEN);        //设置ROP2
        DrawStroke(pDC);                        //画图擦除旧线(自定义函数)
        SetCurrentPoint(point);                //设置新点的坐标(自定义函数)
        DrawStroke(pDC);                        //画新线(自定义函数)
3.3.        嵌套View实现画布

        m_drawView = new CHDrawView();//创建画布View
        if (!m_drawView->CreateEx(WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR,
                AfxRegisterWndClass(CS_VREDRAW | CS_HREDRAW,LoadCursor(NULL,IDC_CROSS),
                        (HBRUSH)GetStockObject(WHITE_BRUSH),NULL),///白色画布
                "",WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                m_tracker.m_rect.left,m_tracker.m_rect.top,
                m_tracker.m_rect.right-1,m_tracker.m_rect.bottom-1,
                this->m_hWnd,NULL)){
                TRACE0("Failed to create toolbar\n");
                return -1;      // fail to create
        }
        m_drawView->SetDocument((CHDrawDoc*)m_pDocument);//传递CDocument给新View
        m_drawView->ShowWindow(SW_NORMAL);
        m_drawView->UpdateWindow();
        //设置背景View颜色为灰色
        SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(long)GetStockObject(GRAY_BRUSH));
3.4.        鼠标靠近目标时突出显示
在鼠标移动的时候,OnMouseMove函数会遍历已有图形,判断鼠标所在点是否属于已有图形范围,如果是,则高亮显示该图形。
高亮显示的方法比较简单,只要增加CRectTracker即可,而判断当前点是否属于某图形比较有意思:
3.4.1.        判断一点是否属于矩形HStrokeRect
使用用MFC的CRect类的IsPointIn方法,当鼠标在矩形边框附近时,认为该点属于HStrokeRect。如图,实线矩形表示HStrokeRect。外矩形为外面的虚线矩形,内矩形为里面的虚线矩形:
BOOL HStrokeRect::IsPointIn(const CPoint &point){
        //矩形左上角x坐标
        int x1 = m_points.GetAt(0).x < m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x;
        //矩形左上角y坐标
        int y1 = m_points.GetAt(0).y < m_points.GetAt(1).y ? m_points.GetAt(0).y : m_points.GetAt(1).y;
        //矩形右下角x坐标
        int x2 = m_points.GetAt(0).x > m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x;
        //矩形右下角y坐标
        int y2 = m_points.GetAt(0).y > m_points.GetAt(1).y ? m_points.GetAt(0).y : m_points.GetAt(1).y;
        //构建外矩行和内矩形
        CRect rect(x1,y1,x2,y2), rect2(x1+5,y1+5,x2-5,y2-5);
        //如果在外矩形内并在内矩形外
        if(rect.PtInRect(point) && !rect2.PtInRect(point))
                return TRUE;
        else
                return FALSE;
}
3.4.2.        判断一点是否属于线段
首先判断一点是否属于这条线段所属的直线,根据直线的判定公式y1/x1 = y2/x2得到x1*y2-x2*y1=0,但是在画图中应该在直线附近就能选中,所以在本程序中:|x1*y2-x2*y1| < 偏差,然后判断该点是否属于这条线段。
        //计算该点到线段HStrokeLine的两个顶点的线段(x1,y1), (x2,y2)
        int x1 = point.x - m_points.GetAt(0).x;
        int x2 = point.x - m_points.GetAt(1).x;
        int y1 = point.y - m_points.GetAt(0).y;
        int y2 = point.y - m_points.GetAt(1).y;
        //计算判断量x1*y2 - x2*y1
        int measure = x1*y2 - x2*y1;
        //误差允许范围,也就是直线的“附近”
        int rule = abs(m_points.GetAt(1).x - m_points.GetAt(0).x)
                +abs(m_points.GetAt(0).y - m_points.GetAt(1).y);
        rule *= m_penWidth;//将线宽考虑进去
        //属于直线
        if(measure < rule && measure > -rule){
                //判断该点是否属于这条线段
                if(x1 * x2 < 0)
                        return TRUE;;
        }
        return FALSE;
3.4.3.        判断一点是否属于椭圆
根据椭圆的定义椭圆上的点到椭圆的两个焦点的距离之和为2a,首先计算出椭圆的a, b, c,然后计算出椭圆的两个焦点。
针对某个点,首先根据点坐标和两个焦点的坐标计算出该点到椭圆焦点的距离,然后减去2a,如果在“附近”,则认为其属于HStrokeEllipse,否则不属于。
        //计算椭圆的a, b, c
        int _2a = abs(m_points.GetAt(0).x - m_points.GetAt(1).x);
        int _2b = abs(m_points.GetAt(0).y - m_points.GetAt(1).y);
        double c = sqrt(abs(_2a*_2a - _2b*_2b))/2;
        //计算椭圆的焦点
        double x1,y1,x2,y2;
        if(_2a > _2b){//横椭圆
                x1 = (double)(m_points.GetAt(0).x + m_points.GetAt(1).x)/2 - c;
                x2 = x1 + 2*c;
                y1 = y2 = (m_points.GetAt(0).y + m_points.GetAt(1).y)/2;
        }
        else{//纵椭圆
                _2a = _2b;
                x1 = x2 = (m_points.GetAt(0).x + m_points.GetAt(1).x)/2;
                y1 = (m_points.GetAt(0).y + m_points.GetAt(1).y)/2 - c;
                y2 = y1 + 2*c;
        }
        //点到两个焦点的距离之和,再减去2a
        //distance(point - p1) + distance(point - p2) = 2*a;
        double measure = sqrt((x1 - point.x)*(x1-point.x) + (y1 - point.y)*(y1-point.y) )
                + sqrt( (point.x - x2)*(point.x - x2) + (point.y - y2)*(point.y - y2))
                - _2a;
        //计算椭圆的“附近”
        double rule = 4*m_penWidth;
        if(measure <  rule && measure > -rule)
                return TRUE;
        else
                return FALSE;
3.5.        文档序列化
MFC提供了良好的序列化机制,只要在类定义时加入DECLARE_SERIAL宏,在类构造函数的实现前加入IMPLEMENT_SERIAL宏,然后实现Serialize方法即可。本程序即使用该方法序列化:
首先在CHDrawDoc类实现Serialize方法,保存画布大小和所有图形信息:
void CHDrawDoc::Serialize(CArchive& ar)
{
        if (ar.IsStoring())
        {
                //保存时,首先保存画布高和宽,然后序列化所有图形
                ar<<m_cavasH<<m_cavasW;
                m_strokeList.Serialize(ar);
        }
        else
        {
                //打开时,首先打开画布高和宽,然后打开所有图形
                ar>>m_cavasH>>m_cavasW;
                m_strokeList.Serialize(ar);
        }
}
m_strokeList.Serialize(ar);这一句很神奇,Debug追踪的时候会发现,容器类会自动序列化容器内的元素数量,并调用每个元素的序列化方法序列化,所以还需要对每个图形元素实现序列化,以HStrokeLine为例:
在HStrokeLine的类声明中:
class HStrokeLine : public HStroke  
{
public:
        HStrokeLine();
        DECLARE_SERIAL(HStrokeLine)
然后在HStrokeLine的构造函数实现前:
IMPLEMENT_SERIAL(HStrokeLine, CObject, 1)
HStrokeLine::HStrokeLine()
{
        m_picType = PIC_line;
}
最后实现HStrokeLine的序列化函数,因为这里HStrokeLine集成自HStroke类而且没有特殊的属性,而HStroke类实现了Serialize函数,所以HStrokeLine类不需要实现Serilize方法,看一下HStroke的Serialize方法即可:
void HStroke::Serialize(CArchive& ar)
{
        if(ar.IsStoring()){
                int enumIndex = m_picType;
                ar<<enumIndex<<m_penWidth<<m_penColor;
                m_points.Serialize(ar);
        }
        else{
                int enumIndex;
                ar>>enumIndex>>m_penWidth>>m_penColor;
                m_picType = (enum HPicType)enumIndex;
                m_points.Serialize(ar);
        }
}
3.6.        打开保存导出
文档序列化实现以后,程序的打开和保存功能就已经完成了。但是从序列化方法可以看出,打开和保存的都是矢量图形,所以这里实现了一个导出为BMP图像的方法,导出:
        //保存文件对话框,选择导出路径
        CFileDialog dlg(FALSE, "bmp","hjz.bmp");
        if(dlg.DoModal() != IDOK){
                return ;
        }
        CString filePath = dlg.GetPathName();
        //
        CClientDC client(this);//用于本控件的,楼主可以不用此句
        CDC cdc;
        CBitmap bitmap;
        RECT rect;CRect r;
        GetClientRect(&rect);
        int cx = rect.right - rect.left;
        int cy = rect.bottom - rect.top;
        bitmap.CreateCompatibleBitmap(&client, cx, cy);
        cdc.CreateCompatibleDC(NULL);
        //获取BMP对象
        CBitmap * oldbitmap = (CBitmap* ) cdc.SelectObject(&bitmap);
        //白色画布
        cdc.FillRect(&rect, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
        //画图
        for(int i = 0; i < GetDocument()->m_strokeList.GetSize(); i ++){
                GetDocument()->m_strokeList.GetAt(i)->DrawStroke(&cdc);
        }
        cdc.SelectObject(oldbitmap);
        ::OpenClipboard(this->m_hWnd);
        ::EmptyClipboard();
        ::SetClipboardData(CF_BITMAP, bitmap);
        ::CloseClipboard();
        
        
        HBITMAP hBitmap = (HBITMAP)bitmap;
        HDC hDC;
        int iBits;
        WORD wBitCount;
        DWORD dwPaletteSize=0, dwBmBitsSize=0, dwDIBSize=0, dwWritten=0;
        BITMAP Bitmap;
        BITMAPFILEHEADER bmfHdr;
        BITMAPINFOHEADER bi;
        LPBITMAPINFOHEADER lpbi;
        HANDLE fh, hDib, hPal,hOldPal=NULL;
        
        hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
        iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
        DeleteDC(hDC);
        if (iBits <= 1)  wBitCount = 1;
        else if (iBits <= 4)  wBitCount = 4;
        else if (iBits <= 8)  wBitCount = 8;
        else      wBitCount = 24;
        
        GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap);
        bi.biSize   = sizeof(BITMAPINFOHEADER);
        bi.biWidth   = Bitmap.bmWidth;
        bi.biHeight   = Bitmap.bmHeight;
        bi.biPlanes   = 1;
        bi.biBitCount  = wBitCount;
        bi.biCompression = BI_RGB;
        bi.biSizeImage  = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrImportant = 0;
        bi.biClrUsed  = 0;
        
        dwBmBitsSize = ((Bitmap.bmWidth * wBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
        
        hDib = GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
        lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
        *lpbi = bi;
        
        hPal = GetStockObject(DEFAULT_PALETTE);
        if (hPal)
        {
                hDC = ::GetDC(NULL);
                hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE);
                RealizePalette(hDC);
        }
        
        GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)
                +dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);
        
        if (hOldPal)
        {
                ::SelectPalette(hDC, (HPALETTE)hOldPal, TRUE);
                RealizePalette(hDC);
                ::ReleaseDC(NULL, hDC);
        }
        
        fh = CreateFile(filePath, GENERIC_WRITE,0, NULL, CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
        
        if (fh == INVALID_HANDLE_VALUE)  
                return                 ;
        
        bmfHdr.bfType = 0x4D42; // "BM"
        dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
        bmfHdr.bfSize = dwDIBSize;
        bmfHdr.bfReserved1 = 0;
        bmfHdr.bfReserved2 = 0;
        bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
        
        WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
        WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
        
        GlobalUnlock(hDib);
        GlobalFree(hDib);
        CloseHandle(fh);
3.7.        友好用户界面
菜单项选中和工具栏图标下沉。该功能的实现非常简单,而且用户体验很好,以当前所画的图形为例:
第一步:增加3个菜单项
名称        ID
直线        ID_DRAW_LINE
椭圆        ID_DRAW_ELLIPSE
矩形        ID_DRAW_RECT
第二步:在工具栏上增加3个工具栏项,注意ID要和上面的三个ID相同。
第三步:在CHDrawDoc类的ClassWizard中增加消息响应函数,分别为以上三个ID增加COMMAND和UPDATE_COMMAND_UI的Handler,COMMAND的Handler就是针对按下工具栏按钮或菜单项的响应函数,而UPDATE_COMMAND_UI则是显示菜单栏时执行的操作,有点类似OnDraw。
以直线为例,ID_DRAW_LINE的COMMAND的Handler为OnDrawLine
void CHDrawDoc::OnDrawLine()
{
        //设置当前画图的图形类型为直线
        m_picType = PIC_line;
}
ID_DRAW_LINE的UPDATE_COMMAND_UI的Handler为OnUpdateDrawLine:
void CHDrawDoc::OnUpdateDrawLine(CCmdUI* pCmdUI)
{
        //如果当前画图类型为直线,设置菜单项前加对号,工具栏项下沉
        pCmdUI->SetCheck(PIC_line == m_picType);
}
3.8.        右键菜单修改选中图形的属性
实现方法如下:
第一步:在资源视图中增加一个菜单
第二步:在CHDrawView中增加右键菜单响应函数OnRButtonDown:
void CHDrawView::OnRButtonDown(UINT nFlags, CPoint point)
{
        //检查所有处于选中状态的图形,可以有多个
        CHDrawDoc *pDoc = GetDocument();
        m_strokeSelected.RemoveAll();//首先清空旧数据
        for(int i = 0; i < pDoc->m_strokeList.GetSize(); i ++){
                if(pDoc->m_strokeList.GetAt(i)->IsHightLight())
                        m_strokeSelected.Add(pDoc->m_strokeList.GetAt(i));
        }
        //显示右键菜单
        CMenu rmenu;
        rmenu.LoadMenu(IDR_MENU_SET);//加载资源中的菜单IDR_MENU_SET
        ClientToScreen(&point);//需要坐标转换
        rmenu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
        //因为这里的rmenu是局部变量,所以必须Detach掉
        rmenu.Detach();
        CView::OnRButtonDown(nFlags, point);
}
第三步:增加菜单响应函数,这里以删除当前所选图形为例:
void CHDrawView::OnPicDelete()
{
        //获取存储数据的文档类
        CHDrawDoc *pDoc = GetDocument();
        //移除所有处于选中状态的图形
        int i = 0, j = 0;
        for(; i < m_strokeSelected.GetSize(); i ++){
                //这里的j没有归0,是有原因的,可以很有效的提高效率
                //遍历复杂度为两个数组的和
                for(; j < pDoc->m_strokeList.GetSize(); j ++){
                        if(m_strokeSelected.GetAt(i) == pDoc->m_strokeList.GetAt(j)){
                                delete pDoc->m_strokeList.GetAt(j);
                                pDoc->m_strokeList.RemoveAt(j);
                                break;
                        }
                }
        }
        //如果没有处于选中状态的图形,则不需要刷新。
        if(i > 0)
                Invalidate();
}
3.9.        撤销和恢复操作
MFC提供了默认的撤销和恢复的ID,但是并没有提供默认实现,本程序的思路是,定义一个数组和一个数组索引,每执行一个操作,就把当前状态存储到数组中,并把数组索引加1。
撤销时,把索引减一的数组元素恢复到当前文档,恢复时,把索引加一的数组元素恢复到当前文档。
在程序中的步骤为:
第一步:定义数组,数组索引和备份,恢复函数:
        CObArray m_backup;
        int m_backup_index;
        void ReStore(BOOL backward);
        void BackUp();
void CHDrawDoc::BackUp()
{
        //备份操作,有利有弊。简单,节省内存,序列化有变时不需修改;产生文件占据磁盘
        CString fileName;
        fileName.Format("hjz%d", m_backup.GetSize());
        OnSaveDocument(fileName);
        //这里使用Insert而不是Add是因为恢复是并没有删除
        m_backup.InsertAt(m_backup_index++, NULL, 1);
}

void CHDrawDoc::ReStore(BOOL backward)
{
        m_backup_index -= backward ? 1 : -1;//撤销还是恢复
        //…把数组元素恢复到当前文档
        OnOpenDocument(m_backup.GetAt(m_backup_index-1));
}
第二步:添加撤销和恢复菜单项,并添加消息句柄:
void CHDrawDoc::OnEditUndo()
{
        ReStore(TRUE);
        UpdateAllViews(NULL);
}
void CHDrawDoc::OnEditRedo()
{
        ReStore(FALSE);
        UpdateAllViews(NULL);
}
第三步:在每次对文档的修改操作之前,调用GetDocument()->Backup()
3.10.        使用鼠标拖拽选中多个图形


回复

使用道具 举报

快速回复 返回列表 客服中心 搜索