您的位置:首页 > 其它

[学习笔记]Windows程序设计:第四章 文本输出

2013-01-12 21:06 375 查看

一、绘制和重绘

        1、有效矩形和无效矩形

      
在Windows中,我们能在窗口的客户区绘制文本和图形,而当窗口的客户区被变动时,Windows不能保留原来绘制的文本和图形,如果要恢复原来的文本和图形就必须进行重新绘制。Windows通过发送WM_PAINT消息来通知窗口过程其客户区需要重绘。大多数Windows程序在WinMain函数初始化过程中会在进入消息循环之前调用UpdateWindow函数。这是Windos会向窗口过程发送最初的WM_PAINT消息。
      
(1)、在以下任何一个事件发生时,窗口过程都会收到一条WM_PAINT消息:
      
◆ 在用户移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见。
      
◆ 用户调整窗口的大小(当窗口类样式设定为CS_HREDRAW和CS_VREDRAW值时)。
      
◆ 程序调用ScrollWindow或ScrollDC函数滚动客户区。
      
◆ 程序调用InvalidateRect或InvalidateRgn函数显示产生WM_PAINT消息。
      
(2)、在某些情况下,当客户区的一部分被临时覆盖时,Windows会试图保存被覆盖部分,从便将来恢复时使用,但这不一定能成功。所以在以下情况下,Windows可能会发送WM_PAINT消息:
      
◆ Windows关闭一个覆盖了部分窗口的对话框或消息框。
      
◆ 下拉菜单弹出,然后被释放。
      
◆ 显示提示信息。
      
(3)、在某些情况下,Windows总是会保存被覆盖部分的显示内容,然后再恢复。这些情况如下:
      
◆ 鼠标指针在客户区内移动。
      
◆ 在客户区内拖动图标。
      
尽管窗口消息处理程序在接收到WM_PAINT消息之后,就准备更新整个客户区,但通常只需要更新一部分(最常见的是一个矩形区域)。需要重新绘制的部分被称为“无效区域”或“更新区域”。在客户区中有一个无效区域将导致Windows在应用程序的消息队列中放置一条WM_PAINT消息。只有当程序客户区的一部分失效时,窗口过程才会接收到WM_PAINT消息。
      
Windows内部为每一个窗口都保存了一个“绘制信息结构”(PAINTSTRUCT)。这个结构保存着一个可以覆盖该无效区域的最小矩形的坐标和一些其他的信息。这个最小矩形被称为“无效矩形”(rcPaint)。如果在窗口过程处理一条等候处理的WM_PAINT消息之前,客户区中的另外一部分也失效了,那么Windows将计算一个覆盖这两个失效部分的新的无效区域和无效矩形,并更新绘制信息结构中的数据。Windows不会在消息队列中放置多条WM_PAINT消息。当窗口过程收到WM_PAINT消息时,它可以获得无效矩形的坐标。通过调用GetUpdateRect函数也可以获取无效矩形的坐标。
      
窗口过程在处理WM_PAINT消息时,在调用BeginPaint函数后,整个客户区会变成有效的。窗口过程可以通过调用InvalidateRect函数来使客户区中的任意矩形失效。也可以通过调用ValidateRect函数来使客户区中的任意矩形有效。如果该函数的调用使整个客户区都有效,那么当前消息队列中的WM_PAINT消息就会被删除。

        2、绘制信息结构

typedef struct tagPAINTSTRUCT
{
HDC            hdc ;
BOOL           fErase ;
 RECT           rcPaint ;
BOOL           fRestore ;
BOOL           fIncUpdate ;
BYTE           rgbReserved[32] ;
} PAINTSTRUCT ;
      
当程序调用BeginPaint函数时,Windows将自动填充这个结构中的字段。程序只能够使用前三个字段。其他的供Windows内部使用。多数情况下,fErase成员将被设置为FALSE。这意味着Windows在BeginPaint函数中已经擦除了无效区域的背景。(在窗口过程中自定义背景擦除方式需处理WM_ERASEBKGND。)Windows使用WNDCLASS结构中的hbrBackground指定的画刷来擦除背景。rcPaint成员定义了无效矩形的边界(即RECT结构中的left、top、right、bottom)。
      
当程序通过调用InvalidateRect函数使客户区中的一个矩形失效时,则该函数的最后一个参数会指定是否擦除背景。如果这个参数为FALSE,则随后调用的BeginPaint函数将不会擦除背景,在调用完BeginPaint后PAINTSTRUCT结构的fErase成员将为TRUE。

        

二、GDI简介

      
绘制一个窗口的客户区需要调用Windows的图形设备接口(GDI)函数。而所有的GDI函数都需要一个设备环境句柄作为函数的第一个参数。设备环境(DC)是GDI内部维护的一个数据结构。设备环境与特定的显示设备(如显示器或打印机)相关联。对于视频显示,设备环境通常与屏幕上的一个特定的窗口相关联。
      
