原创]Windows Gdi入门初级应用(VC SDK)
原創(chuàng)]Windows Gdi入門初級(jí)應(yīng)用(VC SDK)?
?
好久沒發(fā)貼了,今天手癢癢,發(fā)一個(gè)。
GDI的繪圖函數(shù)基本上都是有狀態(tài)的,所有的函數(shù)都要求一個(gè)HDC類型的句柄。
這個(gè)HDC的獲得有幾個(gè)途徑BeginPaint,GetWindowDC, GetDC.他們的參數(shù)都只需要一個(gè)HWND就差不多了。
記得調(diào)用了BeginPaint后要調(diào)用EndPaint進(jìn)行清理,調(diào)用GetWindowDC和GetDC后要調(diào)ReleaseDC進(jìn)行清理。
在MFC代碼中常常遇到的CDC CPaintDC CWindowDC CClientDC。在這里稍作解釋。
CDC :例如用GDI畫矩形要Rectangle(hDC,...),而使用CDC則是dc.Rectangle(...),由此可見CDC主要是把原本需要HDC作為參數(shù)的GDI函數(shù)封裝了一下,HDC成了它的一個(gè)成員變量。
CPaintDC CWindowDC CClientDC:他們都是從CDC繼承,分別是對(duì)上面所說的BeginPaint,GetWindowDC, GetDC調(diào)用對(duì)進(jìn)行封裝(CPaintDC構(gòu)造時(shí)調(diào)用BeginPaint,析構(gòu)時(shí)調(diào)用EndPaint,其余同理)。
BeginPaint一般用在對(duì)WM_PAINT的響應(yīng)函數(shù)中使用
GetWindowDC可獲得整個(gè)Window的HDC,而GetDC僅能獲得客戶區(qū)的HDC,區(qū)別就在于----
前者有效地繪制區(qū)域是整個(gè)窗口(邊框、標(biāo)題欄、客戶區(qū)的總和)。
后者有效地繪制區(qū)域僅限于客戶區(qū)。
兩者的坐標(biāo)系都是相對(duì)坐標(biāo)而非屏幕坐標(biāo),原點(diǎn)是(0,0)。即以自己可繪制區(qū)域的左上角作為原點(diǎn)。
這里可以順帶的講講RECT了,RECT是一個(gè)結(jié)構(gòu),依次有4個(gè)成員left,top,right,bottom用來代表一個(gè)矩形區(qū)域。CRect從RECT繼承,提供了一些常用的操作(例如說位移,縮小等等),其實(shí)就是改變4個(gè)成員的值。完全不用CRect也可以。許多GDI函數(shù)都要求一個(gè)RECT作為參數(shù),或者類似的用(x,y,cx,cy)作參數(shù),其實(shí)也就是一個(gè)RECT變種,用了寬度和高度罷了。
基礎(chǔ)知識(shí)介紹完畢,開始實(shí)例教程:
我們以如何繪制一個(gè)具有平面風(fēng)格的狀態(tài)欄為例:
首先從CStatusBar繼承一個(gè)類:CStatusBarNew。(如果無法通過類向?qū)ё鲞@件事,而你又對(duì)MFC的MESSAGEMAP等等東西不熟悉,可以從CStatusBarCtrl繼承一個(gè),待生成代碼后,把所有的CStatusBarCtrl改為CStatusBar)
在此,只需要重寫WM_PAINT和WM_ERASEBKGND這兩個(gè)消息的響應(yīng)函數(shù)。
BOOL CStatusBarNew::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CRect rect;
GetWindowRect(&rect);
ScreenToClient(&rect);
CBrush brush(0xf2f2f2);
pDC->FillRect(&rect, &brush);
return TRUE;
}
這個(gè)函數(shù)把狀態(tài)欄背景用0xf2f2f2這種顏色填充。
void CStatusBarNew::OnPaint()
{
CPaintDC cDC(this); // device context for painting
// TODO: Add your message handler code here
CRect rcItem;
cDC.SetBkMode(TRANSPARENT);
cDC.SelectObject (::GetStockObject (NULL_BRUSH));//選入畫刷
// 獲取字體
CFont* pfont = GetFont();
CFont* def_font;
if (pfont)
def_font = cDC.SelectObject(pfont);//選入字體
CPen pen;
pen.CreatePen(PS_SOLID, 1, RGB(0xBD, 0xBA, 0xBD));
CPen* pOldPen = cDC.SelectObject(&pen);//選入畫筆
CBrush br(0x00f2f2f2);
for ( int i = 0; i < m_nCount; i++ )
{
GetItemRect (i, rcItem);
//填充面板背景
cDC.FillRect(rcItem, &br);
rcItem.bottom--;
if(i == 0) rcItem.left += 2;
//對(duì)每個(gè)面板畫圓角矩形
cDC.RoundRect(rcItem, CPoint(5, 5));
//畫面板上的文字
UINT nNewStyle = GetPaneStyle(i);
//如果style為SBPS_DISABLED,則跳過不畫
if ((nNewStyle & SBPS_DISABLED) != 0) continue;
CString text = GetPaneText(i);
UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_TOP | DT_LEFT;
rcItem.left += 3;
rcItem.top += 3;
cDC.DrawText(text, rcItem, uFormat);
}
if (pfont)
cDC.SelectObject(def_font);//恢復(fù)字體
//畫右下角小標(biāo)志(這里畫了六個(gè)小圓圈)
if (GetStyle() & SBARS_SIZEGRIP)
{
CRect rc;
GetClientRect(&rc);
rc.left = rcItem.right;
rc.right--;
rc.bottom--;
rc.left = rc.right - rc.Width() / 4;
rc.top = rc.bottom - rc.Width();
int w = rc.Width();
rc.top++;
rc.left++;
cDC.SelectObject(GetStockObject(GRAY_BRUSH));
cDC.Ellipse(&rc);
rc.OffsetRect(-w, -w);
cDC.Ellipse(&rc);
rc.OffsetRect(w, 0);
cDC.Ellipse(&rc);
rc.OffsetRect(-w, w);
cDC.Ellipse(&rc);
rc.OffsetRect(-w, 0);
cDC.Ellipse(&rc);
rc.OffsetRect(2 * w, -2 * w);
cDC.Ellipse(&rc);
}
cDC.SelectObject(pOldPen);//恢復(fù)畫筆
}
上面的函數(shù)我們可以多次看到SelectObject的調(diào)用,這就是前面所說的繪圖函數(shù)基本上都是有狀態(tài)的。
這個(gè)狀態(tài)保存在HDC中,而SelectObject則設(shè)置HDC的狀態(tài)。通常稱為選入。
至于注釋中的恢復(fù)是怎么回事呢?這要從CPen CBrush CFont等等說起了,它們是對(duì)GDI對(duì)象的封裝。
GDI對(duì)象通過CreatePen CreateBrush CreateFont等等函數(shù)創(chuàng)建,返回一個(gè)HGDIOBJ。
這些對(duì)象不使用的時(shí)候需要銷毀,用DeleteObject函數(shù),但是如果一個(gè)HGDIOBJ被選入到一個(gè)HDC中的時(shí)候,
它就不能被銷毀,這樣就造成了GDI資源的泄漏。
解決這一問題通常有兩種做法:
第一種,就是上面代碼中看到的:
先保存原來的HGDIOBJ,def_font = cDC.SelectObject(pfont);
用完了之后再恢復(fù)原來的 cDC.SelectObject(def_font);
這樣做,就保證了pfont能被正確銷毀,至于原來的def_font能不能被銷毀,就不關(guān)我們的事了。
第二種,利用了系統(tǒng)的庫(kù)存對(duì)象。庫(kù)存GDI對(duì)象是windows系統(tǒng)預(yù)先創(chuàng)建的,不需要應(yīng)用程序銷毀。
所以,不需要保存原來的HGDIOBJ,直接像這樣
SelectObject (hdc, ::GetStockObject (NULL_BRUSH));
或者cDC.SelectStockObject(NULL_BRUSH);
就可以保證HDC中沒有被選入任何我們自己創(chuàng)建的畫刷了。
這兩種方法各有好處,視情況選用。
另外上面說大部分GDI函數(shù)都是有狀態(tài)的,有一個(gè)例外就是FillRect函數(shù),它靠一個(gè)傳給他的畫刷進(jìn)行填充。
實(shí)例講述完畢,接下來有一些補(bǔ)充技巧:
1. GDI繪圖技巧的學(xué)習(xí):通過閱讀、運(yùn)行、調(diào)試別人源代碼獲得經(jīng)驗(yàn)這條路徑是最快的。
2.GDI程序的調(diào)試
調(diào)試GDI一般來說比其他程序困難,但是掌握了一些技巧也就沒什么障礙了。
調(diào)試GDI的時(shí)候,將IDE和代調(diào)試的程序窗口在桌面上盡量分開排列,不要重疊在一起。
這樣你能通過單步執(zhí)行,看到每一步的繪圖效果。
為配合上述策略,在應(yīng)用程序初始化的時(shí)候加上下面一句:
#ifdef _DEBUG
GdiSetBatchLimit(1);
#endif
這能保證調(diào)試時(shí)每一條GDI函數(shù)調(diào)用能馬上產(chǎn)生效果。因?yàn)閃indows為了性能優(yōu)化,可能會(huì)分批處理GDI調(diào)用。
3.內(nèi)存繪圖
首先理解內(nèi)存繪圖,即把要繪制的東西先在內(nèi)存中畫好,然后一次性的畫到屏幕上來。內(nèi)存繪圖經(jīng)常用來防止閃爍。
因?yàn)殚W爍的原因是因?yàn)榉床钐蟆@缒愕睦L圖過程是先用白色擦除整個(gè)窗口,然后再將黑色的文字畫到屏幕上來,
這樣在窗口重繪的時(shí)候,原本黑色文字區(qū)域就會(huì)白光一閃,然后再出現(xiàn)文字,也就是我們說的閃爍了。
而內(nèi)存繪圖的過程呢,是先創(chuàng)建一個(gè)內(nèi)存DC,然后在這個(gè)DC上把要繪制的圖形畫好,之后一次性的填到屏幕上去。
示例代碼如下:
HDC hDestDC;
RECT rc;
//..此處得到目標(biāo)的HDC和目標(biāo)的RECT
HDC hdc = ::CreateCompatibleDC (hDestDC);
HBITMAP hBitmap = ::CreateCompatibleBitmap (hDestDC, rc.right, rc.bottom);
HBITMAP hOldBitmap = ::SelectObject (hDC, hBitmap);
//... 此處用hdc進(jìn)行繪圖
//...
::BitBlt (m_hDestDC, rc.left, rc.top, rc.Width(), rc.Height(), hDC, rc.left, rc.top, SRCCOPY);
::SelectObject (hDC, hOldBitmap);
當(dāng)然,這樣用起來不太方便,可以將這些操作封裝到一個(gè)叫CMemDC的對(duì)象中,利用構(gòu)造和析構(gòu)自動(dòng)進(jìn)行這些操作。
直接使用CMemDC還有一個(gè)好處,調(diào)試GDI時(shí),如果圖形都在內(nèi)存中繪制,那么還是看不到繪圖過程。
代碼如果這樣寫:
CRect rc;
GetWindowRect(&rc);
#ifdef _DEBUG
CPaintDC dc;
#else
CPaintDC cdc;
CMemDC dc(cdc.m_hDC, &rc);
#endif
那么就既能享受內(nèi)存繪圖的好處又能方便調(diào)試了。
入門篇先寫到這里,以后有工夫再寫進(jìn)階篇
?
?
?
總結(jié)
以上是生活随笔為你收集整理的原创]Windows Gdi入门初级应用(VC SDK)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [2DPIC调试笔记]parameter
- 下一篇: [2dPIC调试笔记]输入参数归一化10