标准滚动栏msdn示例简化版

滚动栏是Windows中重要的UI元素,它有两种类型:标准滚动栏、滚动栏控制。二者比较起来,标准滚动栏简单但功能有限制。它只在窗口客户区的边界上,作为窗口的一个部分不含有独立的窗口句柄,消息由所在窗口的WndProc处理。滚动栏控制功能更强大一点,相对复杂一些。它可以在窗口的任何地方,是独立的子窗口有自己的窗口句柄,消息由自己处理。

本文的目的是展示滚动栏的消息处理方法,不涉及复杂的应用,所以例子比较简单,是对msdn上滚动栏示例的简化:只含有垂直滚动栏,不含有水平滚动栏。

下面是这个例子的运行截图:

对右侧的滚动栏进行交互,客户区的文本可以正确地显示出来。这个示例程序代码非常简单:

1. 头文件和声明

// Scrollable.cpp
// 2012-12-04 by btwsmile

#include <Windows.h>
#include <tchar.h>
// WndProc pre-declaration
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

因为要调用Windows API函数,所以需包含Windows.h头文件。tchar.h是提供通用字符串处理的,程序中调用了_tcslen()函数计算字符串的长度,为了支持Unicode这样做是很有必要的。WndProc是窗口过程函数的声明式,其实现被放在WinMain后。

2. WinMain函数的定义

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("Scrollable");
	WNDCLASS wndclass;
	MSG msg;

	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)(COLOR_WINDOW+1);
	wndclass.lpszMenuName	= NULL;
	wndclass.lpszClassName	= szAppName;

	if( !::RegisterClass(&wndclass) )
	{
		MessageBox(NULL, TEXT("Register wndclass failed!"), TEXT("ERROR"), MB_OK);
		return 1;
	}
	HWND hWnd = ::CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW | WS_VSCROLL,
							200, 200, 320, 240,
							NULL, NULL, hInstance, NULL);
	::ShowWindow(hWnd, iCmdShow);
	::UpdateWindow(hWnd);

	while( ::GetMessage(&msg, NULL, 0, 0) )
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}
	return msg.wParam;
}

遵循了窗口的一般创建过程,即:

a. 定义窗口类wndclass;

b. 注册窗口类;

c. 创建窗口并获得句柄hWnd;

d. 显示和更新窗口;

e. 启动消息循环。

需要特别注意的是,在调用CreateWindow函数创建窗口时,窗口风格需指定WS_VSCROLL,这样系统才会在窗口的右侧增加一个滚动栏。

3. WndProc的实现

WndProc有点长,但容易理解,主要分成两个部分:变量声明,消息处理。

3.1 变量声明

// WndProc definition
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hdc; 
	PAINTSTRUCT ps;
	TEXTMETRIC tm;
	SCROLLINFO si;

	static int yClient;     // height of client area
	static int yChar;       // vertical scrolling unit 
	static int yPos;        // current vertical scrolling position
	// Create an array of lines to display. 
	#define LINES 28 
	static TCHAR *abc[] = { 
		   TEXT("anteater"),  TEXT("bear"),      TEXT("cougar"), 
		   TEXT("dingo"),     TEXT("elephant"),  TEXT("falcon"), 
		   TEXT("gazelle"),   TEXT("hyena"),     TEXT("iguana"), 
		   TEXT("jackal"),    TEXT("kangaroo"),  TEXT("llama"), 
		   TEXT("moose"),     TEXT("newt"),      TEXT("octopus"), 
		   TEXT("penguin"),   TEXT("quail"),     TEXT("rat"), 
		   TEXT("squid"),     TEXT("tortoise"),  TEXT("urus"), 
		   TEXT("vole"),      TEXT("walrus"),    TEXT("xylophone"), 
		   TEXT("yak"),       TEXT("zebra"),
		   TEXT("This line contains words, but no character. Go figure."),
		   TEXT("")
		 }; 

hdc是设备描述表句柄,想要在客户区输出文本或图形都需要hdc;ps是绘制结构,在处理WM_PAINT消息的时候使用,其rcPaint成员表示无效矩形区域,在计算TextOut的垂直坐标时会用到;tm表示字体的尺寸信息;si表示滚动栏信息。yClient表示客户区的高度,yChar表示每一行的高度,yPos表示当前垂直滚动栏的位置。LINES是常量宏,表示abc所含字符串数量。abc数组保存着将在窗口中显示字符串,每行一个字符串。

