原創]Windows Gdi入門初級應用(VC SDK)

來源:互聯網
上載者:User

原創]Windows Gdi入門初級應用(VC SDK) 

 
好久沒發貼了,今天手痒痒,發一個。
GDI的繪圖函數基本上都是有狀態的,所有的函數都要求一個HDC類型的控制代碼。
這個HDC的獲得有幾個途徑BeginPaint,GetWindowDC, GetDC.他們的參數都只需要一個HWND就差不多了。
記得調用了BeginPaint後要調用EndPaint進行清理,調用GetWindowDC和GetDC後要調ReleaseDC進行清理。
在MFC代碼中常常遇到的CDC CPaintDC CWindowDC CClientDC。在這裡稍作解釋。
CDC :例如用GDI畫矩形要Rectangle(hDC,...),而使用CDC則是dc.Rectangle(...),由此可見CDC主要是把原本需要HDC作為參數的GDI函數封裝了一下,HDC成了它的一個成員變數。
CPaintDC CWindowDC CClientDC:他們都是從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這兩個訊息的響應函數。
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;
}
這個函數把狀態列背景用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;

//對每個面板畫圓角矩形
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);//恢複字型

//畫右下角小標誌(這裡畫了六個小圓圈)
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);//恢複畫筆

}
上面的函數我們可以多次看到SelectObject的調用,這就是前面所說的繪圖函數基本上都是有狀態的。
這個狀態儲存在HDC中,而SelectObject則設定HDC的狀態。通常稱為選入。
至於注釋中的恢複是怎麼回事呢?這要從CPen CBrush CFont等等說起了,它們是對GDI對象的封裝。
GDI對象通過CreatePen CreateBrush CreateFont等等函數建立,返回一個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上把要繪製的圖形畫好,之後一次性的填到螢幕上去。
範例程式碼如下:
HDC hDestDC;
RECT rc;
//..此處得到目標的HDC和目標的RECT
HDC hdc = ::CreateCompatibleDC (hDestDC);
HBITMAP hBitmap = ::CreateCompatibleBitmap (hDestDC, rc.right, rc.bottom);
HBITMAP hOldBitmap = ::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時,如果圖形都在記憶體中繪製,那麼還是看不到繪圖過程。
代碼如果這樣寫:
CRect rc;
GetWindowRect(&rc);
#ifdef _DEBUG
CPaintDC dc;
#else
CPaintDC cdc;
CMemDC dc(cdc.m_hDC, &rc);
#endif
那麼就既能享受記憶體繪圖的好處又能方便調試了。

入門篇先寫到這裡,以後有工夫再寫進階篇
 
 
 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.