VC GDI編程

 正文 
所謂GDI(Graphics Device Interface,圖形設備接口)其實就是API函數中專門針對於圖形開發的函數集合這些函數都是Microsoft公司編寫好的,爲了能讓開發人員快速地開發圖形程序,開發人員只需要調用就行

在談GDI函數之前,一定要先講一下數據,因爲圖形開發,肯定離不開數據以簡單的二維圖形爲例,你想在窗口中顯示一個正弦曲線,就必須有這個正弦曲線的數據,然後用GDI提供的畫圖函數,講數據顯示成圖形
二維圖形其實就是以點線面三種元素組成 

點 
圖形中最基本的元素就是點,C++中對於點的結構定義是 
typedef struct tagPOINT 

   LONG x; 
   LONG y; 
} POINT; 
就是由橫座標x和縱座標y組成的一個點(這個中學生就懂,呵呵,那也不能省) 
GDI中將點畫到窗口中的函數是SetPixel 


線 
線就是有序的點集合,可以用一個點數組表示一條線,也可以用鏈表來表示一條線,兩者的區別就在於添加和刪除線中一點的方便程度數組的優點在於存取方便,但添加和刪除確比較麻煩,鏈表優點在於添加和刪除方便,但系統開銷比數組大具體大家在開發中是利用數組還是鏈表,需要根據具體需求來決定GDI中使用MoveToLineTo和PolyLine函數來繪製線

面 
面也是由一組有序點組成的,它和線的唯一區別在於繪製的時候需要用不同的函數來實現而且繪製面的時候,需要將數據以數組方式存儲,因爲GDI函數繪製面的函數Polygon和PolyPolygon不支持鏈表

GDI函數 
純API開發C++程序,開發效率有限,而利用MFC對於VC開發效率有很大的提高,所以我們這以MFC開發爲例說明GDI開發 

GDI開發中第一個概念就是DC(Device Context),大部分的書都翻譯成設備上下文,這其實是一種直譯,很多新手(我當初就是其中之一,呵呵)看到設備上下文的時候,都不知道是什麼東西可以簡單理解爲畫布,或當前窗口中繪圖控制的一個接口它是一個抽象集合或抽象概念,用它可以有效地管理畫筆刷子字體圖片和區域
CDC就是MFC中,對應DC的類 
以CDC來繪製窗口內圖形的步驟 
1. 獲取當前窗口的DC,獲取窗口DC可以用CDC* CWnd::GetDC()函數得到一個CDC指針, 
2. 得到DC指針後,先調用CDC::SelectObject 函數來設定畫筆刷子字體圖片和區域等繪圖設置工作 
3. 調用繪圖函數SetPixel繪製點,MoveToLineTo和PolyLine繪製線,Polygon和PolyPolygon繪製面 
4. 調用CDC::SelectObject恢復繪圖前的設定 
5. 釋放資源,其中就有DC的釋放,CWnd::ReleaseDC(CDC* pDC),將最開始獲取的DC指針釋放

 

2.CDC與HDC的區別

CDC是MFC的DC的一個類 
HDC是DC的句柄,API中的一個類似指針的數據類型. 
MFC類的前綴都是C開頭的 
H開頭的大多數是句柄 
CDC是所有MFC中的DC的基類.常用的CClientDC dc(this);就是CDC的子類(或稱派生類). 
CDC等設備上下分類,都含有一個類的成員變量:m_nHdc;即HDC類型的句柄. 
 
3.CDC對象提供處理顯示器或打印機等設備上下文的成員函數,以及處理與窗口客戶區對應的顯示上下文的成員。
 
4.有關CDC類的繼承  
        父類:從 CObject 直接繼承而來。繼承了CObject類的各種特性,如動態創建等等。
  子類:CClientDC-------代表操作窗口的DC ,是比較常用的一個子類
  CMetaFileDC ------響應Meta File的DC ,Meta File是一些GDI消息。
  CPaintDC-------響應WM_PAINT消息的DC。
  CWindowDC ------代表整個屏幕的DC
 
5.內存DC
在windows下搞圖形界面的設計難免要使用到內存DC,將所有的繪製工作先繪製在內存DC上,然活一次性拷貝到屏幕DC上,就是這樣了。可以消除一些圖形的閃爍問題,當然還有其他的用處,比如簡單遊戲中的象素碰撞檢測等等
5.1創建內存DC
    HDC hdc = ::GetDC(NULL);  
 CDC   *pDC = CDC::FromHandle(hdc);   //創建兼容DC(屏幕)
 CDC imageMemDC;
 imageMemDC.CreateCompatibleDC(pDC);
 
6.OnPaint與OnEraseBkgnd()
在MFC中 任何一個window元件的繪圖 都是放在這兩個member function中
在設定上 OnEraseBkgnd()是用來畫底圖的 而OnPaint()是用來畫主要物件的
舉例說明 一個按鈕是灰色的 上面還有文字
則OnEraseBkgnd()所做的事就是把按鈕畫成灰色
而OnPaint()所做的事 就是畫上文字


既然這兩個member function都是用來畫出元件的
那為何還要分OnPaint() 與 OnEraseBkgnd() 呢
其實OnPaint() 與 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速在裡面的繪圖程式最好是不要太耗時間
因為每當window元件有任何小變動都會馬上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程式有空閒的時候才會被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能會呼叫OnEraseBkgnd()好幾次
 
