一、 基礎
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...