再上一篇:3.1 显示的时机
上一篇:3.2 简单的文本显示
主页
下一篇:3.4 关于文本显示的更多内容
再下一篇:4.1 图形设备接口
文章列表

3.3 滚动条的应用

《Windows 下的 C/C++高级编程》讲述Windows环境下调用Win32 API函数程序设计

但是,上面的程序还有不尽如人意的地方。除非用户的显示器足够大、分辨率足够高,否则就无法看到程序源文件后面数行的内容。如果窗口比较窄,有些行靠右边的部分也可能看不到。为了解决这个问题,应该使用滚动条。

3.3.1 滚动条基础

在Windows的图形界面环境下,滚动条具有极其重要的作用。由于窗口用户区大小的限制,当大量文本或者图形需要显示时,其中的一部分(有时甚至是绝大部分)将处于用户区以外,不能显示出来,这就需要使用滚动条滚动用户区中显示的内容,使原先在用户区之外的内容也能在用户区中显示出来。

滚动条分为垂直方向的和水平方向的,前者供上下滚动使用,后者供左右滚动使用。用户可以单击滚动条两端的箭头,上下滚动一行或左右滚动一列;可以单击滚动条与两端箭头之间的空白区域,上下或左右滚动一屏;还可以用鼠标拖动滚动块到特定的位置,用户区中的内容会随之作相应的滚动,如图3-7所示。

在应用程序中包含滚动条很简单,只需在创建窗口函数CreateWindow的第3个参数(窗口显示风格参数)中设置相应的风格即可。设置WS_VSCROLL风格将包含垂直滚动条,设置WS_HSCROLL风格将包含水平滚动条。

#第45页~ Windows下的C/C++高级编程

图3-7 滚动条操作示意图

3.3.2 滚动条的范围和位置

每个滚动条均有一个相关的范围和位置。滚动条的范围由最小值和最大值两个整数值定义,用来规定滚动条的滚动范围。对于垂直滚动条,当滚动块处于滚动条的顶部时,滚动条处于最小位置状态,当滚动块处于滚动条的底部时,滚动条处于最大位置状态。对于水平滚动条,最小值是滚动块处于滚动条的最左端,最大值是滚动块处于滚动条的最右端。

在默认的情况下,滚动条的范围是从0到100。如果程序需要,通过调用SetScrollRange函数可以更改滚动条的范围,其定义如下:

BOOL SetScrollRange(

HWND hWnd, // 窗口句柄

int nBar, // 用于指定所调整的是垂直滚动条还是水平滚动条

int nMinPos, // 指定的滚动范围最小值

int nMaxPos, // 指定的滚动范围最大值

BOOL bRedraw // 是否在指定新范围后重绘滚动条

);

hWnd:用于指定需更改范围的滚动条所属的窗口。

nBar:用于指定需更改范围的滚动条,取值为SB_VERT或者SB_HORZ,前者指明为垂直滚动条,后者指明为水平滚动条。

nMinPos和nMaxPos:重新指定的滚动条范围的最小值和最大值。

bRedraw:用于指定 Windows是否在更改后重绘滚动条。如果是,则设置其值为TRUE;如果想避免过多的重绘而影响效率,则设置为FALSE。

滚动块的位置由一个整数表示,其值在滚动条范围之内。例如,范围为0到4的滚动条具有0、1、2、3、4共5个滚动块位置,如图3-8所示。其中,位置0处于滚动条的最小位置处,位置4处于滚动条的最大位置处。

#第46页~

第3章 文本显示

图3-8 滚动块位置示意图

可以使用函数GetScrollPos来得到滚动块的当前位置,其定义如下:

int GetScrollPos(

HWND Wnd, // 窗口句柄

int nBar // 用于指定是垂直滚动条还是水平滚动条

);

其中,参数hWnd用于指定滚动条所属的窗口。参数nBar用于指定滚动条,取值SB_VERT为垂直滚动条,取值SB_HORZ为水平滚动条。

可以使用函数SetScrollPos来设定滚动块的位置,其定义如下:

