Windows GDI應用 入門篇

2021-04-08 18:35:31 字數 3545 閱讀 4900

一、 基礎

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)

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

void cstatusbarnew::onpaint()

if (pfont)

cdc.selectobject(def_font);//恢復字型

//畫右下角小標誌(這裡畫了六個小圓圈)

if (getstyle() & sbars_sizegrip)

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函式,它靠乙個傳給他的畫刷進行填充。

三、 技巧

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

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

正則 入門篇

如果你對正則感興趣,讀完這篇文章,一定會有收穫 寫好正規表示式的兩個要點 1.正確匹配字元數量 相關符號 2.正確匹配字元種類 相關符號 除上面符號以外的其它符號 相關符號 代表 萬能匹配 可以匹配除了 n 換行符 之外的任何單個字元 代表 不要貪婪 用在 後面,表示匹配的越少越好 也代表 非 即一...

正則 入門篇

如果你對正則感興趣,讀完這篇文章,一定會有收穫 代表 萬能匹配 可以匹配除了 n 換行符 之外的任何單個字元 代表 不要貪婪 用在 後面,表示匹配的越少越好 也代表 非 即一 代表 除了你 在中括號內,如 表示單個非下劃線字元 也代表 匹配首位 代表 匹配末位 代表 至無窮 大於等於0的整數 代表 ...

前端入門篇

作為乙個後端的開發,我其實不想設計到前端的開發。雖然都會是不錯的體驗,但是術業有專攻,還是揚長避短才能成就部分期待。但是沒有什麼能獨善其身,一方面環境使然,一方面前端的良好的效果能促進完成更好的產品,更加符合自己期待的東西,所以開始吧 前端目前的框架主流是vue react angular 推薦re...