3.2 消息处理

	switch (uMsg) 
	{ 
		case WM_CREATE : 
			hdc = GetDC (hwnd); 
			GetTextMetrics (hdc, &tm);	// get text metric info
			yChar = tm.tmHeight + tm.tmExternalLeading; 
			ReleaseDC (hwnd, hdc); 
			return 0; 
 
		case WM_SIZE: 
			yClient = HIWORD (lParam);
			si.cbSize = sizeof(si); 
			si.fMask  = SIF_RANGE | SIF_PAGE; 
			si.nMin   = 0; 
			si.nMax   = LINES - 1; 
			si.nPage  = yClient / yChar; 
			SetScrollInfo(hwnd, SB_VERT, &si, TRUE); // Set the vertical scrolling range and page size
			return 0; 

		case WM_VSCROLL:
			 si.cbSize = sizeof (si);
			 si.fMask  = SIF_ALL;
			 GetScrollInfo (hwnd, SB_VERT, &si);
			 yPos = 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; 
			 }

			 si.fMask = SIF_POS;
			 SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
			 GetScrollInfo (hwnd, SB_VERT, &si);
			 // If the position has changed, scroll window and update it
			 if (si.nPos != yPos)
			 {
				ScrollWindow(hwnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
				UpdateWindow (hwnd);
			 }
			 return 0;

		case WM_PAINT :
			 hdc = BeginPaint (hwnd, &ps);
			 si.cbSize = sizeof (si);
			 si.fMask  = SIF_POS;
			 GetScrollInfo (hwnd, SB_VERT, &si);
			 yPos = si.nPos;
			 {
				 // Find painting limits
				 int iFirstLine = max (0, yPos + ps.rcPaint.top / yChar);
				 int iLastLine = min (LINES - 1, yPos + ps.rcPaint.bottom / yChar);
				 for (int i = iFirstLine; i <= iLastLine; i++)
					 ::TextOut(hdc, 0, ps.rcPaint.top + (i-iFirstLine)*yChar, abc[i], _tcslen(abc[i]));
			 }
			 EndPaint (hwnd, &ps);
			 return 0;
         
		case WM_DESTROY :
			 PostQuitMessage (0);
			 return 0;
	}
	return DefWindowProc (hwnd, uMsg, wParam, lParam);
}

在处理WM_CREATE消息时,调用GetTextMetrics函数获取字体尺寸信息,yChar等于tmHeight与tmExternalLeading之和。

在处理WM_SIZE消息时,调用SetScrollInfo函数设置滚动栏的range和page size,可以看出是以“行”为单位的,而非像素。

接下来处理WM_VSCROLL消息,当用户与右侧滚动栏交互会触发这一消息,其wParam参数的低16位表示交互请求类型,有8种,这里只处理了其中的7种,没处理的一种是SB_THUMBPOSITION,这种消息其实是当用户拖动滚动块松开鼠标后发出的。

处理WM_VSCROLL消息分成三大步:一是更新si.nPos,二是调用SetScrollInfo设置滚动栏的信息,三是判断是否发生了变更,若变更则调用ScrollWindow滚动窗口并调用UpdateWindow里面更新无效的矩形区域。更新的实质是直接向WndProc发送WM_PAINT消息,从而可以看出WndProc在返回前被重入的。

接着处理WM_PAINT消息,对无效的矩形区域进行绘制。注意TextOut附近的代码被专门放入一个语句块中,用大括号标识出来,不这么做编译器会报错,这与作用域有关。

需要特别注意TextOut输出字符串时所使用的坐标,水平方向上总是0,垂直方向上是ps.rcPaint.top + (i-iFirstLine)*yChar,即以无效矩形区域的上侧(top)为基准,偏移i-iFirstLine行后的起始坐标值。

最后是WM_DESTROY消息,当用户关闭程序,窗口被销毁后就会抛出此消息。这时Post退出消息号0,消息循环获取它,正好可以退出循环,整个程序运行结束。

你可能感兴趣的:(标准滚动栏msdn示例简化版)