int SetScrollPos(

HWND Wnd, // 窗口句柄

int nBar, // 用于指定是垂直滚动条还是水平滚动条

int nPos, // 滚动块新位置的值

BOOL bRedraw // 是否在指定新范围后重绘滚动条

);

其中,参数nPos为滚动块新位置的取值,它必须在滚动条nMinPos和nMaxPos所设定的范围之内。

程序使用滚动条时,需要应用程序和Windows系统共同维和滚动条,更新滚动块位置。以下的工作由Windows系统处理:

处理所有滚动条鼠标事件;

向包含滚动条的窗口的窗口函数发送滚动条消息;

当用户拖动滚动块时移动滚动块;

当用户单击滚动块与滚动条两端箭头之间的空白区域时,提供一种“反相视频”闪烁。

以下是应用程序应当完成的工作:

初始化滚动条的范围和位置;

处理窗口过程接收的滚动条消息;

更新滚动块的位置;

响应滚动条的更改,相应地更改窗口用户区中的内容。

3.3.3 滚动条消息

当使用鼠标进行滚动条操作时,Windows将向相应的窗口函数发送滚动条消息。如果操

#第47页~ Windows下的C/C++高级编程

作的是垂直滚动条,则窗口函数将接收到WM_VSCROLL消息;如果操作的是水平滚动条,则窗口函数将接收到 WM_HSCROLL 消息。对滚动条的任何一个鼠标操作都将产生两个消息,一个在按下鼠标时产生,一个在释放鼠标时产生。

这两个消息的wParam参数描述了用户对滚动条进行的操作。Wparam参数被分为一个低字节和一个高字节。其中,低字节是一个数值标识符,用来表示所进行的操作,其定义如下:

#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

其中,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条,包含LEFT和RIGHT的标识符用于水平滚动条,如图3-9所示。

图3-9 滚动条消息wParam参数示意图

#第48页~

第3章 文本显示

如果在滚动条中按住鼠标键不放,窗口函数就会收到多条滚动条消息。当释放鼠标后,窗口函数会收到一个带有SB_ENDSCROLL信息的消息,应用程序通常忽略该消息。

当用户拖动滚动块时,窗口函数会收到许多带有SB_THUMBTRACK信息的消息;当用户释放鼠标键从而停止拖动时,窗口函数会收到一条带有 SB_THUMBPOSITION 信息的消息。如果wParam的低字节是SB_THUMBTRACK,则其高字节是被拖动的滚动块的当前位置;如果wParam的低字节是SB_THUMBPOSITION,则其高字节是用户释放鼠标键时滚动块的最终位置。对于其他的滚动条操作,wParam的高字节应当被忽略。

如果处理SB_THUMBTRACK,则在用户拖动滚动块时应用程序需要同步更新用户区的内容。而如果只处理SB_THUMBPOSITION,应用程序只需在用户停止拖动时更新用户区的内容。如果应用程序设置了一个很大的滚动条范围,并且有大量的数据需要进行处理,然后加以显示,则当用户快速拖动滚动块时,应用程序对屏幕的更新速度可能跟不上对SB_THUMBTRACK 的处理,所以,大多数应用程序忽略 SB_THUMBTRACK,而只处理SB_THUMBPOSITION。

WParam参数的低字节也可能是LB_TOP、LB_BOTTOM、LB_LEFT或者LB_RIGHT,表明滚动块已经被移到最小位置或最大位置。对于窗口滚动条,窗口函数不会收到这样的wParam参数,只有在作为子窗口而创建的滚动条才会用到这样的wParam参数。

另外,为了给用户提供反馈信息,Windows在鼠标拖动滚动块时会移动它,同时给相应的窗口函数发送带有SB_THUMBTRACK或者SB_THUMBPOSITION信息的消息。但是,Windows 不会去改变滚动块的位置,而应当由应用程序在响应相应的消息时通过调用SetScrollPos函数来设置滚动块的新位置。如果应用程序不加以处理,则在用户释放鼠标键从而停止滚动时,滚动块会迅速跳回原来的位置。

3.3.4 添加滚动条

下面就通过一个简单的例子来说明滚动条的具体应用。该程序对上一节的SimpleDisplay.c稍加修改,在其基础上添加了一个简单的垂直滚动条,见程序3-3。

