Window GDI入門初階

2021-04-09 08:51:44 字數 3765 閱讀 7552

好久沒發貼了,今天手癢癢,也發乙個。

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

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

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

那麼就既能享受記憶體繪圖的好處又能方便除錯了。

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

C語言初階入門(一)

就是將各種資料變數轉換成可以被計算機識別的二進位制語言。那麼怎麼使用呢?舉個例子 int a,b 方法一 int a 0 int b 0 方法二 在定義變數的資料型別時,上述兩種方法皆可使用。值得強調的是,方法二雖然比方法一更加繁瑣,但是從工程上講,方法二更加便於後期的維護與檢索。這裡可以通過乙個s...

C 模板初階

template typename t void swap t left,t right intmain 在編譯器編譯階段,對於模板函式的使用,編譯器需要根據傳入的實參型別來推演生成對應型別的函式以供呼叫。比如 當用double型別使用函式模板時,編譯器通過對實參型別的推演,將t確定為double型...

Cpp 模板初階

title 模板初階 date 2019 03 18 20 37 56 tags cpp categories cpp toc true 告訴編譯器乙個模子,讓編譯器根據不同的型別利用該模子來生成 泛型程式設計 編寫與型別無關的通用 是 復用的一種手段。模板是泛型程式設計的基礎。函式模板代表了乙個函...