顯示器是由許多應用程序填充的,所以如何合理使用這一資源是至關重要的。有兩種極端情況,一種是你的顯示區域不夠顯示,一種是夠顯示但非常的多余,資源浪費。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等類型的錯誤。