程序3-3 DisplayWithScrollBar.c

#include

#include

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpcmdLine, int nCmdShow)

{

static TCHAR szAppName[] = TEXT("DisplayWithScrollBar ");

static TCHAR szClassName[] = TEXT("DisplayWithScrlBarClass ");

HWND hwnd;

MSG msg;

WNDCLASS wndclass;

#第49页~ Windows下的C/C++高级编程

wndclass.style = CS_HREDRAW | CS_VREDRAW;

wndclass.lpfnWndProc = WndProc;

wndclass.cbClsExtra = 0;

wndclass.cbWndExtra = 0;

wndclass.hInstance = hInstance;

wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION );

wndclass.hCursor = LoadCursor( NULL,IDC_ARROW );

wndclass.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );

wndclass.lpszMenuName = NULL;

wndclass.lpszClassName = szClassName;

// 注册窗口类

if ( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT("This program requires Windows NT!"),

szAppName, MB_ICONERROR );

return 0;

}

// 创建窗口

hwnd = CreateWindow(

szClassName,

TEXT("Display Source Text With Scroll Bar Program"),

// 创建窗口时,添加了WS_VSCROLL风格,以添加垂直滚动条

WS_OVERLAPPEDWINDOW | WS_VSCROLL,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL );

// 显示窗口

ShowWindow( hwnd, nCmdShow );

// 更新窗口

UpdateWindow( hwnd );

// 消息循环

#第50页~

第3章 文本显示

while ( GetMessage( &msg, NULL, 0, 0 ) )

{

TranslateMessage( &msg );

DispatchMessage( &msg );

}

return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){

static int cxChar,cyChar; // 用于记录字符的宽度和高度

static int cyClient; // 用于记录窗口用户区的高度

static int nVScrollPos; // 用于记录滚动块当前的位置

static int cLineNumber; // 用于记录源文件总行数

int nCurrentLineNumber; // 用于记录当前所在行数

TCHAR szBuff[256]; // 用于存放读入的字符

HDC hdc; // 显示设备句柄

PAINTSTRUCT ps; // 绘图信息结构体

TEXTMETRIC tm; // 字符信息结构体

FILE *fp; // 文件指针,用于打开、读写、关闭文件等操作

switch( message )

{

case WM_CREATE:

// 获得设备句柄

hdc = GetDC( hwnd );

// 获得字符信息,并确定字符宽度和字符高度

GetTextMetrics( hdc, &tm );

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight + tm.tmExternalLeading;

// 释放设备句柄

ReleaseDC( hwnd, hdc );

/******* 获得源文件DisplayWithScrollBar.c的总行数 *******/

cLineNumber = 0;

// 以只读方式打开源文件DisplayWithScrollBar.c

if( (fp=fopen("DisplayWithScrollBar.c", "r")) != NULL )

{

#第51页~ Windows下的C/C++高级编程

while( !feof(fp) )

{

char ch;

// 一次读入一个字符,直到一行的结尾

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

;

cLineNumber++;

}

// 关闭打开的文件

fclose(fp);

}

/*******************************************************/

// 设置垂直滚动条的范围

SetScrollRange( hwnd, SB_VERT, 0, cLineNumber-1, FALSE );

// 设置垂直滚动条的初始位置

SetScrollPos( hwnd, SB_VERT, 0, TRUE );

return 0;

case WM_SIZE:

// 设置窗口用户区的高度

cyClient = HIWORD(lParam);

return 0;

case WM_VSCROLL:

switch( LOWORD(wParam) )

{

case SB_LINEUP: // 向上一行

nVScrollPos–= 1;

break;

case SB_LINEDOWN: // 向下一行

nVScrollPos += 1;

break;

case SB_PAGEUP: // 向上一屏

nVScrollPos–= cyClient / cyChar;

break;

case SB_PAGEDOWN: // 向下一屏

nVScrollPos += cyClient / cyChar;

break;

case SB_THUMBPOSITION: // 停止拖动滚动块

nVScrollPos = HIWORD(wParam);

#第52页~

第3章 文本显示

break;

default:

break;

}

// 设置当前滚动块位置,min及max函数用于确保该位置在滚动条范围以内

nVScrollPos = min( cLineNumber-1, max(nVScrollPos,0) );

// 如果滚动块位置改变,则重新设置滚动块,并重绘

if( nVScrollPos != GetScrollPos(hwnd, SB_VERT) )

{

SetScrollPos( hwnd, SB_VERT, nVScrollPos, TRUE );

InvalidateRect( hwnd, NULL, TRUE );

}

return 0;

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps );

// 以只读方式打开源文件DisplayWithScrollBar.c

if( (fp=fopen("DisplayWithScrollBar.c", "r")) != NULL )

{

for(nCurrentLineNumber=0;nCurrentLineNumber

{

int j=0,k;

char ch;

// 从文件中读取字符,一次一个,直到一行结束或文件结束

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

if( ch == '\t' ) // 以4个空格代替一个制表符

for( k=0; k<4; k++ )

szBuff[j++] = ‘ ’;

else

szBuff[j++] = ch;

TextOut( hdc, cxChar, cyChar*( nCurrentLineNumber-nVScrollPos), szBuff, j);

}

}

EndPaint( hwnd, &ps );

#第53页~ Windows下的C/C++高级编程

return 0;

case WM_DESTROY:

PostQuitMessage( 0 );

return 0;

}

return DefWindowProc( hwnd, message, wParam, lParam );

}

