正文
所謂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 *pDC = CDC::FromHandle(hdc); //創建兼容DC(屏幕)
CDC imageMemDC;
imageMemDC.CreateCompatibleDC(pDC);
在設定上 OnEraseBkgnd()是用來畫底圖的 而OnPaint()是用來畫主要物件的
舉例說明 一個按鈕是灰色的 上面還有文字
則OnEraseBkgnd()所做的事就是把按鈕畫成灰色
而OnPaint()所做的事 就是畫上文字
既然這兩個member function都是用來畫出元件的
那為何還要分OnPaint() 與 OnEraseBkgnd() 呢
其實OnPaint() 與 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速在裡面的繪圖程式最好是不要太耗時間
因為每當window元件有任何小變動都會馬上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程式有空閒的時候才會被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能會呼叫OnEraseBkgnd()好幾次
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
return (HBRUSH)m_brBk.GetSafeHandle();
return hbr;
}
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.
HDC hdc = ::GetDC(playWnd);
CDC *playDC = CDC::FromHandle(hdc);
LR_DEFAULTSIZE | LR_CREATEDIBSECTION | LR_LOADFROMFILE);
如果自己的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)。