VC编程技术点滴(四)鼠标绘制任意图形

VC编程技术点滴(四)鼠标绘制任意图形

 

一、建立工程
    在Visual Studio 6.0中新建一个单文档工程,除在文档模式中选择单文档方式外,其它步骤均选择默认配置,工程名为CreateLine。
二、鼠标划线的实现思路
    本例为使用鼠标绘制线段、矩形、圆及任意曲线等图形。

    1、画线段:在菜单中选择划线命令,在窗口客户区可以单击鼠标左键连续划线,双击鼠标左键结束划线。设置一个保存鼠标 单击次数的变量,当变量值为0时,使用MoveTo函数设置起始点,变量值大于0时,使用LineTo函数实现连续划线;利用鼠标左键双击消息处理函数, 设置鼠标单击次数变量值为-1,结束划线;利用鼠标移动消息处理函数实现划线过程中的橡皮线显示。

    2、画圆:选择画圆菜单,点击鼠标左键确定圆心,释放左键并拖动鼠标以确定半径,再次点击鼠标时画圆。

    3、画矩形:与画圆类似,点击鼠标左键确定矩形左上角,释放左键并拖动鼠标已确定矩形尺寸,再次点击鼠标时画矩形。

    4、画任意曲线:选择菜单,点击鼠标左键确定曲线起始点,释放并拖动鼠标绘制任意曲线,再次点击鼠标左键结束绘制。