首先,在 CreateWindow函数的窗口风格参数中增加WS_VSCROLL设置,从而在窗口中添加了垂直滚动条。

其次,在窗口函数(WndProc)响应WM_CREATE消息的处理中,增加了对滚动条的初始化工作。

语句

SetScrollRange( hwnd, SB_VERT, 0, cLineNumber-1, FALSE );

用于设置垂直滚动条的范围。源文件DisplayWithScroll.c有cLineNumber行文本,故滚动条的范围被设置为0到cLineNumber-1,这样滚动块的每个位置对应于一行文本。如果滚动块的位置为0,则文本的第一行出现在用户区的顶端;如果滚动块的位置为cLineNumber-1,则最后一行文本出现在用户区的顶端。

语句

SetScrollPos( hwnd, SB_VERT, 0, TRUE );

用于设置滚动块的初始位置,这里置为0。

然后是对滚动条消息的处理。

为处理滚动条消息,程序定义静态变量 nVScrollPos来表示滚动块的当前位置。对于SB_LINEUP 和 SB_LINEDOWN,只需将 nVScrollPos 的值进行简单的加减即可。对于SB_PAGEUP和SB_PAGEDOWN,为了移动一屏,需要计算每屏的文本行数,其值为用户区高度(cyClient)/字符高度(cyChar),然后将 nVScrollPos 加减该值。对于SB_THUMBPOSITION,滚动块的新位置值就是wParam参数的高字节的值。

在计算出新的nVScrollPos值后,为了保证该值在滚动条范围以内,程序使用了语句

nVScrollPos = min( cLineNumber-1, max(nVScrollPos,0) );

然后将该nVScrollPos的值与由GetScrollPos函数获得的滚动块当前位置值比较。如果滚动块的位置确实发生了变化,则调用SetScrollPos来更新,并且调用InvalidateRect使整个用户区无效,从而导致整个用户区的重绘。

与SimpleDisplay不同,DisplayWithScrollBar中第nCurrentLineNumber行文本的Y方向位置为:

cyChar*( nCurrentLineNumber-nVScrollPos);

程序仍然显示所有的源代码文本,但是对于nVScrollPos值小于0或者大于用户区高度/字符高度的那些行,因为在用户区之外,所以它们是不可见的。

该程序的效果图如图3-10所示。

#第54页~

第3章 文本显示

图3-10 DisplayWithScrollBar.c程序效果图

3.3.5 改善滚动条

DisplayWithScrollBar程序能够正常的工作,但是,它只有垂直滚动条而没有水平滚动条,并且它的垂直滚动条也不是很完美。还有很重要的一点,该程序的效率比较低,在文本量很少的情况下,这个问题并不明显,但是,当有大量的文本或者图形需要显示时,就不得不考虑这个问题了。下面就对DisplayWithScrollBar进行改进。

