zoukankan      html  css  js  c++  java
  • MFC开发--截图工具

      近期学习了MFC的相关知识,MFC(Microsoft Foundation Classes)是微软公司提供的一个类库,可以这样简单理解,就是对于Win32的封装(MFC对windows API函数的封装),但是MFC主要还是引入面向对象的开发思维,即一切用对象进行调用,我认为对巩固C++面向对象的思维有很大的帮助,所以进行了学习。并且开发了一个具有简单功能的截图工具,因为之前学习过一段时间的WIn32知识,所以在接下来的叙述中,主要以 对比Win32和C++的相关思想 为介入点进行介绍。

    MFC介绍(有MFC基础的朋友,可以跳过这一段)

    1.消息映射表

    在WIn32中的消息响应是在消息循环中,通过Switch-case来实现的,其实在MFC中,消息循环这一部分用遍历数组来代替了,并且在各个类中加入了 DECLARE_MESSAGE_MAP(),BEGIN_MESSAGE_MAP(),END_MESSAGE_MAP(),这个数组里存放了各种消息对应的函数指针,而且将消息和消息处理函数构成了一个映射表,又叫消息映射表。这方面和博客中另一篇文章 动态创建数组有异曲同工之妙。

    2.MFC框架

    MFC的框架由系统先搭好的窗口和CWinApp,CFrameWnd,CView,CDocument这些类构成,而这些类层次,我总结如下图所示

    3.MFC消息分类

    这个和Win32中消息有所不同,不同于键盘,鼠标这些客观发送的消息类型,在MFC中分为标准/系统消息,命令消息,控件/通知消息,自定义消息,同样我也总结了分别在哪使用,和如何进行添加,如图:

    在这需要注意的一点就是在自定义消息中,用户发送的两个函数有实现上的区别,SendMessage()是直接发送到目标类中,而PostMessage()是发送到消息队列中,由目标类自己去截获,消息少时没有影响,又称为同步和异步消息投放函数。

    接下来就介绍一下,这个截屏工具的开发思想,首先获取桌面屏幕信息,再将桌面信息存入客户区内,没错就是这么简单,附加的功能就是可以画图和撤销

    截图工具开发

    1.修改MFC框架中窗口样式

    这个因人而异,我是删除了状态栏,工具栏,边框(WS_POPUP)并且弹出就是全屏状态(值得注意的是在MFC中一个变量就表示了多种样式,微软的开发人员用位运算或来加各种属性,我相信日常开发人员在加属性时,这也是日常操作,所以在修改时,删去属性用异或,或者取反与也可以)

    2.添加一系列控件,并且加入消息处理函数

     这些都属于命令消息,需要注意的是在给关闭控件添加消息的时候,不能直接在关闭消息函数里就直接PostQuitMessage(),因为直接调用的话会出现内存泄漏,如下图:

     因为这就是关闭消息的过程,WM_CLOSE->destroy->...->PostQuitMessage,由此可以看出最后一步才是直接发送退出消息,所以在点击退出的同时,给主窗口(CMainFrme类)发送一个关闭的消息

    this->PostMessage(WM_CLOSE);

    3.截屏

    接下来就是截屏工具的主要功能,我分为三步,第一步,拿去桌面图片,第二步,保存,第三步,贴图至客户区

    在这就要介绍hdc在MFC中的封装,hdc是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows 数据结构。所有绘制调用都通过设备上下文对象进行,说白了就相当于画板,而在MFC中将GetDC,ReleaseDC这个函数都封装在CDC这些类的构造和析构中,除了不用手动调用创建和释放以外,还不用获取窗口句柄了,谁调用就是获取谁的,我也总结了一些常用的DC类,

    CClientDC        dc(this) 视图客户区
    CClientDC           dc(AfxGetMainWnd()) 获取主窗口
    CWindowDC    dc(AfxGetMainWnd()) 窗口
    CWindowDC    dc(GetDesktopWindow()) 桌面窗口
     1 CMainFrame::CMainFrame()
     2 {
     3     //============获取桌面图片 保存===============
     4 
     5     //--------------获取屏幕参数----------------
     6     m_nScreenCX = ::GetSystemMetrics(SM_CXSCREEN);
     7     m_nSCreenCY = ::GetSystemMetrics(SM_CYSCREEN);
     8     //--------------获取屏幕参数----------------
     9 
    10     //------------- 获取桌面图片----------------
    11     CWindowDC DesktopDC(GetDesktopWindow());
    12     CDC cdc;
    13     cdc.CreateCompatibleDC(&DesktopDC);
    14 
    15     CBitmap *bitmap = new CBitmap;
    16     bitmap->CreateCompatibleBitmap(&DesktopDC,m_nScreenCX,m_nSCreenCY);
    17     cdc.SelectObject(bitmap);
    18     cdc.BitBlt(0,0,m_nScreenCX,m_nSCreenCY,&DesktopDC,0,0,SRCCOPY);
    19     //------------- 获取桌面图片----------------
    20 
    21 
    22     //-----------------保存--------------------
    23     sk.push(bitmap);
    24 
    25 
    26 
    27     //-----------------保存--------------------
    28 
    29     //============获取桌面图片 保存===============
    30 }

    值得注意的是,我选择了栈作为保存方式,主要是因为我考虑到之后要写撤销的功能,而之后事实证明,利用栈这一数据结构来保存好处不止这一点,此时的效果如图

    4.画图控件处理

    我添加了 曲线,直线,长方形,圆形,三角形这些画图功能,在MFC中 ON_COMMAND_RANGE(起始ID,终止ID,处理函数地址)这个函数可以此命令可以处理相同操作的ID,因为无论什么形状,最终的功能就是画图,接下来就是如何判断点击的是何种控件,在这个地方用if去判断也可以,我利用资源中这个控件图片宏的连续性(不连续可以改连续),和枚举来进行判断,避免了大量的if判断

    1 enum {PEN,LINE,RETANGLE,CIRCULAR,TANGLE};
    2 
    3 void CCUTBITMAPView::OnChooseTool(UINT nID)
    4 {
    5     m_nDrawStyle = nID - ID_TOOL_PEN;
    6 }

    接下来就是画图功能,来分析一下,在选择后鼠标按下开始画,鼠标松开时结束画,所以添加了WM_LBUTTONDOWN,WM_LBUTTONUP,WM_MOUSEMOVE三个命令消息,并且建立一个标记来记录鼠标是否按下了,不然的话,打开后不用点击就会自动开始画,除了曲线以外都可以用MFC库函数来进行查询(简单练习不建议使用太复杂的多边形,因为到最后就是复杂的高中几何题呀),曲线的画法就像微积分中的微分思维一样,当一条曲线切割的足够短时,就是N多条直线组成,那么只要起始和结束坐标切换足够快的话就是曲线了。

    5.bug处理

    到这一步就可以在截屏的图案上进行绘画了,但是会发现之前画的痕迹并没有消除,有点3D的感觉,哈哈哈,如何处理这个问题呢,重复贴图,利用之前的栈顶元素来擦除痕迹,接下来痕迹是没有了,但是不停的在闪烁,常见问题--闪烁就用双缓冲来解决吧,不过MFC和WIn32这个思维转化的有点困难,对象调用的思维还得多熟悉熟悉,把代码贴上来,有MFC的初学者也可以看看

     1 void CCUTBITMAPView::OnMouseMove(UINT nFlags, CPoint point)
     2 {
     3     CMainFrame *pFrame = (CMainFrame*)AfxGetMainWnd();
     4     if(m_bIsLButtonDown == true)
     5     {
     6         CClientDC dc(this);
     7         if(m_nDrawStyle == PEN)
     8         {
     9             dc.MoveTo(FirstPoint);
    10             dc.LineTo(point);
    11             FirstPoint = point;
    12             return;
    13         }
    14         //------------双缓冲------------
    15         CDC hMenDC;
    16         hMenDC.CreateCompatibleDC(&dc);
    17         CBitmap bitmap;
    18         bitmap.CreateCompatibleBitmap(&dc,pFrame->m_nScreenCX,pFrame->m_nSCreenCY);
    19         hMenDC.SelectObject(bitmap);
    20 
    21 
    22         //------------双缓冲------------
    23 
    24         //-----------用栈顶元素 擦除轨迹------------
    25         CDC cdc;
    26         cdc.CreateCompatibleDC(&dc);
    27         cdc.SelectObject(pFrame->sk.top());
    28         hMenDC.BitBlt(0,0,pFrame->m_nScreenCX,pFrame->m_nSCreenCY,&cdc,0,0,SRCCOPY);
    29         //-----------用栈顶元素 擦除轨迹------------
    30         switch (m_nDrawStyle)
    31         {
    32             case LINE:
    33                 {
    34                     hMenDC.MoveTo(FirstPoint);
    35                     hMenDC.LineTo(point);
    36                 }
    37                 break;
    38             case RETANGLE:
    39                 {
    40                     hMenDC.Rectangle(FirstPoint.x,FirstPoint.y,point.x,point.y);
    41                 }
    42                 break;
    43             case CIRCULAR:
    44                 {
    45                     hMenDC.Ellipse(FirstPoint.x,FirstPoint.y,point.x,point.y);
    46                 }
    47                 break;
    48             case TANGLE:
    49                 {
    50                     POINT tangle[3] = {
    51                         {(FirstPoint.x+point.x)/2,FirstPoint.y},
    52                         {point.x,point.y},
    53                         {FirstPoint.x,point.y}
    54                     };
    55                     hMenDC.Polygon(tangle,3);
    56                 }
    57                 break;
    58         }
    59         dc.BitBlt(0,0,pFrame->m_nScreenCX,pFrame->m_nSCreenCY,&hMenDC,0,0,SRCCOPY);
    60     }
    61 
    62     CView::OnMouseMove(nFlags, point);
    63 }

    但是每一次画的,第二次点击,都会覆盖掉,解决这个,就是和当时截取桌面画面一样,只不过这次在鼠标抬起时,截取客户区,存入栈中,同样每次拿栈顶消除痕迹就会留下痕迹了,代码如下

     1 void CCUTBITMAPView::OnLButtonUp(UINT nFlags, CPoint point)
     2 {
     3     m_bIsLButtonDown = false;
     4     //----------------------留下之前的痕迹-----------------------
     5     CMainFrame *pFrame = (CMainFrame*)AfxGetMainWnd();
     6 
     7 
     8     CClientDC dc(this);
     9 
    10     CDC cdc;
    11     cdc.CreateCompatibleDC(&dc);
    12     CBitmap *pBitMap = new CBitmap;
    13     pBitMap->CreateCompatibleBitmap(&dc,pFrame->m_nScreenCX,pFrame->m_nSCreenCY);
    14     cdc.SelectObject(pBitMap);
    15     cdc.BitBlt(0,0,pFrame->m_nScreenCX,pFrame->m_nSCreenCY,&dc,0,0,SRCCOPY);
    16 
    17     pFrame->sk.push(pBitMap);
    18     //----------------------留下之前的痕迹-----------------------
    19     CView::OnLButtonUp(nFlags, point);
    20 }

    6.撤销功能

    Ctrl+Z首先先添加到键盘映射表中

    主要思路就是不停的将栈顶元素弹出栈中,就可以了,代码如下

     1 void CMainFrame::OnAccelerator32778()
     2 {
     3     //撤销的函数
     4     if(sk.size() > 1)
     5     {
     6         delete sk.top();
     7         sk.pop();
     8         GetActiveView()->SendMessage(WM_PAINT);
     9     }
    10 }

    接下来,还有保存等其他的功能,未完待续。。。,先放一张实现的截图

     最后,记录一个我觉得挺重要MFC的消息流向(因为知道,什么消息写在哪个类中)

    标准消息 WM_XXX 子类到父类,查找消息映射表
    命令消息 WM_COMMAND 消息路由 CView-->CDoc-->CDocTemplate-->CFrameWnd-->CWinApp
    控件/通知消息 WM_NOTIFY 子窗口到父窗口,查找消息映射表
    自定义消息 UM_XXX 确定了,查找调用者那个类

     

     

    2019-07-21 19:17:07 编程小菜鸟自我总结,来往的朋友可以留下自己的建议和意见,谢谢!!!

  • 相关阅读:
    【bzoj2500】幸福的道路 树形dp+单调队列
    【ARC069F】Flags 2-sat+线段树优化建图+二分
    【bzoj2437】[Noi2011]兔兔与蛋蛋 二分图最大匹配+博弈论
    剑指offer——树的子结构
    剑指offer——反转链表
    腾讯算法岗面试算法题——计数排序
    作业帮面试题
    剑指offer——重建二叉树
    剑指offer——二维数组中的查找
    删除链表中重复的结点
  • 原文地址:https://www.cnblogs.com/xgmzhna/p/11222308.html
Copyright © 2011-2022 走看看