在Windows程序设计-文本输出一文中,给出了显示当前Windows硬件参数的一些文本信息,然而,在这个示例显示程序,有很大一个缺陷,除非有一个大屏幕跟高分辨率的显示卡,否则就无法看到系统尺度列表的最后几行。如果窗口太窄,甚至根本看不到值,下面我们来研究一下解决这些文本现实问题的办法。
1.显示区域的大小
我们通常使用GetClientRect函数来取得显示区域的大小,使用这个函数没有什么不好,但是在每次要使用信息时就去调用它一遍是没有效率的。确定窗口显示区域大小的更好方法是在窗口消息处理程序中处理WM_SIZE消息。在窗口大小改变时,Windows给窗口消息处理程序发送一个WM_SIZE消息。传给窗口过程的lParam参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。要保存这些尺寸,需要在窗口过程函数中定义两个静态变量:
static int cxClient,cyClient;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
LOWORD和HIWORD宏在Windows表头文件WINDEF.H中定义。这些宏的定义看起来像这样:
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
这两个宏传回WORD值(16位的无正负号整数,范围从0到0xFFFF)。一般,将这些值保存在32位有符号整数中。这就不会牵扯到任何转换问题,并使得这些值在以后需要的任何计算中易于使用。
在许多Windows程序中,WM_SIZE消息必然跟着一个WM_PAINT消息。为什么呢?因为在我们定义窗口类时指定窗口类别样式为:CS_HREDRAW | CS_VREDRAW,这种窗口类别样式告诉Windows,如果水平或者垂直大小发生改变, 则强制更新显示区域。
2.滚动条
滚动条是图形使用者接口中最好的功能之一,可以使用滚动条显示任何东西--无论是文字、图形、表格、数据库记录、图像或是网页,只要它所需的空间超出了窗口的显示区域所能提供的空间,就可以使用滚动条。滚动条既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。用户可以使用鼠标在滚动条两端的箭头上或者在箭头之间的区域中点一下,这时,“滚动框”在滚动条内的移动模拟了所显示的信息在整个文当中的近似相关位置。用户也可以用鼠标拖动滚动框到特定得位置。
每个滚动条均有一个相关的“范围”(这是一对整数,分别代表最小值和最大值)和“位置”(它是滚动框在此范围内的位置)。当滚动框在滚动条的顶部(或左部)时,滚动框的位置是范围的最小值;在滚动条的底部(或右部)时,卷动方块的位置是范围的最大值。
在默认情况下,滚动条的范围是从0(顶部或左部)至100(底部或右部),但将范围改变为更方便于程序的数值也是很容易的:
函数:SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
功能:改变滚动条的范围
参数:hwnd:滚动条所属的窗口的句柄;
iBar:为SB_VERT或者SB_HORZ,
iMin和iMax:分别是范围的最小值和最大值。
bRedraw:如果想要Windows根据新范围重画滚动条,则设置bRedraw为TRUE;
滚动框的位置总是离散的整数值。例如,范围为0至4的滚动条具有5个滚动框位置。可以使用SetScrollPos在滚动条范围内设置新的滚动框位置:
函数:SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
功能:改变滚动框的位置;
参数:hwnd:滚动框所属的窗口的句柄;
iBar:为SB_VERT或者SB_HORZ,
iPos:新的位置,必须在iMin至iMax的范围内。
bRedraw:如果想要Windows根据新范围重画滚动条,则设置bRedraw为TRUE;
Windows提供了类似的函数(GetScrollRange和GetScrollPos)来取得滚动条的目前范围和位置。3.滚动条消息
在用鼠标单击滚动条或者拖动滚动框时,Windows给窗口过程发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个“通知码”。通知码的值由以SB(代表“scroll bar(滚动条)”)开头的标识符定义。
//SYSMETS2.cpp文件,头文件还是文本输出一中的头文件,此处不再列出
#include <Windows.h>
#include "SysMets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
//入口函数
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
static TCHAR szAppName[] = TEXT("SysMets2");
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 = szAppName;
//注册窗口类
if(!RegisterClass(&wndclass))
{
MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR);
return 0;
}
//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名
TEXT("Get System Metric No.2"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL, //窗口风格
CW_USEDEFAULT, //初始X(左上角)位置
CW_USEDEFAULT, //初始Y(左上角)位置
CW_USEDEFAULT, //初始X(宽度)大小
CW_USEDEFAULT, //初始Y(高度)大小
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //作为WM_CREATE消息的附加参数lParam传入的数据指针
//显示窗口
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;
int i,y;
TCHAR szBuffer[10];
HDC hdc;
PAINTSTRUCT PS;
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); //lParam高16位表示显示区域的高度
return 0;
case WM_VSCROLL:
switch(LOWORD(wParam)) //WM_VSCROLL消息的wParam底16位表示通知码
{
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); //此时wParam的高16位表示的是此时滚动框所在的位置
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); //使整个相似区域无效,产生WM_PAINT消息以进行重绘
}
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd,&PS);
for(i = 0; i < NUMLINES; i++)
{
<pre name="code" class="cpp"> //根据滚动框的位置重新计算每一行显示文本的位置,确保滚动框位置所代表的行数出现在显示区域的顶部
y = cyChar * (i - iVscrollPos); TextOut(hdc, 0, y, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));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].iIndex)));SetTextAlign(hdc,TA_LEFT | TA_TOP);}EndPaint(hwnd,&PS);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd,message,wParam,lParam);}
4.滚动条信息函数
函数:SetScrollInfo(hwnd, iBar, &si, bRedraw) ;
功能:设置滚动条结构体信息参数
参数:hwnd:滚动条所在的窗口句柄
iBar: iBar参数是SB_VERT或SB_HORZ,它还可以是用于滚动条控制的SB_CTL;
si:第三个参数是SCROLLINFO结构,定义为:
其中,cbsize参数指定结构大小,
fMask字段设定为一个以SIF前缀开头的标识,指明活动那些数据。例如:当SIF_RANGE作为fMask参数值时,必须把nMin和nMax字段设定为所需的滚动条范围。使用SIF_RANGE旗标时,应把nMin和nMax字段设定为从函数传回的目前范围。SIF_POS标识也一样,必须把结构的nPos字段设定为所需的位置。可以通过GetScrollInfo使用SIF_POS旗标来取得目前位置。当处理带有SB_THUMBTRACK或SB_THUMBPOSITION通知码的WM_VSCROLL或WM_HSCROLL消息时,使用SIF_TRACKPOS标识。从函数的传回中,SCROLLINFO结构的nTrackPos字段将指出目前的32位的滚动块位置。在SetScrollInfo函数中仅使用SIF_DISABLENOSCROLL标识。如果指定了此标识,而且新的滚动条参数使滚动条消失,则该滚动条就不能使用了。SIF_ALL标识是SIF_RANGE、SIF_POS、SIF_PAGE和SIF_TRACKPOS的组合。在WM_SIZE消息处理期间设置滚动条参数时,这是很方便的(在SetScrollInfo函数中指定SIF_TRACKPOS后,它会被忽略)。
在上例中,滚动条范围设置最小为0,最大为NUMLINES-1。当滚动条位置是0时,第一行信息显示在显示区域的顶部;当滚动条的位置是NUMLINES-1时,最后一行显示在显示区域的顶部,并且看不见其它行。这说明滚动条范围太大。事实上只需把信息最后一行显示在显示区域的底部而不是顶部即可。我们可以对上例作出一些修改以达到此点。当处理WM_CREATE消息时不设置滚动条范围,而是等到接收到WM_SIZE消息后再做此工作:
iVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, TRUE) ;
假定NUMLINES等于75,并假定特定窗口大小是:50(cyChar除以cyClient)。换句话说,我们有75行信息但只有50行可以显示在显示区域中。使用上面的两行程序代码,把范围设置最小为0,最大为25。当滚动条位置等于0时,程序显示0到49行。当滚动条位置等于1时,程序显示1到50行;并且当滚动条位置等于25(最大值)时,程序显示25到74行。很明显需要对程序的其它部分做出修改,但这是可行的。
而这个滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做掉了一大堆杂事。可以像下面的程序代码一样使用SCROLLINFO结构和SetScrollInfo:
si.cbSize = sizeof (SCROLLINFO) ;
si.cbMask = SIF_RANGE | SIF_PAGE ;
si.nMin = 0 ;
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
这样做之后,Windows会把最大的滚动条位置限制为si.nMax - si.nPage +1而不是si.nMax。像前面那样做出假设:NUMLINES等于75 (所以si.nMax等于74),si.nPage等于50。这意味着最大的滚动条位置限制为74 - 50 + 1,即25。这正是我们想要的。当页面大小与滚动条范围一样大时,会发生什么情况呢?在这个例子中,就是nPage等于75或更大的情况。Windows通常隐藏滚动条,因为它并不需要。如果不想隐藏滚动条,可在呼叫SetScrollInfo时使用SIF_DISABLENOSCROLL,Windows只是让那个滚动条不能被使用,而不隐藏它。
示例程序3.新滚动条设置函数处理滚动条:
#include <Windows.h>
#include "SysMets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
//入口函数
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
static TCHAR szAppName[] = TEXT("SysMets3");
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 = szAppName;
//注册窗口类
if(!RegisterClass(&wndclass))
{
MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR);
return 0;
}
//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名
TEXT("Get System Metric No.3"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, //窗口风格
CW_USEDEFAULT, //初始X(左上角)位置
CW_USEDEFAULT, //初始Y(左上角)位置
CW_USEDEFAULT, //初始X(宽度)大小
CW_USEDEFAULT, //初始Y(高度)大小
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //作为WM_CREATE消息的附加参数lParam传入的数据指针
//显示窗口
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,cxClient,cyClient, iMaxWidth;
int i,x,y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;
TCHAR szBuffer[10];
HDC hdc;
PAINTSTRUCT PS;
TEXTMETRIC tm;
SCROLLINFO si;
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);
iMaxWidth = 40 * cxChar + 22 * cxCaps; //显示区域字符最大宽度
return 0;
case WM_SIZE:
cxClient = LOWORD(lParam); //水平显示区域大小
cyClient = HIWORD(lParam); //竖直显示区域大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE; //此标志设置了获取(设置)滚动条的范围和页面大小。
si.nMin = 0;
si.nMax = NUMLINES - 1;
si.nPage = cyClient / cyChar;
SetScrollInfo(hwnd,SB_VERT,&si,TRUE); //该函数会把最大的滚动条位置限制为si.nMax - si.nPage + 1,而不是si.nMax
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE; //此标志设置了获取(设置)滚动条的范围和页面大小。
si.nMin = 0;
si.nMax = 2 + iMaxWidth / cxChar;
si.nPage = cxClient / cxChar;
SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); //设置水平滚动条的范围和水平页面的大小
return 0;
case WM_VSCROLL:
si.cbSize = sizeof(si);
si.fMask = SIF_ALL; //SIF_RANGE、SIF_POS、SIF_PAGE和SIF_TRACKPOS的组合
GetScrollInfo(hwnd,SB_VERT,&si); //获取竖直滚动条的相关参数
iVertPos = 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(si.nPos != iVertPos)
{
ScrollWindow(hwnd,0,cyChar * (iVertPos - si.nPos),NULL,NULL);
UpdateWindow(hwnd);
}
return 0;
case WM_HSCROLL:
si.cbSize = sizeof(si);
si.fMask = SIF_ALL; //SIF_RANGE、SIF_POS、SIF_PAGE和SIF_TRACKPOS的组合
GetScrollInfo(hwnd,SB_HORZ,&si); //获取竖直滚动条的相关参数
iHorzPos = 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;
default:
break;
}
si.fMask = SIF_POS;
SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
GetScrollInfo(hwnd,SB_HORZ,&si);
if(si.nPos != iHorzPos)
{
ScrollWindow(hwnd,cxChar * (iHorzPos - si.nPos),0,NULL,NULL);
}
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd,&PS);
si.cbSize = sizeof(&si);
si.fMask = SIF_POS;
GetScrollInfo(hwnd,SB_VERT,&si);
iVertPos = si.nPos;
GetScrollInfo(hwnd,SB_HORZ,&si);
iHorzPos = si.nPos;
iPaintBeg = max(0,iVertPos + PS.rcPaint.top / cyChar);
iPaintEnd = min(NUMLINES - 1,iVertPos + PS.rcPaint.bottom / cyChar);
for(i = iPaintBeg; i < iPaintEnd; i++)
{
x = cxChar * (1 - iHorzPos);
y = cyChar * (i - iVertPos);
TextOut(hdc, x, y, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));
TextOut(hdc, x + 22 * cxCaps, y, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));
SetTextAlign(hdc,TA_RIGHT | TA_TOP);
TextOut(hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].iIndex)));
SetTextAlign(hdc,TA_LEFT | TA_TOP);
}
EndPaint(hwnd,&PS);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}