DisplayWithScrollBar 中所使用的 SetScrollRange、GetScrollRange、SetScrollPos 和GetScrollPos函数在早期的Windows版本中就出现了,只是在Win32 API中将它们的参数升级到32位。虽然它们仍然能继续工作,但是新的Windows系统提供了两个更好的函数,即SetScrollInfo和GetScrollInfo。这两个函数不但实现了以前函数的全部功能,还增加了一些新的功能。

首先,新的滚动条函数使用真正的32位参数,使得可以接收的滚动条变动范围扩大了。老的滚动条函数当接收到带有SB_THUMBTRACK或SB_THUMBPOSITION信息的滚动条消息时,其参数wParam虽然是32位的,但因wParam高端的16位用于确定信息种类,所以只有低端的16位能够提供滚动块位置信息。而通过GetScrollInfo函数则可以获取真正的32位位置信息值。

其次,涉及滚动块的大小。读者可能已经注意到,DisplayWithScrollBar中的滚动块大小是固定的。然而在通常的Windows应用程序中,滚动块的大小应当与应用程序窗口用户区的大小以及所需显示内容大小有关,大致的比例关系为:

滚动块大小/滚动条长度 ≈ 页面大小/滚动条范围 ≈ 一屏显示的文档量/文档总量

其中页面大小是指一屏的显示量,例如在DisplayWithScrollBar中即为cyClient/cyChar。可以通过SetScrollInfo函数来设置页面大小。

这里用到一个新的结构SCROLLINFO(滚动条信息结构),其定义如下:

typedef struct tagSCROLLINFO {

UINT cbSize; // 结构体自身的大小(其值为sizeof(SCROLLINFO))

UINT fMask; // 操作标识

#第55页~ Windows下的C/C++高级编程

int nMin; // 滚动条范围最小值

int nMax; // 滚动条范围最大值

UINT nPage; // 页面大小

int nPos; // 滚动块位置

int nTrackPos; // 被拖动的滚动块位置

} SCROLLINFO;

在程序中,通常可以如下定义SCROLLINFO结构类型:

SCROLLINFO si;

SetScrollInfo函数如下:

int SetScrollInfo(

HWND hwnd, // 句柄

int fnBar, // 用于确定具体的滚动条

LPCSCROLLINFO lpsi, // 指向滚动条信息结构体(SCROLLINFO)的指针

BOOL fRedraw // 是否在更改滚动条信息后重绘滚动条

);

其中,参数fnBar各种取值定义见表3-4。

表3-4 fnBar取值定义表

取值 意义

SB_HORZ 表明SetScrollInfo函数操作对象为水平滚动条

SB_VERT 表明SetScrollInfo函数操作对象为垂直滚动条

SB_CLT 表明SetScrollInfo函数用于滚动条控制。此时,hwnd参数为滚

动条控制句柄

参数lpsi指向一个SCROLLINFO结构体,该结构中fMask域是以SIF为前缀的一个或多个标识,并且可以使用位或操作符(|)组合这些标识。fMask各种取值定义见表3-5。

表3-5 SetScrollInfo函数fMask域取值定义表

取值 意义

SIF_PAGE 指明SetScrollInfo函数设置lpsi参数所指向的SCROLLINFO结

构中的nPage域

SIF_POS 指明SetScrollInfo函数设置lpsi参数所指向的SCROLLINFO结

构体中的nPos域

SIF_RANGE 指明SetScrollInfo函数设置lpsi参数所指向的SCROLLINFO结

构体中的nMin域和nMax域

SIF_ALL SIF_PAGE 、SIF_POS和SIF_RANGE的组合

SIF_DISABLENOSCROLL 如果新的滚动条参数表明不再需要滚动条,SetScrollInfo函数将

禁用滚动条

GetScrollInfo函数如下:

BOOL GetScrollInfo(

#第56页~

第3章 文本显示

HWND hwnd, // 句柄

int fnBar, // 用于确定具体的滚动条

LPSCROLLINFO lpsi // 指向滚动条信息结构体(SCROLLINFO)的指针

);

其中,参数hwnd和fnBar的意义与SetScrollInfo中的相同。