程序在绘制前必须获取一个设备环境句柄。在获取句柄后,Windows会在内部的设备环境结构中填入默认的属性值。当程序完成了对客户区的绘制后,它必须释放该设备环境句柄。程序必须在处理同一条消息的过程中获取句柄和释放句柄。不能
4000
在两条消息中间传递一个设备环境句柄。唯一的例外是通过调用CreateDC函数创建的设备环境。
      
获取设备环境句柄:方法一
      
窗口过程在处理WM_PAINT消息时必须成对地调用BeginPaint和EndPaint,BeginPaint返回设备环境句柄,EndPaint释放设备环境句柄。如果窗口过程不处理WM_PAINT消息,WM_PAINT消息会被传送给Windows默认的窗口过程DefWindowProc,在DefWindowProc中,WM_PAINT消息处理的代码如下:
case WM_PAINT:
BeginPaint (hwnd, &ps);
EndPiant (hwnd,&ps);
return 0;
      
以下代码是错误的:
case WM_PAINT:
BeginPaint (hwnd, &ps);
EndPiant (hwnd,&ps);
return 0;
      
由于部分客户区无效,Windows会在消息队列中放置一条WM_PAINT消息。除非调用BeginPaint和EndPiant函数对(或ValidateRect),否则Windows不会将那个区域有效化,因此,Windows将会不停地发送WM_PAINT消息。
      
获取设备环境句柄:方法二
      
成对使用GetDC和ReleaseDC,与BeginPiant函数返回的设备环境句柄不同,从GetDC返回的设备环境句柄中的裁剪矩形是整个客户区,GetDC不会将无效区域有效化。需使用ValidateRECT函数将整个客户区有效化。
      
另一个与GetDC类似的函数是GetWindowDC。GetWindowDC返回的是整个窗口的设备环境句柄。对应的消息为WM_NCPAINT(非客户区绘制)消息。

三、文本输出

       1、文本尺寸

      
系统的字体尺寸取决于Windows运行时的显示分辨率和选定的系统字号,而Windows提供的是与设备无关的图形界面,所以应该通过调用GetTextMetrics函数来获取这些信息。用户界面的尺寸可以通过调用GetSystemMetrics函数来获取。Windows运行时系统字体不会变化。因此,只需调用一次GetTextMetrics函数。最好的时机是在窗口过程处理WM_CREATE消息时。
case   WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;  //小写字符的加权平均宽度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;  //大写字符的平均宽度,通常为tmAveCharWidth的1.5倍
cyChar = tm.tmHeight + tm.tmExternalLeading ;  //字符高度和两行文字间的间隔
ReleaseDC (hwnd, hdc) ;
return 0 ;
      
在窗口中,每一行文本显示在上一行文本下方cyChar个像素处。tmPitchAndFamily成员的低位决定字体是否为等宽字体:1代表变宽,0代表等宽。

       2、客户区尺寸

      
当客户区尺寸发生变化时,可以通过调用GetClinetRECT函数来获取客户区大小,但每次需要时都要调用这个函数显然没有效率。更好的办法是在窗口过程处理WM_SIZE消息时获取窗口的客户区的大小。当窗口的大小发生变化时,Windos会向窗口过程发送一条WM_SIZE消息。相应的lParam变量的低位字是客户区的宽度,高位字是高度。可以如下处理WM_SIZE消息:
caseWM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
      
LOWORD和HIWORD在WINDEF.H中的宏定义如下:
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

四、滚动条

    1、滚动条的维护

      
当需要显示的内容超过窗口的客户区时,可以加入滚动条。方法是在CreateWindow的第三个参数中包括窗口风格标识符WS_VSCROLL和WS_HSCROLL
      
在程序中使用滚动条时,程序需要和Windows共同负责维护滚动条以及滑块在滚动条中的位置。对滚动条的处理Windows负责如下任务:
      
◆ 处理滚动条中的所有鼠标消息
      
◆ 当用户单击滚动条时,提供一种「反向显示」的闪烁
      
◆ 当用户拖动滑块时,在滚动条内移动滑块 
      
◆ 向拥有滚动条的窗口的窗口过程发送滚动条消息 
      
程序负责如下任务:
      
◆ 初始化滚动条的范围和位置
      
◆ 处理传递给窗口过程的滚动条消息
      
◆ 更新滑块的位置
      
◆ 根据滚动条的变化更新客户区的内容

    2、滚动条消息

      
当用户单击滚动条或拖动滑块时,Windows向窗口过程发送WM_VSCROLL消息或WM_HSCROLL消息。当滚动条是窗口的一部分时,可以忽略lParam参数:它只用于滚动条是子窗口时,通常是在对话框中。wParam的低位字代表了鼠标在滚动条上的动作。这个值被称为“通知码”。下面是在WINUSER.H中定义的通知码:
   #define SB_LINEUP              0
