Windows Gdi 應用-入門篇 (VC SDK)

一、基礎
  GDI的繪圖函數基本上都是有狀態的,所有的函數都要求一個HDC類型的句柄。這個HDC的獲得有幾個途徑BeginPaint,GetWindowDC,GetDC.他們的參數都只需要一個HWND就差不多了。記得調用了BeginPaint後要調用EndPaint進行清理,調用GetWindowDC和GetDC後要調ReleaseDC進行清理。在MFC代碼中常常遇到的CDCCPaintDCCWindowDCCClientDC。在這裏稍作解釋。

  CDC:例如用GDI畫矩形要Rectangle(hDC,...),而使用CDC則是dc.Rectangle(...),由此可見CDC主要是把原本需要HDC作爲參數的GDI函數封裝了一下,HDC成了它的一個成員變量。

  CPaintDCCWindowDCCClientDC:他們都是從CDC繼承,分別是對上面所說的BeginPaint,GetWindowDC,GetDC調用對進行封裝(CPaintDC構造時調用BeginPaint,析構時調用EndPaint,其餘同理)。

  BeginPaint:一般用在對WM_PAINT的響應函數中使用

  GetWindowDC:可獲得整個Window的HDC,而GetDC僅能獲得客戶區的HDC,區別就在於--

  前者有效地繪製區域是整個窗口(邊框、標題欄、客戶區的總和)。
  後者有效地繪製區域僅限於客戶區。

  兩者的座標系都是相對座標而非屏幕座標,原點是(0,0)。即以自己可繪製區域的左上角作爲原點。 

  這裏可以順帶的講講RECT了,RECT是一個結構,依次有4個成員left,top,right,bottom用來代表一個矩形區域。CRect從RECT繼承,提供了一些常用的操作(例如說位移,縮小等等),其實就是改變4個成員的值。完全不用CRect也可以。許多GDI函數都要求一個RECT作爲參數,或者類似的用(x,y,cx,cy)作參數,其實也就是一個RECT變種,用了寬度和高度罷了。

二、實例教程

  基礎知識介紹完畢,開始實例教程:

  我們以如何繪製一個具有平面風格的狀態欄爲例:

  首先從CStatusBar繼承一個類:CStatusBarNew。(如果無法通過類嚮導做這件事,而你又對MFC的MESSAGEMAP等等東西不熟悉,可以從CStatusBarCtrl繼承一個,待生成代碼後,把所有的CStatusBarCtrl改爲CStatusBar)

  在此,只需要重寫WM_PAINT和WM_ERASEBKGND這兩個消息的響應函數。

BOOLCStatusBarNew::OnEraseBkgnd(CDC*pDC)
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
CRectrect;
GetWindowRect(&rect);
ScreenToClient(&rect);
CBrushbrush(0xf2f2f2);
pDC->FillRect(&rect,&brush);
returnTRUE;
}


  上面函數把狀態欄背景用0xf2f2f2這種顏色填充。