参数lpsi所指SCROLLINFO结构体中fMask域的各种取值定义见表3-6。

表3-6 GetScrollInfo函数fMask域取值定义表

取值 意义

SIF_PAGE 指明SetScrollInfo函数读取当前的页面大小值,并将该值赋予lpsi

参数所指向的SCROLLINFO结构中的nPage域

SIF_POS 指明SetScrollInfo函数读取当前的滚动块位置值,并将该值赋予

lpsi参数所指向的SCROLLINFO结构体中的nPos域

指明SetScrollInfo函数读取当前滚动条范围的最小和最大值,并SIF_RANGE 分别赋予lpsi参数所指向的SCROLLINFO结构体中的nMin域

和nMax域

指明SetScrollInfo函数读取当前被拖动的滚动块的位置值,并将SIF_TRACKPOS 该值赋予lpsi参数所指向的SCROLLINFO结构体中的nTrackPos

SIF_ALL SIF_PAGE 、SIF_POS、SIF_RANGE和SIF_TRACKPOS的组合

在DisplayWithScrollBar中,滚动条的范围为0到cLineNumber-1。这样,当滚动块在最小位置时,文本的第一行出现在用户区的顶端;当滚动块在最大位置时,文本的最后一行出现在用户区的顶端,用户区的绝大部分区域是空白,看不见其他行。

这说明DisplayWithScrollBar中所设置的滚动条范围过大。事实上,当滚动块处于最大位置时,只需将最后一行文本显示在用户区的底部而不是顶端即可。为此,需要将nVScrollMax的值修改为cLientNumber – cyClient / cyChar。SetScrollInfo可以完成这个修改。为了得到cyClient的值,不再在处理WM_CREATE消息时设置滚动条范围,而应在处理WM_SIZE消息时再处理。

si.cbMask = SIF_RANGE | SIF_PAGE;

si.nMin = 0;

si.nMax = cLineNumber – 1;

si.nPage = cyClient / cyChar;

SetScrollInfo( hwnd, SB_VERT, &si, TRUE );

Windows会自动把滚动条范围的最大值设置为si.nMax – si.nPage + 1,而不是简单的设置为si.Max。

程序 3-4 就是改进过的滚动条程序。该程序使用新的滚动条函数 SetScrollInfo 和GetScrollInfo。除垂直滚动条外,还添加了水平滚动条,并且还提高了用户区的重绘效率。

程序3-4 DisplayWithScrollBarV2.c

#include

#第57页~ Windows下的C/C++高级编程

#include

#define MaxBuff 256

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpcmdLine, int nCmdShow)

{

static TCHAR szAppName[] = TEXT("DisplayWithScrollBarV2");

static TCHAR szClassName[]= TEXT("DisplayWithScrlBarV2Class");

HWND hwnd;

MSG msg;

WNDCLASS wndclass;

wndclass.style = CS_HREDRAW | CS_VREDRAW;

wndclass.lpfnWndProc = WndProc;

wndclass.cbClsExtra = 0;

wndclass.cbWndExtra = 0;

wndclass.hInstance = hInstance;

wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION );

wndclass.hCursor = LoadCursor( NULL,IDC_ARROW );

wndclass.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );

wndclass.lpszMenuName = NULL;

wndclass.lpszClassName = szClassName;

if ( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT("This program requires Windows NT!"),

szAppName, MB_ICONERROR );

return 0;

}

hwnd = CreateWindow(

szClassName,

TEXT("Display Source Text With Scroll Bar Program Version 2"),

WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

#第58页~

第3章 文本显示

NULL,

NULL,

hInstance,

NULL );

ShowWindow( hwnd, nCmdShow );

UpdateWindow( hwnd );

while ( GetMessage( &msg, NULL, 0, 0 ) )

{

TranslateMessage( &msg );

DispatchMessage( &msg );

}