#define SB_LINELEFT            0
#define SB_LINEDOWN            1
#define SB_LINERIGHT           1
#define SB_PAGEUP              2
#define SB_PAGELEFT            2
#define SB_PAGEDOWN            3
#define SB_PAGERIGHT           3
#define SB_THUMBPOSITION       4
#define SB_THUMBTRACK          5
#define SB_TOP                 6
#define SB_LEFT                6
#define SB_BOTTOM              7
#define SB_RIGHT               7
#define SB_ENDSCROLL           8
      
将鼠标放在滑块上然后按下鼠标键时,可以移动滑动。这将会生成带SB_THUMBTRACK和SB_THUMBPOSITION通知码的消息。当wParam的低位字是SB_THUMBTRACK时,则需要在用户拖动滑块时移动客户区的内容,wParam的高位字是用户拖动滑块的当前位置。当wParam的低位字是SB_THUMBPOSITION时,则只需要在用户停止拖动滑块时更新客户区的内容,wParam的高位字是用户松开鼠标时滑块的最终位置。

    3、SYSMETS3的窗口过程函数代码

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;
HDC hdc ;
int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
PAINTSTRUCT ps ;
SCROLLINFO si ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
// Save the width of the three columns
iMaxWidth = 40 * cxChar + 22 * cxCaps ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// Set vertical scroll bar range and page size
si.cbSize = sizeof (si) ;
si.fMask = SIF_RANGE | SIF_PAGE ;
si.nMin = 0 ;
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
// Set horizontal scroll bar range and page size
si.cbSize = sizeof (si) ;
si.fMask = SIF_RANGE | SIF_PAGE ;
si.nMin = 0 ;
si.nMax = 2 + iMaxWidth / cxChar ;
si.nPage = cxClient / cxChar ;
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
return 0 ;
case WM_VSCROLL:
// Get all the vertical scroll bar information
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// Save the position for comparison later on
iVertPos = si.nPos ;
switch (LOWORD (wParam))
{
case SB_TOP:
si.nPos = si.nMin ;
break ;
case SB_BOTTOM:
si.nPos = si.nMax ;
break ;
case SB_LINEUP:
si.nPos - = 1 ;
break ;
case SB_LINEDOWN:
si.nPos += 1 ;
break ;
case SB_PAGEUP:
si.nPos -= si.nPage ;
break ;
case SB_PAGEDOWN:
si.nPos += si.nPage ;
break ;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos ;
break ;
default:
break ;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// If the position has changed, scroll the window and update it
if (si.nPos != iVertPos)
{
ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
NULL, NULL) ;
UpdateWindow (hwnd) ;
}
return 0 ;
case WM_HSCROLL:
// Get all the vertical scroll bar information
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
// Save the position for comparison later on
GetScrollInfo (hwnd, SB_HORZ, &si) ;
iHorzPos = si.nPos ;
switch (LOWORD (wParam))
{
case SB_LINELEFT:
si.nPos -= 1 ;
break ;
case SB_LINERIGHT:
si.nPos += 1 ;
break ;
case SB_PAGELEFT:
si.nPos -= si.nPage ;
break ;
case SB_PAGERIGHT:
si.nPos += si.nPage ;
break ;
case SB_THUMBPOSITION:
si.nPos = si.nTrackPos ;
break ;
default :
break ;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
GetScrollInfo (hwnd, SB_HORZ, &si) ;
// If the position has changed, scroll the window
if (si.nPos != iHorzPos)
{
ScrollWindow (hwnd, cxChar * (iHorzPos - si.nPos), 0,
NULL, NULL) ;
}
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
// Get vertical scroll bar position
si.cbSize = sizeof (si) ;
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
iVertPos = si.nPos ;
// Get horizontal scroll bar position
GetScrollInfo (hwnd, SB_HORZ, &si) ;
iHorzPos = si.nPos ;
// Find painting limits
iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
iPaintEnd = min ( NUMLINES - 1,
iVertPos + ps.rcPaint.bottom / cyChar) ;
for (i = iPaintBeg ; i <= iPaintEnd ; i++)
{
x = cxChar * (1 - iHorzPos) ;
y = cyChar * (i - iVertPos) ;
TextOut (hdc, x, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, x + 22 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
      
这里我们依赖Windows来维护滚动条信息和做边界检查。在处理WM_VSCROLL和WM_HSCROLL时,首先获取滚动条信息,根据通知码调整位置,然后调用SetScrollInfo函数设置位置。程序然后调用GetScrollInfo。如果在调用SetScrollInfo时位置超出了范围,Windows将自动修正位置,并通过GetScrollInfo调用返回正确的位置。
      
使用ScrollWindow函数来滚动窗口客户区的内容,而不是重绘。最后两个参数设为NULL,表示需要滚动整个客户区。Windows自动将新滚动出现的地方无效化,从而产生一条WM_PAINT消息。这里并不需要调用InvalidateRect函数。注意ScrollWindow并不是GDI函数,这是少数几个能够改变窗口的客户区显示的非GDI函数之一。

9572
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: