显示器是由许多应用程序填充的,所以如何合理使用这一资源是至关重要的。有两种极端情况,一种是你的显示区域不够显示,一种是够显示但非常的多余,资源浪费。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等类型的错误。