有时候当我们显示大段文字的时候,经常会出现屏幕空间不够的情况,在本节中就要解决这个问题。
显示区域大小
我们在之前的例子中获取显示区域的大小都使用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