return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){

static int cxChar,cyChar; // 用于记录字符宽度和字符高度

static int cxClient,cyClient; // 用于记录用户区宽度和高度

static int nHScrollPos,nVScrollPos; // 用于记录水平滚动条和垂直滚动条滚动块的位置

static int cMaxCharNumber; // 用于记录文件中单行文本的最大字符数

static int cLineNumber; // 用于记录文件中文本的总行数

int nFirstLine,nLastLine; // 用于记录需要显示的最初行和最末行

int nCurrentCharNumber; // 用于记录一行中当前字符数

int nCurrentLineNumber; // 用于记录当前行数

TCHAR szBuff[MaxBuff]; // 用于存放读入的字符

HDC hdc; // 显示设备句柄

PAINTSTRUCT ps; // 绘图信息结构体

TEXTMETRIC tm; // 文本信息结构体

SCROLLINFO si; // 滚动条信息结构体

FILE *fp; // 文件指针,用于打开、读写、关闭文件等操作

switch( message )

{

case WM_CREATE:

hdc = GetDC( hwnd );

// 获得字符信息,并确定字符宽度和字符高度

GetTextMetrics( hdc, &tm );

#第59页~ Windows下的C/C++高级编程

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC( hwnd, hdc );

/***** 获得源文件DisplayWithScrollBarV2.c的总行数和单行文本最大字符数 *****/

nCurrentCharNumber = 0;

cMaxCharNumber = 0;

cLineNumber = 0;

if( (fp=fopen("DisplayWithScrollBarV2.c", "r")) != NULL )

{

while( !feof(fp) )

{

char ch;

// 读入完整的一行

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

nCurrentCharNumber++;

// 如果新读入行字符数大于原先的单行文本最大字符数,则更新后者

if( nCurrentCharNumber > cMaxCharNumber )

cMaxCharNumber = nCurrentCharNumber;

cLineNumber++;

nCurrentCharNumber = 0;

}

fclose(fp);

}

/***************************************************************************/

return 0;

case WM_SIZE:

// 获取用户区大小信息

cxClient = LOWORD(lParam);

cyClient = HIWORD(lParam);

/********** 设置滚动条信息 **********/

si.cbSize = sizeof(SCROLLINFO);

si.fMask = SIF_RANGE | SIF_PAGE;

si.nMin = 0;

si.nMax = cLineNumber - 1;

si.nPage = cyClient / cyChar;

#第60页~

第3章 文本显示

SetScrollInfo( hwnd, SB_VERT, &si, TRUE );

si.cbSize = sizeof(SCROLLINFO);

si.fMask = SIF_RANGE | SIF_PAGE;

si.nMin = 0;

si.nMax = cMaxCharNumber - 1;

si.nPage = cxClient / cxChar;

SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );

/**************************************/

return 0;

// 水平滚动条消息

case WM_HSCROLL:

// 获取水平滚动条信息并记录当前水平滚动块的位置

si.cbSize = sizeof(SCROLLINFO);

si.fMask = SIF_ALL;

GetScrollInfo( hwnd, SB_HORZ, &si );

nHScrollPos = 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;

case SB_LEFT: // 到达最左端

si.nPos = si.nMin;

break;

case SB_RIGHT: // 到达最右端

#第61页~ Windows下的C/C++高级编程

si.nPos = si.nMax;

break;

default:

break;

}

// 设置水平滚动条信息

si.fMask = SIF_POS;

SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );

// 读取水平滚动条信息

GetScrollInfo( hwnd, SB_HORZ, &si );

// 如果垂直滚动块确实移动了,则相应地滚动屏幕

if( si.nPos != nHScrollPos )

ScrollWindow( hwnd, cxChar*(nHScrollPos-si.nPos), 0, NULL, NULL );

return 0;

// 垂直滚动条消息

case WM_VSCROLL:

// 获取垂直滚动条信息并记录当前垂直滚动块的位置

si.cbSize = sizeof(SCROLLINFO);

si.fMask = SIF_ALL;

GetScrollInfo( hwnd, SB_VERT, &si );

nVScrollPos = si.nPos;

switch( LOWORD(wParam) )

{

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: // 滚动块被拖动

#第62页~

第3章 文本显示

si.nPos = si.nTrackPos;

break;

case SB_TOP: // 到达最顶端

si.nPos = si.nMin;

break;

case SB_BOTTOM: // 到达最底端

si.nPos = si.nMax;

break;

default:

break;

}

