顯示器是由許多應用程序填充的,所以如何合理使用這一資源是至關重要的。有兩種極端情況,一種是你的顯示區域不夠顯示,一種是夠顯示但非常的多余,資源浪費。Windows程序只能對顯示區域大小甚至字符的大小做很少的假定,必須使用Windows提供的功能來取得關于程序執行環境的信息。關于重新繪制在書中講了許多,那是講給從dos時代走過來的人的,我用慣了xp的人,很容易明白,顯示區域是充滿意外的,我們不停移動切換著各個窗口,這時必然要重新繪制。重繪分三種情況:
繪制整個區域
在使用者移動窗口或顯示窗口時,窗口中先前被隱藏的區域重新可見。 使用者改變窗口的大小(如果窗口類別樣式有著CS_HREDRAW和CS_VREDRAW位旗標的設定)。 程序使用ScrollWindow或ScrollDC函數滾動顯示區域的一部分。 程序使用InvalidateRect或InvalidateRgn函數刻意產生WM_PAINT消息。 |
繪制覆蓋區域
鼠標光標穿越顯示區域。 圖標拖過顯示區域。 |
均可能發生
Windows擦除覆蓋了部分窗口的對話框或消息框。 菜單下拉出來,然后被釋放。 顯示工具提示消息 |
關于無效區域和無效矩形
無效區域是顯示器上被遮蓋的部分,這部分的圖形是可能不規則的,windows將計算出包圍這個無效區域的最小矩形,該矩形稱為無效矩形。需要強調的是消息循環中只存在一個WM_PAINT,這就要求當出現兩個無效區域時,windows會自動計算包圍兩個無效區域的無效矩形。窗口處理程序收到該消息時會通過GetUpdateRect來獲得坐標信息。在處理WM_PAINT消息處理期間,窗口消息處理程序在呼叫了BeginPaint之后,整個顯示區域即變為有效。程序也可以通過呼叫ValidateRect函數使顯示區域內的任意矩形區域變為有效。變為有效即清除該消息。
獲得設備內容句柄
有兩種方法獲得,第一種就是上一次講述的BeginPaint和EndPaint函數。介紹其中的PAINTSTRUCT數據結構。
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT, *PPAINTSTRUCT; |
Windows自動填充各個屬性。我們只使用前三個屬性。HDC是設備環境句柄,由BeginPaint返回。fErase一般是0,表示windows擦除了無效矩形的背景。擦除用的畫刷就是開始窗口類中hbrBackground設定的備用畫刷。
rcPaint屬性是一個rect變量,保存著如上圖left,right,top,bottom的值,即無效矩形的邊界。想強制更新無效矩形外的區域可以使用如下函數
InvalidateRect (hwnd, NULL, TRUE) ; |
在任何時候使用他將使整個區域變為無效。
另一種方法是通過GetDC()來獲取,使用完后通過RelseaseDC()釋放。
hdc=GetDC(hWnd); GetClientRect(hWnd,&rect); DrawText(hdc,"Hello,Windows XP!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); //EndPaint(hWnd,&ps); ReleaseDC(hWnd,hdc); ValidateRect (hWnd, NULL) ; |
與上一種方法不通的是,這里需要調用ValidateRect (hWnd, NULL)使無效區域有效,如果沒有這一句,會發現屏幕上的字會不停的閃爍,不斷的刷新。與此類似GetWindowDC傳回寫入整個窗口的設備內容句柄。
TextOut函數
這里需要注意的是TextOut(hdc,20,20,str,sizeof(str)/sizeof(char)-1)的最后一個參數,str雖然是一個指針,但是要使用c語言的字符串
char str[]="Hello,Windows XP!"; |
若使用指針指向一個字符串,如函數中的求長度的方法將得出錯誤的結果,因為只為指針開辟了特定的空間大小,因編譯器而異。
深入字體
為了在顯示器上顯示多行文字,可以想象,必須知道字體的高度寬度等信息,這樣才不至于字與字相互覆蓋重疊。
GetSystemMetrics |
各類視覺組件大小 |
GetTextMetrics |
取得字體大小 |
typedef struct tagTEXTMETRIC { LONG tmHeight; LONG tmAscent; LONG tmDescent; LONG tmInternalLeading; //縱向空隙 LONG tmExternalLeading;//橫向空隙 LONG tmAveCharWidth;//小寫字母寬度 LONG tmMaxCharWidth;//大寫字母寬度 LONG tmWeight; LONG tmOverhang; LONG tmDigitizedAspectX; LONG tmDigitizedAspectY; char tmFirstChar; char tmLastChar; char tmDefaultChar; char tmBreakChar; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmPitchAndFamily; BYTE tmCharSet; } TEXTMETRIC; |
通過函數可以填充這樣一個數據結構的變量中的各個屬性。
由圖可知
TmHeight=tmAscent+tmDescent
大寫字母平均寬度=tmMaxCharWith*1.5= (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
有了上述信息,我們就可以指定我們想要的字體了。字體的設定可以在WM_CREATE消息處理時指定。例如
case WM_CREATE: hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; return 0 ; |
完成這些后就可以格式化輸出了,我們需要使用wsprintf函數,將格式化內容放入字符數組內,該函數返回的是字符串長度,正好給TextOut函數使用。
綜合例子
Windows.H文件
//=========================== // (c)狗尾草 2008.1.19 //=========================== #include<tchar.h> #include<windows.h> #define LINENUMBERS ((int)(sizeof(sysmetrics)/sizeof(sysmetrics[0]))) struct { int index; TCHAR* szLable; TCHAR* szDesc; } sysmetrics[]= { SM_CXSCREEN,"SM_CXSCREEN","窗口寬像素", SM_CYSCREEN,"SM_CYSCREEN","窗口高像素", SM_CXVSCROLL,"SM_CXVSCROLL","垂直滾動寬度", SM_CYHSCROLL,"SM_CYHSCROLL","水平滾動高度", 。。。。。。 }; |
書上將windows.H文件放到system.C文件中,這樣會發生錯誤,因為像SM_CXSCREEN這樣的常量是在windows.H文件中的,所以如果該頭文件不包含,編譯器將提示未定義。TCHAR類型意味著要包含tchar.h。這也是原文忽略的。
system.C文件只列出消息處理函數如下
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { static int cxChar,cyChar,cxCaps; int i; HDC hdc; PAINTSTRUCT ps; RECT rect; //char str[]="你好,Windows XP!"; TCHAR szBuffer[10]; TEXTMETRIC tm; switch(message) { case WM_CREATE: hdc=GetDC(hWnd); GetTextMetrics(hdc,&tm); cxChar=tm.tmAveCharWidth; cyChar=tm.tmHeight+tm.tmExternalLeading; cxCaps=(tm.tmPitchAndFamily&1?3:2)*cxChar/2; ReleaseDC(hWnd,hdc); return 0; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); //hdc=GetDC(hWnd); //GetClientRect(hWnd,&rect); //DrawText(hdc,"Hello,Windows XP!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); for(i=0;i<LINENUMBERS;i++) { TextOut(hdc,0,cyChar*i,sysmetrics[i].szLable,lstrlen(sysmetrics[i].szLable)); TextOut(hdc,20*cxCaps,cyChar*i,sysmetrics[i].szDesc,lstrlen(sysmetrics[i].szDesc)); SetTextAlign(hdc,TA_RIGHT|TA_TOP);//右對齊方式,顯示數字 TextOut (hdc,22*cxCaps+40*cxChar,cyChar*i,szBuffer,wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].index))); SetTextAlign(hdc,TA_LEFT|TA_TOP);//改回來哦 } //TextOut(hdc,20,20,str,sizeof(str)/sizeof(char)-1); EndPaint(hWnd,&ps); //ReleaseDC(hWnd,hdc); //ValidateRect (hWnd, NULL) ; return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd,message,wParam,lParam); } |
當我們寫完這寫代碼時,本意味程序就可以正常運行了,但是意外發生了,
//SM_MOUSEWHEELPRESENT,TEXT ("SM_MOUSEWHEELPRESENT"), TEXT ("Mouse wheel present flag"), //SM_XVIRTUALSCREEN, TEXT ("SM_XVIRTUALSCREEN"), TEXT ("Virtual screen x origin"), //SM_YVIRTUALSCREEN, TEXT ("SM_YVIRTUALSCREEN"), TEXT ("Virtual screen y origin"), //SM_CXVIRTUALSCREEN, TEXT ("SM_CXVIRTUALSCREEN"), TEXT ("Virtual screen width"), //SM_CYVIRTUALSCREEN, TEXT ("SM_CYVIRTUALSCREEN"),TEXT ("Virtual screen height"), //SM_CMONITORS, TEXT ("SM_CMONITORS"), TEXT ("Number of monitors"), //SM_SAMEDISPLAYFORMAT,TEXT ("SM_SAMEDISPLAYFORMAT"),TEXT ("Same color format flag") |
這些常量在VC6的windows.H中居然沒有包含,在用devcpp編譯,OK,非常成功的通過了,我拷貝devcpp中的頭文件到VC6中,結果還是不成,會在windef或者winicon等文件中出現編譯錯誤。我想這一定和頭文件的版本有關。用VC2005就沒有問題了,不過在使用VC2005時會出現很多類型錯誤,TCHAR等類型的錯誤。