三、具体实现
    1、设置成员变量
    在工程的ClassView中右键选择CCreateLineView类,选择添加成员变量,包括记录图形类型的CString变量 m_drawtype;记录线段起始和终止点(对圆来说则是圆心坐标和圆周上的点坐标)的m_Startp和m_Endp,类型为CPoint;添加记录 鼠标左键单击次数的变量m_step(类型为int)。
    2、添加绘图菜单命令
    为统一处理鼠标绘图菜单消息,这里用到了ON_COMMAND_RANGE宏和ON_UPDATE_COMMAND_UI_RANGE宏,用于映射一组绘图菜单命令。下面是MFC手册中关于ON_COMMAND_RANGE宏的用法:

    ON_COMMAND_RANGE( id1, id2, memberFxn )

    参数: id1 一个连续范围的命令ID的起始值。 
           id2 一个连续范围的命令ID的结束值。 
           memberFxn 命令组被映射到的消息处理函数的名字。 

    说明:
    不同于ON_COMMAND把单个命令ID映射到成员函数。使用这个宏可以把一个连续范围的多个命令ID映射到单个命令处理成员函数。ID的范围从id1 开始,到id2结束,也就是说这一组命令ID要连续。一般来说,只要连续定义菜单等命令,其ID都是连续的,可以通过 View->Resourec symbloes菜单,或String Table资源来查看。如果命令ID不连续,可以直接在Resource.h中修改。
    由于ClassWizard不支持消息映射范围,所以你必须自己写入这个宏。确保你把它写在了消息映射的“//{{AFX_MSG_MAP”分界符外面。   

    基于此,可以在ResourceView的菜单资源中添加“鼠标绘图”菜单,在该菜单下添加“绘直线”、“画圆”、“画矩形”、“画任意曲线”等二级菜单,并编辑各菜单的命令ID名称,如画曲线的命令ID为ID_CURVE。

    在CreateLineView.h和CreateLineView.cpp文件中手工添加如下代码:

    // CreateLineView.h : interface of the CCreateLineView class

    ......

    class CCreateLineView : public CView
    {

       ......

     // Generated message map functions
     protected:
        //{{AFX_MSG(CCreateLineView)
        afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
        afx_msg void OnMouseMove(UINT nFlags, CPoint point);
        //}}AFX_MSG
        //以上为使用类向导添加的消息处理函数(后面还有详细说明)

        //下两行代码为手工添加,用于处理鼠标绘图菜单的统一处理
        afx_msg void OnDrawByMouse(int m_nID);
       afx_msg void OnUpdateDrawByMouse(CCmdUI *pCmdUI);

       ......

    };

    ......

    //CreateLineView.cpp :implementation of the CCreateLineView class

    ......

    //消息映射
    BEGIN_MESSAGE_MAP(CCreateLineView, CView)
          //{{AFX_MSG_MAP(CCreateLineView)
          ON_WM_LBUTTONDOWN()
          ON_WM_LBUTTONDBLCLK()
          ON_WM_MOUSEMOVE()
          //}}AFX_MSG_MAP
          // Standard printing commands
          ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
          ......   

          //以下两行代码为手工添加,用于鼠标绘图菜单的统一处理

          //设置菜单范围的第一个和最后一个菜单项
          ON_COMMAND_RANGE(ID_DRAWLINE,ID_CURVE,OnDrawByMouse)
         ON_UPDATE_COMMAND_UI_RANGE(ID_DRAWLINE,ID_CURVE,OnUpdateDrawByMouse)

    END_MESSAGE_MAP()

    ......

    //手工添加相应函数

    void CCreateLineView::OnDrawByMouse(int m_nID)
    {
       switch(m_nID)
       {
         case ID_DRAWLINE:
           m_drawtype="line";
           m_step=0;
           break;
         case ID_DRAWCIRCLE:
           m_drawtype="circle";
           m_step=0;
           break;
         case ID_DRAWRECT:
           m_drawtype="rect";
           m_step=0;
           break;
         case ID_CURVE:
           m_drawtype="curve";
           m_step=0;
           break;
         default:;
       }
    }

    void CCreateLineView::OnUpdateDrawByMouse(CCmdUI *pCmdUI)
    {
       int nFlag = 0 ;
       switch(pCmdUI->m_nID)
       {
         case ID_DRAWLINE:
           if(m_drawtype=="line")

              nFlag=1;
           break;
         case ID_DRAWCIRCLE:
           if(m_drawtype=="circle")

              nFlag=1;
           break;
         case ID_DRAWRECT:
           if(m_drawtype=="rect")

              nFlag=1;
           break;
         case ID_CURVE:
           if(m_drawtype=="curve")

              nFlag=1;
              break;
         default:;
       }

       pCmdUI->SetCheck(nFlag);
    }

    ......
    3、添加鼠标左键单击消息处理函数
    在ClassWizard中为CCreateLineView类添加鼠标左键单击消息处理函数(消息名称为WM_LBUTTONDOWN):
    void CCreateLineView::OnLButtonDown(UINT nFlags, CPoint point)
    {
       CDC* pDC = GetDC() ;//获取设备环境
       pDC->SelectStockObject(NULL_BRUSH) ;//将库存GDI对象选进设备环境 
       if(m_drawtype== "line")
       {
         if(m_step==0)//第一次单击鼠标左键确定直线起始点
         {
           m_Startp=m_Endp=point;
           m_step++;
         }
         if(m_step>0)//再次单击鼠标左键确定直线段终点,可以连续绘制折线
         {
           m_Endp=point;
           m_step++;
           DrawLine(pDC,m_Startp,m_Endp);//定义的成员函数

           m_Startp=point;  
         }
      
       if(m_drawtype== "circle")
       {
         if(m_step==0)
         {
           m_Startp=m_Endp=point;
           m_step=1;
         }

         else if(m_step==1)//如果没有else,两个条件语句都会执行(上一if改变了条件变量)
         {
           m_Endp=point;
           m_step=0;//等于0可以继续画圆,等于-1则一次只能画一个圆。双击鼠标左键可以停止画图。
           DrawCircle(pDC,m_Startp,m_Endp);//定义的成员函数
         }
       }

       if(m_drawtype== "rect")
       {
          if(m_step==0)
          {
            m_Startp=m_Endp=point;
            m_step=1;
          }

          else if(m_step==1)
          {
            m_Endp=point;
            m_step=0;
            DrawRect(pDC,m_Startp,m_Endp);//定义的成员函数
          }
       }

       //绘制曲线在鼠标移动函数中具体实现
       if(m_drawtype == "curve")
       {
         if(m_step==0)//第一次单击鼠标左键确定曲线起始点
         {
            m_Startp=m_Endp=point;
            m_step=1;
         }
         else if(m_step==1)//再次单击鼠标左键结束绘制曲线

            m_step=0; 
       }

       ReleaseDC(pDC) ;//释放掉不再使用的DC ; 
       CView::OnLButtonDown(nFlags, point);
    }

    4、添加鼠标左键双击消息处理函数
    在ClassWizard中为CCreateLineView类添加鼠标左键双击消息处理函数(消息名称为WM_LBUTTONDBLCLK):
    void CCreateLineView::OnLButtonDblClk(UINT nFlags, CPoint point)
    {
       m_step=-1;//表示结束划线。

       CView::OnLButtonDblClk(nFlags, point);
    }

    5、添加成员函数

    为CCreateLineView类添加DrawLine等成员函数:

 

    // CreateLineView.h

    protected:

    void DrawLine(CDC *pDC,CPoint startPoint,CPoint endPoint);
    void DrawRect(CDC *pDC,CPoint m_Startp,CPoint m_Endp);
    int ComputeRadius(CPoint m_Centerp,CPoint m_Arroundp);
    void DrawCircle(CDC *pDC,CPoint m_Centerp,CPoint m_Arroundp);

 

    // CreateLineView.cpp

    void CCreateLineView::DrawLine(CDC *pDC,CPoint startPoint, CPoint endPoint)
    {
       pDC->MoveTo(startPoint);
       pDC->LineTo(endPoint);
    }

    void CCreateLineView::DrawCircle(CDC *pDC, CPoint m_Centerp, CPoint m_Arroundp)
    {
       int radius=ComputeRadius(m_Centerp,m_Arroundp);
       CRect rc(m_Centerp.x-radius,m_Centerp.y-radius,m_Centerp.x+radius,m_Centerp.y+radius);
       pDC->Ellipse(rc);
    }
    //计算半径
    int CCreateLineView::ComputeRadius(CPoint m_Centerp, CPoint m_Arroundp)
    {
      int dx=m_Centerp.x-m_Arroundp.x;
      int dy=m_Centerp.y-m_Arroundp.y;
      return (int)sqrt(dx*dx+dy*dy);
    }

    void CCreateLineView::DrawRect(CDC *pDC, CPoint m_Startp, CPoint m_Endp)
    {
       pDC->Rectangle(m_Startp.x,m_Startp.y,m_Endp.x,m_Endp.y);
    }

   
    6、OnMouseMove函数(包括橡皮线的实现):
    在ClassWizard中为CCreateLineView类添加鼠标移动消息处理函数(消息名称为WM_MOUSEMOVE):
    //参考《Visual C++ 实践与提高》之图形图像编程篇   

    void CCreateLineView::OnMouseMove(UINT nFlags, CPoint point)
    {
       //插入设置状态栏的代码(后面有详细说明)
       CDC* pDC = GetDC() ;
       //设置绘图模式,并将先前的绘图模式加以保存
       int nDrawmode = pDC->SetROP2(R2_NOT) ;// 设置绘图像素的颜色为屏幕颜色的反色
       //将库存GDI对象选进设备环境
       pDC->SelectStockObject(NULL_BRUSH) ;

       if(m_drawtype=="line")
       {
         if(m_step >0)
         {
            CPoint prePnt, curPnt ;
            // 获得鼠标所在的前一个位置
            prePnt = m_Endp ;
            curPnt = point ;
            //绘制橡皮线
            DrawLine(pDC, m_Startp,prePnt) ;//该语句起到抹掉上一次划线的作用。当第一次点击鼠标

            //左键时,m_Startp=m_Endp,m_step由0变为1,移动鼠标时,先划了从m_Startp到m_Endp的

            //线(由于两个点的位置相同,系统并未真正划线),又划了从m_Startp到鼠标移动的当前点

            //的线,同时,m_Endp被赋值为鼠标移动的当前点;再次移动鼠标,又会先划一条从m_Startp

            //到m_Endp的线,等于重划了上次移动鼠标时所划的第二条线,由于设置线像素的颜色为屏幕

            //颜色的反色,重划线等于抹掉原来划的线;然后再划从m_Startp到鼠标移动的当前点的线,

            //依次类推,划出新橡皮线,抹掉旧线,就起到了橡皮线的作用。
            DrawLine(pDC, m_Startp,curPnt) ;
            m_Endp = point ;
         }
       }

       if(m_drawtype=="circle")
       {
         if(m_step>0)
         {
            CPoint prePnt, curPnt ;
            // 获得鼠标所在的前一个位置
            prePnt = m_Endp ;
            curPnt = point ;
            DrawCircle(pDC, m_Startp,prePnt) ;//绘制橡皮线            
            DrawCircle(pDC, m_Startp,curPnt) ;       
            m_Endp = point ;
         }
       }

       if(m_drawtype=="rect")
       {
         if(m_step>0)
         {
            CPoint prePnt, curPnt ;
            // 获得鼠标所在的前一个位置
            prePnt = m_Endp ;
            curPnt = point ;
            //绘制橡皮线
            DrawRect(pDC, m_Startp,prePnt) ;
            DrawRect(pDC, m_Startp,curPnt) ;       
            m_Endp = point ;
         }
       }

       if(m_drawtype=="curve")
       {
         if(m_step ==1)
         {
           CPoint prePnt, curPnt ;
           // 获得鼠标所在的前一个位置
           prePnt = m_Endp ;
           curPnt = point ;
           //绘制任意曲线

           //划任意曲线的方式:点击划线菜单,在窗口中点击选择起始点位置,移动鼠标就可以任意划

           //线,再次点击鼠标左键,结束当前曲线输入;选择新的曲线起始点可以继续绘制新曲线。双

           //击鼠标左键结束曲线绘制。
           DrawLine(pDC, prePnt,curPnt) ;      
           m_Endp = point ;
         }
       }
       //恢复到先前的绘图模式
       pDC->SetROP2(nDrawmode) ;
       //释放掉不再使用的DC ;
       ReleaseDC(pDC) ; 
       CView::OnMouseMove(nFlags, point);
    }
    7、状态栏显示鼠标坐标
    在框架类的状态栏设置中增加一个分割区:
    // MainFrm.cpp : implementation of the CMainFrame class
    static UINT indicators[] =
    {
       ID_SEPARATOR,           //增加一个状态分隔区
       ID_SEPARATOR,           // status line indicator
       ID_INDICATOR_CAPS,
       ID_INDICATOR_NUM,
       ID_INDICATOR_SCRL,
    };

    在视图类的鼠标移动消息处理函数中增加状态显示代码:

    // CreateLineView.cpp
    void CCreateLineView::OnMouseMove(UINT nFlags, CPoint point)
    {
       //设置状态条,显示鼠标坐标
       CStatusBar* pStatus=

            (CStatusBar*)AfxGetApp()->m_pMainWnd->GetDescendantWindow(ID_VIEW_STATUS_BAR);
       //ASSERT(pStatus) ;
       CString str ;
       str.Format("(%8d,%8d)",point.x,point.y) ;

       if(pStatus)
       {
          //pStatus->SetPaneText(0,str);未增加状态栏分割区前,只能在第1个分割区显示
          pStatus->SetPaneText(1,str);//在增加了状态栏分割区后,可在第2个分割区显示
       }

      //插入前面提到的其它绘图代码
       CView::OnMouseMove(nFlags, point);
    }

posted @ 2011-08-19 21:08  luoshupeng  阅读(3468)  评论(0编辑  收藏  举报