7.Onpanit與OnEraseBkgnd()
1. 系統的Onpaint中調用了OnDraw,但如果我們自己繼承了一個OnPaint函數又沒有顯式調用OnDraw,則OnDraw就不會被調用
2.OnPaint()是CWnd的類成員,負責響應WM_PAINT消息。OnDraw()是CVIEW的成員函數,沒有響應消息的功能.當視圖變得無效時(包括大小的改變,移動,被遮蓋等等),Windows發送WM_PAINT消息。該視圖的OnPaint 處理函數通過創建CPaintDC類的DC對象來響應該消息並調用視圖的OnDraw成員函數.OnPaint最後也要調用OnDraw,因此一般在OnDraw函數中進行繪製。
3.
 
8.OnCtlColor 返回HBRUSH句柄,用該句柄來繪製窗口背景色,
例如
HBRUSH CtestOnPaintDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
 HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
//m_brBk.CreateSolidBrush(RGB(113 , 111 , 100)); //創建刷子,在初始化或者其他函數中調用
    
 return (HBRUSH)m_brBk.GetSafeHandle();
 return hbr;
}
 
9.資源釋放
在windows系列上做編程,gdi是一個很重要的技術點,有很多程序在運行多次後出現異常,除了衆所周知的內存泄露以外,gdi資源泄露也是一個很直接的原因.今天就把我自己在編程中總結的一些經驗給大家分享,歡迎高手補充.
1.Create出來的gdi對象,一定要用DeleteObject來釋放,釋放順序是先Create的後釋放,後Create的先釋放.
這裏的Create指的是以它爲開頭的gdi函數,比如,CreateDIBitmap,CreateFont等等,最後都要調用DeleteObject來釋放.

2.Create出來的dc要用DeleteDC來釋放,Get到的要用ReleaseDC釋放.

3.確保釋放DC的時候DC中的各gdi對象都不是你自己創建的;確保個gdi對象在釋放的時候不被任何dc選中使用.
假如我們要使用gdi函數畫圖,正確的步驟應該如下:
a.創建一個內存兼容dc(CreateCompatibleDC)
b.創建一個內存兼容bitmap(CreateCompatibleBitmap)
c.關聯創建的內存兼容dc和bitmap(SelectObject)
d.畫圖
e.BitBlt到目的dc上
f.斷開內存兼容dc和bitmap關聯(SelectObject)
g.銷燬內存兼容bitmap
h.銷燬內存兼容dc
由於SelectObject在選入一個新的gdi對象的時候會返回一個原來的gdi對象(假如成功的話),所以需要在步驟c的時候保存返回值,在步驟f的時候當作入口參數使用.還有,步驟g和步驟h實際上順序可以隨意,因爲他們兩個此刻已經沒有關係了,但是爲了結構清晰,我建議按照"先Create的後釋放,後Create的先釋放"的原則進行.
關於步驟f,可能會有爭議,因爲即使省略這一步,步驟g和步驟h看起來照樣可以返回一個成功的值.但實際上可能並沒有執行成功,至少boundschecker會報告有錯,錯誤信息大致是說,在釋放dc的時候還包含有非默認的gdi對象,在釋放gdi對象的時候又說這個gdi對象還被一個dc在使用.所以,我建議保留步驟f.
 
 
案例:
 HWND playWnd = GetPlayWnd();
 HDC hdc = ::GetDC(playWnd);
 CDC *playDC = CDC::FromHandle(hdc);
::ReleaseDC(playwnd,hDC);  //釋放
CDC *playDC = CDC::FromHandle(hdc); 要不要釋放呢?  不是 "可以不釋放 ",而是 "不可以釋放 ".   FromHandle   是通過   HDC   來創建了一個   CDC   對象,以方便操作,釋放   DC   的操作應該針對於   HDC   而非此   CDC   ,   如果釋放了它   pDC-> ReleaseDC   ,就會造成隱患. 只能釋放一次.
 
8.  HBITMAP使用心得
HBITMAP  m_hBitmap = (HBITMAP)::LoadImage(NULL, "D:\\fdf.bmp", IMAGE_BITMAP, 0, 0, 
   LR_DEFAULTSIZE | LR_CREATEDIBSECTION | LR_LOADFROMFILE);
HBITMAP在初始化後就 保留圖片數據,即便圖片被刪除,m_hBitmap裏的數據也不會丟失,指導它被刪除前.
 
 
9.關於CDialog::OnPaint。

如果自己的OnPaint代碼什麼也沒做的話(至少沒有聲明過CPaintDC類型的變量),還必須調用一下CDialog::OnPaint,否則BeginPaint和EndPaint就沒有辦法被調用了。

總之,在響應WM_PAINT消息的時候,必須調用一遍BeginPaint和EndPaint。調用的方法有三種:
1、聲明一個CPaintDC類型的變量(即使你什麼也不畫),CPaintDC的構造函數就是調用BeginPaint,析構函數就是調用EndPaint。
2、調用基類的OnPaint(實際上就是調用API的DefWindowProc,它會自動調用BeginPaint和EndPaint)。
3、自己直接調用BeginPaint和EndPaint。
上述三種方法,必須選擇其一,而且也只能選擇其一(因爲在一個WM_PAINT消息內不能調用兩次BeginPaint和EndPaint)。
發佈了4 篇原創文章 · 獲贊 4 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章