voidCStatusBarNew::OnPaint()
{
CPaintDCcDC(this);//devicecontextforpainting
//TODO:Addyourmessagehandlercodehere
CRectrcItem;
cDC.SetBkMode(TRANSPARENT);
cDC.SelectObject(::GetStockObject(NULL_BRUSH));//選入畫刷

//獲取字體
CFont*pfont=GetFont();
CFont*def_font;
if(pfont)
def_font=cDC.SelectObject(pfont);//選入字體

CPenpen;
pen.CreatePen(PS_SOLID,1,RGB(0xBD,0xBA,0xBD));
CPen*pOldPen=cDC.SelectObject(&pen);//選入畫筆

CBrushbr(0x00f2f2f2);
for(inti=0;i{
GetItemRect(i,rcItem);
//填充面板背景
cDC.FillRect(rcItem,&br);
rcItem.bottom--;
if(i==0)rcItem.left =2;

//對每個面板畫圓角矩形
cDC.RoundRect(rcItem,CPoint(5,5));

//畫面板上的文字
UINTnNewStyle=GetPaneStyle(i);
//如果style爲SBPS_DISABLED,則跳過不畫
if((nNewStyle&SBPS_DISABLED)!=0)continue;
CStringtext=GetPaneText(i);
UINTuFormat=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);//恢復字體

//畫右下角小標誌(這裏畫了六個小圓圈)
if(GetStyle()&SBARS_SIZEGRIP)
{
CRectrc;
GetClientRect(&rc);
rc.left=rcItem.right;
rc.right--;
rc.bottom--;
rc.left=rc.right-rc.Width()/4;
rc.top=rc.bottom-rc.Width();
intw=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);//恢復畫筆

}

 



  上面的函數我們可以多次看到SelectObject的調用,這就是前面所說的繪圖函數基本上都是有狀態的。這個狀態保存在HDC中,而SelectObject則設置HDC的狀態。通常稱爲選入。至於註釋中的恢復是怎麼回事呢?這要從CPenCBrushCFont等等說起了,它們是對GDI對象的封裝。GDI對象通過CreatePenCreateBrushCreateFont等等函數創建,返回一個HGDIOBJ。這些對象不使用的時候需要銷燬,用DeleteObject函數,但是如果一個HGDIOBJ被選入到一個HDC中的時候,它就不能被銷燬,這樣就造成了GDI資源的泄漏。解決這一問題通常有兩種做法:

  第一種,就是上面代碼中看到的:

  先保存原來的HGDIOBJ,def_font=cDC.SelectObject(pfont);

  用完了之後再恢復原來的cDC.SelectObject(def_font);

  這樣做,就保證了pfont能被正確銷燬,至於原來的def_font能不能被銷燬,就不關我們的事了。

  第二種,利用了系統的庫存對象。庫存GDI對象是Windows系統預先創建的,不需要應用程序銷燬。所以,不需要保存原來的HGDIOBJ,直接像這樣

  SelectObject(hdc,::GetStockObject(NULL_BRUSH));

  或者cDC.SelectStockObject(NULL_BRUSH);



  就可以保證HDC中沒有被選入任何我們自己創建的畫刷了。

  這兩種方法各有好處,視情況選用。

  另外上面說大部分GDI函數都是有狀態的,有一個例外就是FillRect函數,它靠一個傳給他的畫刷進行填充。

三、技巧

  實例講述完畢,接下來有一些補充技巧:

  1.GDI繪圖技巧的學習:通過閱讀、運行、調試別人源代碼獲得經驗這條路徑是最快的。

  2.GDI程序的調試

  調試GDI一般來說比其他程序困難,但是掌握了一些技巧也就沒什麼障礙了。調試GDI的時候,將IDE和代調試的程序窗口在桌面上儘量分開排列,不要重疊在一起。這樣你能通過單步執行,看到每一步的繪圖效果。

  爲配合上述策略,在應用程序初始化的時候加上下面一句:

  #ifdef_DEBUG
  GdiSetBatchLimit(1);
  #endif

  這能保證調試時每一條GDI函數調用能馬上產生效果。因爲Windows爲了性能優化,可能會分批處理GDI調用。

  3.內存繪圖

  首先理解內存繪圖,即把要繪製的東西先在內存中畫好,然後一次性的畫到屏幕上來。內存繪圖經常用來防止閃爍。因爲閃爍的原因是因爲反差太大。例如你的繪圖過程是先用白色擦除整個窗口,然後再將黑色的文字畫到屏幕上來,這樣在窗口重繪的時候,原本黑色文字區域就會白光一閃,然後再出現文字,也就是我們說的閃爍了。而內存繪圖的過程呢,是先創建一個內存DC,然後在這個DC上把要繪製的圖形畫好,之後一次性的填到屏幕上去。

      示例代碼如下:


HDChDestDC;
RECTrc;
//..此處得到目標的HDC和目標的RECT
HDChdc=::CreateCompatibleDC(hDestDC);
HBITMAPhBitmap=::CreateCompatibleBitmap(hDestDC,rc.right,rc.bottom);
HBITMAPhOldBitmap=::SelectObject(hDC,hBitmap);
//...此處用hdc進行繪圖
//...
::BitBlt(m_hDestDC,rc.left,rc.top,rc.Width(),rc.Height(),hDC,rc.left,rc.top,SRCCOPY);
::SelectObject(hDC,hOldBitmap);

 



  當然,這樣用起來不太方便,可以將這些操作封裝到一個叫CMemDC的對象中,利用構造和析構自動進行這些操作。直接使用CMemDC還有一個好處,調試GDI時,如果圖形都在內存中繪製,那麼還是看不到繪圖過程。

  代碼如果這樣寫:

CRectrc;
GetWindowRect(&rc);
#ifdef_DEBUG
CPaintDCdc;
#else
CPaintDCcdc;
CMemDCdc(cdc.m_hDC,&rc);
#endif

 


  那麼就既能享受內存繪圖的好處又能方便調試了。

  入門篇先寫到這裏,以後有工夫再寫進階篇。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章