// 设置垂直滚动块位置

si.fMask = SIF_POS;

SetScrollInfo( hwnd, SB_VERT, &si, TRUE );

// 读取垂直滚动块位置

GetScrollInfo( hwnd, SB_VERT, &si );

// 如果垂直滚动块确实移动了,则相应地滚动屏幕

if( si.nPos != nVScrollPos )

ScrollWindow( hwnd, 0, cyChar*(nVScrollPos-si.nPos), NULL, NULL );

return 0;

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps );

// 读取滚动块位置信息

si.cbSize = sizeof(SCROLLINFO);

si.fMask = SIF_POS;

GetScrollInfo( hwnd, SB_HORZ, &si ); // 水平滚动条

nHScrollPos = si.nPos;

GetScrollInfo( hwnd, SB_VERT, &si ); // 垂直滚动条

nVScrollPos = si.nPos;

// 设置需要显示的文本行范围

nFirstLine = max( 0, nVScrollPos + ps.rcPaint.top / cyChar );

nLastLine = min( cLineNumber - 1, nVScrollPos + ps.rcPaint.bottom / cyChar );

/******************** 显示文本 ********************/

if( (fp=fopen("DisplayWithScrollBarV2.c", "r")) != NULL )

#第63页~ Windows下的C/C++高级编程

{

for( nCurrentLineNumber=0; nCurrentLineNumber<=nLastLine; nCurrentLineNumber++ )

{

int j=0,k;

char ch;

if( nCurrentLineNumber < nFirstLine )

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

;

else

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

if( ch == '\t' ) // 以4个空格代替一个制表符

for( k=0; k<4; k++ )

szBuff[j++] = ‘ ‘;

else

szBuff[j++] = ch;

TextOut( hdc, cxChar*(-nHScrollPos), cyChar*(nCurrentLineNumber-nVScrollPos),

szBuff, j);

}

}

/******************************************************/

EndPaint( hwnd, &ps );

return 0;

case WM_DESTROY:

PostQuitMessage( 0 );

return 0;

}

return DefWindowProc( hwnd, message, wParam, lParam );

}

DisplayWithScrollBarV2程序只显示需要显示的文本行。由语句:

nFirstLine = max( 0, nVScrollPos + ps.rcPaint.top / cyChar );

nLastLine = min( cLineNumber - 1, nVScrollPos + ps.rcPaint.bottom / cyChar );

计算出所需显示的首行和末行,然后将应当出现在用户区中的文本行显示出来,其余的加以忽略。这样就大大提高了显示的效率。这里需要显示的内容不多,效果还不明显,但是如果有大量的文本或者图形需要显示,效率问题就很重要了。

与大多数Windows应用程序相似,在处理滚动块被拖动而产生的滚动条消息时,该程序对水平滚动条和垂直滚动条采取了不同的做法。垂直滚动条接收处理SB_THUMBTRACK信息,意味着当用户拖动垂直滚动块时,用户区的内容会随之同步滚动,这需要机器具有比较 #第64页~

第3章 文本显示强大的处理能力。而水平滚动条接收处理SB_THUMBPOSITION信息,意味着如果用户拖动水平滚动块,只有当用户释放鼠标键的时候,用户区的内容才更新,这样所作的工作要少得多。大多数Windows应用程序中,水平滚动条都不接收处理SB_THUMBTRACK信息,因为一般没有这个必要。

最后,DisplayWithScrollBarV2.c使用了ScrollWindow函数来滚动用户区中的内容。该函数定义如下:

BOOL ScrollWindow(

HWND hWnd, // 窗口句柄

int XAmount, // 水平滚动量

int YAmount, // 垂直滚动量

CONST RECT *lpRect, // 滚动区域

CONST RECT *lpClipRect // 剪取区域

);

其中,参数XAmount为水平滚动量,向左为负,向右为正。参数YAmount为垂直滚动量,向上为负,向下为正。参数lpRect和lpClipRect同时设置为NULL,表示滚动整个用户区。

DisplayWithScrollBarV2程序的运行效果如图3-11所示。

图3-11 DisplayWithScrollBarV2.c程序效果图