第四章 输出文字
1.绘制与更新
当程式在显示区域显示文字或图形时,它经常要「绘制」它的显示区域。在Windows中,只能在视窗的显示区域绘制文字和图形,Windows是一个讯息驱动系统。它通过把讯息投入应用程式讯息伫列中或者把讯息发送给合适的视窗讯息处理程式,将发生的各种事件通知给应用程式。Windows通过发送WM_PAINT讯息通知视窗讯息处理程式,视窗的部分显示区域需要绘制。
1.1 WM_PAINT讯息
大多数Windows程式在WinMain中进入讯息回圈之前的初始化期间都要呼叫函式UpdateWindow。Windows利用这个机会给视窗讯息处理程式发送第一个WM_PAINT讯息。这个讯息通知视窗讯息处理程式:必须绘制显示区域。此後,视窗讯息处理程式应在任何时刻都准备好处理其他WM_PAINT讯息,必要的话,甚至重新绘制视窗的整个显示区域。
1.2 有效矩形和无效矩形
只有在显示区域的某一部分失效时,视窗才会接受WM_PAINT讯息。Windows内部为每个视窗保存一个「绘图资讯结构」,这个结构包含了包围无效区域的最小矩形的座标以及其他资讯,在处理WM_PAINT讯息处理期间,视窗讯息处理程式在呼叫了BeginPaint之後,整个显示区域即变为有效。
2 GDI(图形装置介面)简介
GDI函数
TextOut (hdc, x, y, psText, iLength) ;
装置内容代号,开始位置,字符串指针,字串长度
2.1 装置内容
装置内容(简称为「DC」)实际上是GDI内部保存的资料结构。装置内容中的有些值是图形「属性」,这些属性定义了GDI绘图函式工作的细节。
当程式需要绘图时,它必须先取得装置内容代号。在取得了该代号後,Windows用内定的属性值填入内部装置内容结构。当程式在显示区域绘图完毕後,它必须释放装置内容代号。代号被程式释放後就不再有效,且不能再被使用。
2.2 取得装置内容代号的方法
1)在处理WM_PAINT讯息时,使用BeginPaint和EndPaint两个函数,这两个函数需要视窗代号(hwnd)和PAINTSTRUCT结构的变量的地址为参数。
PAINTSTRUCT ps; //绘图咨询结构
----------------
typedef struct tagPAINTSTRUCT
{
HDC hdc;
BOOL fErase; //被标志为FALSE(0),这意味著Windows已经擦除了无效矩形的背景
RECT rcPaint; //RECT结构,RECT结构定义了一个矩形,其四个栏位为left、top、right和bottom,无效矩形
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
-----------------
一般地,处理WM_PAINT讯息的形式如下:
---------------------------------------------------
HDC hdc; //装置内容代号
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
使用GDI //上下函数必须存在,否则区域无效,一直执行WM_PAINT讯息。
EndPaint(hwnd, &ps);
return 0;
---------------------------------------------------
2)虽然最好是在处理WM_PAINT讯息处理期间更新整个显示区域,但是您也会发现在处理非WM_PAINT讯息处理期间绘制显示区域的某个部分也是非常有用的。或者您需要将装置内容代号用於其他目的,如取得装置内容的资讯。要得到视窗显示区域的装置内容代号,可以呼叫GetDC来取得代号,在使用完後呼叫ReleaseDC:
--------------------------
hdc = GetDC (hwnd) ;
使用GDI函式
ReleaseDC (hwnd, hdc) ;
--------------------------
GetDC传回的装置内容代号具有一个剪取矩形,它等於整个显示区域。
3.文字
3.1 TextOut函数
----------------------------------------------------------------------------
TextOut (hdc, //装置内容代号,装置内容的属性控制了被显示字串的特徵,
x, //水平初始位置
y, //垂直初始位置
psText, //指向字串的指针
iLength) ; //字元个数
----------------------------------------------------------------------------
3.2 字体大小
程式可以呼叫GetSystemMetrics函式以取使用者介面上各类视觉元件大小的资讯,呼叫GetTextMetrics取得字体大小。
要使用GetTextMetrics函式,需要先定义一个结构变数(通常称为tm): TEXTMETRIC tm ;
------------------------------------------
typedef struct tagTEXTMETRIC
{
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
……
}
------------------------------------
在需要确定文字大小时,先取得装置内容代号,再呼叫GetTextMetrics:
-----------------------------
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
ReleaseDC (hwnd, hdc) ;
-----------------------------
Windows启动後,系统字体的大小就不会发生改变,所以在程式执行过程中,程式写作者只需要呼叫一次GetTexMetrics。最好是在视窗讯息处理程式中处理WM_CREATE讯息时进行此呼叫,WM_CREATE讯息是视窗讯息处理程式接收的第一个讯息。
3.3 取得显示区域大小
确定视窗显示区域大小的更好方法是在视窗讯息处理程式中处理WM_SIZE讯息。在视窗大小改变时,Windows给视窗讯息处理程式发送一个WM_SIZE讯息。传给视窗讯息处理程式的lParam参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。
-----------------------------------
static int cxClient, cyClient;
……
case WM_SIZE:
cxClient = LOWORD (lParam);
cyClient = HIWORD (lParam);
return 0;
------------------------------------
4 卷动列
很容易在应用程式中包含水平或者垂直的卷动列,程式写作者只需要在CreateWindow的第三个参数中包括视窗样式(WS)识别字WS_VSCROLL(垂直卷动)和/或WS_HSCROLL(水平卷动)即可。Windows负责处理对卷动列的所有滑鼠操作,但是,视窗卷动列没有自动的键盘介面。
在程式内使用卷动列时,程式写作者与Windows共同负责维护卷动列以及更新卷动方块的位置。下面是Windows对卷动列的处理:
* 处理所有卷动列滑鼠事件
* 当使用者在卷动列内单击滑鼠时,提供一种「反相显示」的闪烁
* 当使用者在卷动列内拖动卷动方块时,移动卷动方块
* 为包含卷动列视窗的视窗讯息处理程式发送卷动列讯息
以下是程式写作者应该完成的工作:
* 初始化卷动列的范围和位置
* 处理视窗讯息处理程式的卷动列讯息
* 更新卷动列内卷动方块的位置
* 更改显示区域的内容以回应对卷动列的更改
4.1 卷动列的范围和位置
---------------------------------------------------------------------------
SetScrollRange (hwnd, //
iBar, //iBar为SB_VERT或者SB_HORZ
iMin, //范围的最小值
iMax, //范围的最大值
bRedraw) ; //为TRUE时,Windows根据新范围重画卷动列
---------------------------------------------------------------------------
在卷动列范围内设置新的卷动方块位置:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;//参数iPos是新位置,它必须在iMin至iMax的范围内
提供了类似的函式(GetScrollRange和GetScrollPos)来取得卷动列的目前范围和位置。
4.2 卷动列讯息
在用滑鼠单击卷动列或者拖动卷动方块时,Windows给视窗讯息处理程式发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)讯息。在卷动列上的每个滑鼠动作都至少产生两个讯息,一条在按下滑鼠按钮时产生,一条在释放按钮时产生。
和所有的讯息一样, WM_VSCROLL和WM_HSCROLL也带有wParam和lParam讯息参数。对於来自作为视窗的一部分而建立的卷动列讯息,您可以忽略lParam;它只用于作为子视窗而建立的卷动列(通常在对话方块内)。
wParam讯息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了滑鼠对卷动列进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(卷动列)」)开头的识别字定义。
滑鼠在卷动列的不同区域单击所产生的通知码如图4-7所示。
如果在卷动列的各个部位按住滑鼠键,程式就能收到多个卷动列讯息。当释放滑鼠键後,程式会收到一个带有SB_ENDSCROLL通知码的讯息。一般可以忽略这个讯息,Windows不会去改变卷动方块的位置,而您可以在程式中呼叫SetScrollPos来改变卷动方块的位置。
当把滑鼠的游标放在卷动方块上并按住滑鼠键时,您就可以移动卷动方块。这样就产生了带有SB_THUMBTRACK和SB_THUMBPOSITION通知码的卷动列讯息。在wParam的低字组是SB_THUMBTRACK时,wParam的高字组是使用者在拖动卷动方块时的目前位置。该位置位於卷动列范围的最小值和最大值之间。在wParam的低字组是SB_THUMBPOSITION时,wParam的高字组是使用者释放滑鼠键後卷动方块的最终位置。对於其他的卷动列操作,wParam的高字组应该被忽略。
为了给使用者提供回馈,Windows在您用滑鼠拖动卷动方块时移动它,同时您的程式会收到SB_THUMBTRACK讯息。然而,如果不通过呼叫SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION讯息,在使用者释放滑鼠键後,卷动方块会迅速跳回原来的位置。
程式能够处理SB_THUMBTRACK或SB_THUMBPOSITION讯息,但一般不同时处理两者。如果处理SB_THUMBTRACK讯息,在使用者拖动卷动方块时您需要移动显示区域的内容。而如果处理SB_THUMBPOSITION讯息,则只需在使用者停止拖动卷动方块时移动显示区域的内容。处理SB_THUMBTRACK讯息更好一些(但更困难),对於某些型态的资料,您的程式可能很难跟上产生的讯息。
WINUSER.H表头档案还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出卷动列已经被移到了它的最小或最大位置。然而,对於作为应用程式视窗一部分而建立的卷动列来说,永远不会接收到这些通知码。
在卷动列范围使用32位元的值也是有效的,尽管这不常见。然而,wParam的高字组只有16位元的大小,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函式(在下面描述)来得到资讯。
WndProc视窗讯息处理程式在处理WM_CREATE讯息时增加了两条叙述,以设置垂直卷动列的范围和初始位置:
----------------------------
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
----------------------------
4.3 更好函数
SetScrollInfo (hwnd, iBar, &si, bRedraw) ;
GetScrollInfo (hwnd, iBar, &si) ;
iBar参数是SB_VERT或SB_HORZ,它还可以是用於卷动列控制的SB_CTL。SetScrollInfo的最後一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新资讯後的卷动列。
两个函式的第三个参数是SCROLLINFO结构,定义为:
------------------------------------------------------------
typedef struct tagSCROLLINFO
{
UINT cbSize ; // set to sizeof (SCROLLINFO)
UINT fMask ; // values to set or get
int nMin ; // minimum range value
int nMax ; // maximum range value
UINT nPage ; // page size
int nPos ; // current position
int nTrackPos ; // current tracking position
}SCROLLINFO, * PSCROLLINFO ;
------------------------------------------------------------
…………