[Win32]简单的滚动条文本输出

1. 文本输出时空间不足的问题:

在上一个例子中屏幕明显不够大,而Windows只能简单地从客户区顶部显示那些信息,只能依赖把落在客户区之外的部分剪裁掉


2. 客户区尺寸大小的高效获取办法:

如果每次需要使用客户区尺寸时都调一次GetClientRect效率会非常低,因此获取该尺寸最佳的时机就是在响应WM_SIZE消息的时候,每当客户区形状或大小发生改变时就会产生WM_SIZE消息,而其附加参数wParam的低16位和高16位分别保存即时的窗口宽和高,因此可以声明两个静态变量来保存宽和高,这样就可以在任何消息响应中共享该变量了:

static int		cxClient, cyClient;

#define LOWORD(l)           ((WORD)(l))
#define HIWORD(l)           ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

case WM_SIZE:
	cxClient = LOWORD( lParam );
	cyClient = HIWORD( lParam );
	return 0;
其中LOWRD和HIWORD宏定义在windef.h中

通常在响应WM_SIZE消息之后会立即产生一个WM_PAINT消息,因为类风格重定义了CS_HREDRAW | CS_VREDRAW,一旦窗口形状大小发生改变则需要重绘!


3. 滚动条的简介:

创建滚动条:在CreateWindow的时候添加Window Style参数WS_VSCROLL(垂直滚动条)或WS_HSCROLL(水平滚动条)

!客户区并不包含滚动条所占用的空间

滚条的使用方法:

[Win32]简单的滚动条文本输出_第1张图片

设置滚动条的范围:

BOOL SetScrollRange( // 设定滚条的范围
	HWND	hWnd,    // 滚条所属窗口
	int		nBar,    // 设置的是哪个滚条,SB_VERT(垂直滚条)或SB_HORZ(水平滚条),SB表示Scroll Bar  
	// Windows用一组整数值表示滚条的范围
	int		nMinPos, // 位置最小值
	int		nMaxPos, // 位置最大值  
	BOOL	bRedraw  // TRUE表示滚条要重绘否则不需要立即重绘
	);
滑块的位置总是离散值,一个范围为0 ~ 4的滚条总共有5个位置:

[Win32]简单的滚动条文本输出_第2张图片

设置滑块的位置:

int SetScrollPos( HWND hWnd, int nBar, int nPos, BOOL bRedraw );

其中第3个参数就是指定滑块的位置,必须处于nMin和nMax范围内!


4. 处理滚条消息:

滚条消息及处理方式总汇:

由Windows处理的部分:

1) 滚条上的所有鼠标消息:由Windows处理,比如按下滚条两端的方向键不放时会将WM_LBUTTONDOWN转化为一个SB_LINEUP(垂直滚条)或其它(取决于哪个方向键),其中SB表示Scroll Bar

2) 当用户将鼠标移动到滑块上时以及点击滑块时会提供一种反向闪烁(移动在上面时高亮,点击时更高亮):有Windows处理,不由用户处理

3) 当用户拖动滑块时滑块自动根据用户的拖动而移动:这是由Windows来处理的,不由用户处理,但是点击箭头、点击翻页时滑块的移动却需要用户处理!

***这涉及到nPos和nTrackPos两个内部变量的问题,nPos是指滑块的实际位置,而nTrackPos是用鼠标拖动滑块时滑块的实时位置,而最终决定滑块显示在哪个位置的是nPos,nTrackPos只在鼠标拖动滑块时由Windows自动维护,而nPos必须有用户自己维护,即使是拖动完滑块松开鼠标键的时候也需要利用赋值语句nPos = nTrackPos来将滑块的最终位置定位到松开鼠标键的位置

4) 向拥有滑块的窗口发送滚条消息:由Windows处理,而用户负责对SB消息的响应

程序需要负责的部分:

1) 初始化滚条的范围以及位置

2) 处理各种滚条消息(SB消息),这些消息都包装在WM_VSCROLL和WM_HSCROLL消息中的wParam参数的低16位中了,高16位保存SB消息的附加参数

3) 更新滑块的nPos位置,nTrackPos有系统来维护

4) 响应消息的重点就是根据滑块的位置变化更新客户区的内容(上翻下翻等)


滚条消息汇总:

[Win32]简单的滚动条文本输出_第3张图片

如果鼠标点击不放则会连续收到多条消息,但当鼠标放开时则会收到SB_ENDSCROLL消息,通常不处理该消息

关于SB_THUMBRACK和SB_THUMBPOSITION消息:

前者表示按着滑块滑动(不放)时产生的消息,由于拖动是连续的,因此会连续收到多条SB_THUMBTRACK消息,而刚刚所说的nTrackPos就存储在wParam的高位,其中SB消息表示存放在WM_XSCROLL的wParam的低位,SB的附加参数wParam的高位,但是nTrackPos不能改变nPos,nPos才是真正定位滑块位置的变量(是一个编程时认为定义的一个静态变量),也是SetScrollPos的参数,而客户区内容的调整也是依据nPos来指定的,因此,想要根据滑块的拖动时时调整客户区中的内容就的在SB_THUMBTRACK中加上nPos = nTrackPos,也就是nPos = HIWORD(wParam);,注意即使不响应该消息滑块也会滑动的,因为滑块的滑动是有Windows根据nTrackPos自动维护的而不是由用户维护的,因此如果不响应SB_THUMBTRACK则客户区内容不会伴随滑块滑动而调整,但是滑块依然会滑动

而SB_THUMBPOSITION表示拖动放开的消息,wParam高位保存了放开鼠标时滑块的位置

因为nPos才是真正决定滑块最终位置的变量,因此如果不响应SB_THUMBPOSITION和SB_THUMBTRACK消息,则当拖动滑块后放开鼠标时滑块会回到原来的位置!

一般只处理两个消息中的一个就行了,要么响应SB_THUMBTRACK消息,这种是连续更新的,而响应SB_THUMBPOSITION只在鼠标松开是才调整客户区内容,但是推荐前者,前者更友好!


5. 滚条位于子窗口的特殊情况以及通知码的定义:

这里的子窗口通常是指对话框或者控件,通常在收到WM_XSCROLL消息时都可以忽略lParam,但是如果滚条子窗口中lParam是有用的

之前所说的SB消息称之为消息其实是不准确的,因为它是指WM_XSCROLL消息的附加标志,表示是WM_XSCROLL消息的哪类特征,因此应该叫做通知码,其中还有4个特殊的通知码:SB_TOP、SB_BOTTOM、SB_LEFT、SB_RIGHT,分别表示滑块滑到了端点处,这些通知码只有在非子窗口中会收到,在子窗口中则不会收到该类通知码


6. 添加垂直滚条的sysmets2:

// sysmets2.c

#include 

#include "sysmets.h"

#define	NPAGELINES		( ( cyClient ) / ( cyChar ) )
#define CLINE(i)		( ( cyChar ) * ( ( i ) - ( iVscrollPos ) ) )

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

int WINAPI WinMain(
	HINSTANCE	hInstanc,
	HINSTANCE	hPrevInstanc,
	LPSTR		lpszCmdLine,
	int			nCmdShow
	)
{
	static TCHAR	szAppName[] = TEXT("sysmets2");

	WNDCLASS	wndclass;
	HWND		hWnd;
	MSG			msg;

	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= WndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= 0;
	wndclass.hIcon			= LoadIcon( NULL, IDI_APPLICATION );
	wndclass.hCursor		= LoadCursor( NULL, IDC_ARROW );
	wndclass.hbrBackground	= (HBRUSH)GetStockObject( WHITE_BRUSH );
	wndclass.hInstance		= hInstanc;
	wndclass.lpszMenuName	= NULL;
	wndclass.lpszClassName	= szAppName;

	RegisterClass( &wndclass );

	hWnd = CreateWindow(
		szAppName,
		TEXT("Get System Metrics Version 2"),
		WS_OVERLAPPEDWINDOW | WS_VSCROLL,	// 注意加上WS_VSCROLL属性为窗口添加垂直滚条
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL,
		NULL,
		hInstanc,
		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, cxCaps, cyChar;
	static int		col1, col2, col3;
	static int		iVscrollPos, cyClient; // 滚条的位置,即nPos和客户区的高度
	// iVscrollPos也表示客户区第一行显示的是文本的第几行

	TCHAR		szBuffer[30];

	HDC				hDC;
	PAINTSTRUCT		ps;
	TEXTMETRIC		tm;

	int		i;
	int		yCur; // 重绘客户区时当前文本行在客户区中应该出现的位置的y坐标
	// 如果为负责表示在客户区外面,会被Windows直接剪裁掉

	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;
		col1 = 0;
		col2 = 22 * cxCaps;
		col3 = col1 + col2 + 40 * cxChar;

		ReleaseDC( hWnd, hDC );

		SetScrollRange( hWnd, SB_VERT, 0, NUMLINES - 1, FALSE ); // 初始化滚条范围,但不要重绘滚条
		SetScrollPos( hWnd, SB_VERT, iVscrollPos, TRUE ); // 等到接下来初始化滚条位置时再重绘滚条以避免不必要的多次重绘
		return 0;

	case WM_SIZE: // 在窗口形状大小改变时获取客户区的尺寸最为高效!
		cyClient = HIWORD( lParam ); // 由于这里只演示垂直滚条,因此不需要客户区水平宽度
		return 0;

	case WM_VSCROLL:
		switch ( LOWORD( wParam ) )
		{
		case SB_LINEUP:
			iVscrollPos -= 1;
			break;

		case SB_LINEDOWN:
			iVscrollPos += 1;
			break;

		case SB_PAGEUP:
			iVscrollPos -= NPAGELINES;
			break;
			
		case SB_PAGEDOWN:
			iVscrollPos += NPAGELINES;
			break;

		// 一下两者选一个即可,但第一个效果更好
		// 如果都是用则总效果和只是用第一个相同!

		case SB_THUMBTRACK:	// 客户区内容随滑块滑动连续更新
			iVscrollPos = HIWORD( wParam );
			break;

		case SB_THUMBPOSITION: // 客户区内容只有当滑动放开鼠标时才更新
			iVscrollPos = HIWORD( wParam );
			break;
		}

		// 在了解了滑块运动方式后(同时改变了关键变量iVscroll即nPos)就可以使用SetScrollPos真正该表滑块位置了

		iVscrollPos = max( 0, min( iVscrollPos, NUMLINES - 1 ) ); // 使滑块始终位于nMin ~ nMax中而不越界!

		if ( iVscrollPos != GetScrollPos( hWnd, SB_VERT ) ) // 只有当滑块位置真正改变时才需要更新相关内容
		{
			SetScrollPos( hWnd, SB_VERT, iVscrollPos, TRUE ); // 更新滑块实际位置并重绘出滑块的新位置
			InvalidateRect( hWnd, NULL, TRUE ); // 同时也需要更具滑块的新位置调整客户区中的内容,使整个客户区无效并重绘
			// 但是InvalidateRect函数是将产生的WM_PAINT消息投递到消息队列中的,优先级不高,如果其他消息处理比较慢则会
			// 发生等待的现象,就是该刷的没刷出来,比如关掉一个对话框后出现一个空白的矩形而要等一会儿才能刷出原窗口
			// 为了避免上述等待现象而及时响应WM_PAINT消息,可以在之后立即使用UpdateWindow函数,这样就可以直接向过程函数
			// 范松WM_PAINT消息立即响应,同时将消息队列中的WM_PAINT消息删除
			UpdateWindow( hWnd );
		}
		return 0; // WM_VSCROLL

	case WM_PAINT:
		hDC = BeginPaint( hWnd, &ps );

		for ( i = 0; i < NUMLINES; i++ )
		{
			yCur = CLINE(i);

			TextOut( hDC, col1, yCur, sysmetrics[i].szLabel, lstrlen( sysmetrics[i].szLabel ) );

			TextOut( hDC, col2, yCur, sysmetrics[i].szDesc, lstrlen( sysmetrics[i].szDesc ) );

			SetTextAlign( hDC, TA_TOP | TA_RIGHT );
			TextOut( hDC, col3, yCur, szBuffer,
				wsprintf( szBuffer, TEXT("%5d"), GetSystemMetrics( sysmetrics[i].iIndex ) ) );
			SetTextAlign( hDC, TA_TOP | TA_LEFT );
		}

		EndPaint( hWnd, &ps );
		return 0;
			
	case WM_DESTROY:
		PostQuitMessage( 0 );
		return 0;
	}

	return DefWindowProc( hWnd, message, wParam, lParam );
}

你可能感兴趣的:(Win32)