windows程序设计(八)显示区域大小与卷动列

有时候当我们显示大段文字的时候,经常会出现屏幕空间不够的情况,在本节中就要解决这个问题。

显示区域大小

我们在之前的例子中获取显示区域的大小都使用GetClientRect 函数,实际上还有更好的方法。确定视窗显示区域大小的更好方法是在视窗讯息处理程式中处理 WM_SIZE 讯息。在视窗大小改变时, Windows 给视窗讯息处理程式发送一个 WM_SIZE 讯息。传给视窗讯息处理程式的 lParam 参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。
若要进行保留的话,需要定义两个静态变量。

static int cxClient, cyClient ; 

处理MV_SIZE的方法如下;

case WM_SIZE:  
	cxClient = LOWORD (lParam) ;   
	cyClient = HIWORD (lParam) ;   
	return 0 ;

由于我们在注册窗体时指定的视窗类别为:

CS_HREDRAW | CS_VREDRAW

CS_HREDRAW 表示如果移动或大小调整更改了工作区的宽度,则重新绘制整个窗口。CS_VREDRAW如果移动或大小调整更改了工作区的高度,则重新绘制整个窗口。也就是说对于这类窗体,如果水平或者垂直发生改变,则会更新显示区域。
可以用下面的公式来计算显示区域的文字总数cyClient / cyChar。如果显示区域大小不够,则无法选择一个完整的字符,则可为0。类似的,水平方向显示的字元数为cxClient /cxChar。
不用担心cxChar和cyChar为0的情况,因为在调用showWindow函数之后,cxChar和cyChar就已经被赋值了。
如果显示区域的大小不足以容纳文字的所有内容,可以使用卷动列来进行显示。

卷动列

卷动列是图形使用者介面中最好的功能之一,它很容易使用,而且提供了 很好的视觉回馈效果。您可以使用卷动列显示无论是文字、图形、 表格、资料库记录、图像或是网页,只要它所需的空间超出了视窗的显示区域所能提供的空间,就可以使用卷动列。卷动列既有垂直方向的,也有左右方向的卷动列。
在这里插入图片描述
想要在程序上应用卷动列其实很简单,只需要在CreateWindow函数的第三个样式中包括视窗样式(WS)识别字 WS_VSCROLL(垂直卷 动)和/或 WS_HSCROLL(水平卷动)即可。这些卷动列通常放在视窗的右部和底 部,伸展为显示区域的整个长度或宽度。显示区域不包含卷动列所占据的空间。

卷动列的范围和位置

在内定情况下,卷动列的范围是从 0(顶部或左部)至 100(底部或右部), 但将范围改变为更方便於程式的数值也是很容易的:
可以使用SetScrollRange 函数

BOOL SetScrollRange(
  HWND hWnd,
  int  nBar,
  int  nMinPos,
  int  nMaxPos,
  BOOL bRedraw
);

第一个参数hWnd为处理滚动条控件或具有标准滚动条的窗口句柄。
第二个参数nBar,指定要设置的滚动条。
第三个参数nMinPos,指定最小滚动位置
第四个参数nMaxPos,指定最大滚动位置
第五个参数bRedraw指定是否应重绘滚动条以反映更改。如果此参数为TRUE,则会重新绘制滚动条。如果为FALSE,则不会重画滚动条。
如果函数成功,则返回值为非零。如果函数失败,则返回非零值。
卷动方块的位置总是离散的整数值,位置如下图所示:
在这里插入图片描述
还可以使用SetScrollPos 函数在卷动列内设置新的方块位置

SetScrollPos (hwnd, iBar, iPos, bRedraw) ;

其中iPos代表滚动条的新位置,它必须在iMin和iMax的范围内。
在使用卷动列,程序作者需要完成以下的工作:首先初始化卷动列的范围和位置 ,其次处理视窗讯息处理程式的卷动列讯息 ,之后更新卷动列内卷动方块的位置 ,最后更改显示区域的内容以回应对卷动列的更改

卷动列的讯息

在用滑鼠单击卷动列或者拖动卷动方块时,Windows 给视窗讯息处理函数发送 WM_VSCROLL(供上下移动)和 WM_HSCROLL(供左右移动)讯息。在卷动列上的每个滑鼠动作都至少产生两个讯息,一条在按下滑鼠按钮时产生,一条在释放按钮时产生。
WM_VSCROLL 和 WM_HSCROLL 的讯息会传递到wParam 和 lParam 讯息参数。wParam 讯息参数被分为一个低字组和一个高字组。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_ENDSCROLL 的讯息,这个信息可以忽略。
当把滑鼠的游标放在卷动方块上并按住滑鼠键时,您就可以移动卷动方块。 这样就产生了带有 SB_THUMBTRACK 和 SB_THUMBPOSITION 通知码的卷动列讯息。 在 wParam 的低字组是 SB_THUMBTRACK 时,wParam 的高字组是使用者在拖动卷动方块时的目前位置。该位置位于卷动列范围的最小值和最大值之间。在 wParam 的低字组是 SB_THUMBPOSITION 时,wParam 的高字组是使用者释放滑鼠键後卷动 方块的最终位置。对於其他的卷动列操作,wParam 的高字组应该被忽略。
Windows 在您用滑鼠拖动卷动方块时移动它,同时程序会收到 SB_THUMBTRACK 讯息。然而,如果不通过呼叫 SetScrollPos 来 处理 SB_THUMBTRACK 或 SB_THUMBPOSITION 讯息,在使用者释放滑鼠键後,卷动方块会迅速跳回原来的位置。
下面为一个使用卷动列的例子:

#include <Windows.h>
#include "SYSTEMS.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstacne, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("SysMets2");
	HWND hwnd;
	MSG msg;
	WNDCLASSEX wndclass;
	wndclass.cbSize = sizeof(wndclass);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0; //没有额外的类内存
	wndclass.cbWndExtra = 0; //没有额外的窗体内存
	wndclass.hInstance = hInstacne;  //实例句柄
	wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);//使用预定义的图标
	wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW); //使用预定义光标
	wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);//使用白色背景画刷	
	wndclass.lpszMenuName = NULL;  //不指定菜单
	wndclass.lpszClassName = szAppName;  //窗口类的名称
	wndclass.hIconSm = NULL; //没有类的小图标

	if (!RegisterClassEx(&wndclass)) { //注册窗口
		MessageBox(NULL, TEXT("Can't funcitom"), szAppName, MB_ICONERROR);
		return 0;
	}


	hwnd = CreateWindowEx(
		0,  //dwExStyle, 扩展样式 
		szAppName, //lpClassName,类名
		"Get System Metrics No.2",  //lpWindowName窗口名称
		WS_OVERLAPPEDWINDOW | WS_VSCROLL, //WS_VSCROLL加入垂直卷动列
		CW_USEDEFAULT,  //X,初始X坐标
		CW_USEDEFAULT,  //Y,初始Y坐标
		CW_USEDEFAULT,  //nWight, 宽度
		CW_USEDEFAULT,  //nHight, 高度
		NULL,  //hWndParent, 父窗口句柄
		NULL,  //hMenu, 菜单句柄
		hInstacne,  //hInstance, 程序实例句柄
		NULL   //ipParam,用户数据
	);

	::ShowWindow(hwnd, iCmdShow);
	::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, cyClient, iVscrollPos;
	//cxChar字元平均宽度  cyChar字元的平均高度
	//cxCaps大写字母的平均宽度   cyClient为显示区域的高度
	//iVscrollPos为卷动列内卷动方块的目前位置
	HDC hdc;
	int i, y;
	PAINTSTRUCT ps;
	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);
		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 -= cyClient / cyChar;
			break;

		case SB_PAGEDOWN://向下调整一页
			iVscrollPos += cyClient / cyChar;
			break;

		case SB_THUMBPOSITION:
			iVscrollPos = HIWORD(wParam);
			break;

		default:
			break;
		}

		iVscrollPos = max(0, min(iVscrollPos, NUMLINES - 1));//控制下拉结束
		if (iVscrollPos != GetScrollPos(hwnd, SB_VERT)) {
			SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
			InvalidateRect(hwnd, NULL, TRUE);
		}
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		for (i = 0; i < NUMLINES; i++)
		{
			y = cyChar *(i - iVscrollPos);
			TextOut(hdc, 0, y, sysmetrics[i].szLable, lstrlen(sysmetrics[i].szLable));
			TextOut(hdc, 22 * cxCaps, y, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));
			SetTextAlign(hdc, TA_RIGHT | TA_TOP);
			TextOut(hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer, 
				     wsprintf(szBuffer, TEXT("%5d"), GetSystemMetrics(sysmetrics[i].index)));   
			SetTextAlign(hdc, TA_LEFT | TA_TOP);
		}
		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

sysmetrics 结构具有 NUMLINES 行文字,所以卷动列范围被设定为 0 至 NUMLINES-1。卷动列的每个位置对应于在显示区域顶部显示的一个文字行。如 果卷动方块的位置为 0,则第一行会被放置在显示区域的顶部。如果位置大於 0, 其他行就会出现在显示区域的顶部。当位置为 NUMLINES-1 时,则最後一行文字 出现在显示区域的顶部。
该程序用WM_VSCROLL 计算出的新的iVscrollPos 值,使用min函数和max函数来调整iVscrollPos。iVscrollPos 与呼叫 GetScrollPos 取得的先前位置相比较,如果卷动位置发生了变化,则使用 SetScrollPos 来进行更新,并且呼叫InvalidateRect 使整个视窗无效。

参考资料:
[1]《Windows程序设计(第五版)》
[2]https://docs.microsoft.com/zh-cn/welcome-to-docs

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值