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 編程小菜鳥自我總結,來往的朋友可以留下自己的建議和意見,謝謝!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM