先讲怎么用普通的方法设置滚动条。重点是设置滚动条而不是文本输出,所以示例程序的文本很简洁。
说到设置滚动条我们就会想到三点:
1.用鼠标拖动滑块使客户区内容重绘
2.用键盘控制滑块移动
3.用鼠标滚轮控制滑块移动
那么我们就把这三个功能都给用上
用到的函数有以下几个:
//设置滚动条范围
//返回TRUE表示成功,FALSE表示失败
BOOL SetScrollRange(
HWND hWnd, //窗口句柄
int nBar, //滚动条类型
int nMinPos, //滚动条的最小位置
int nMaxPos, //滚动条的最大位置
BOOL bRedraw //重绘标志
);
//滚动条位置
//0表示失败,返回先前位置的值表示成功
int SetScrollPos(
HWND hWnd, //窗口句柄
int nBar, //滚动条类型
int nPos, //滚动条新位置
BOOL bRedraw // 重绘标志
);
//向窗口过程发送消息
LRESULT SendMessage(
HWND hWnd, //接收消息的窗口句柄
UINT Msg, //发送的消息
WPARAM wParam, //附加消息
LPARAM lParam //附加消息
);
//该函数查询或设置系统级参数。该函数也可以在设置参数中更新用户配置文件。
BOOL SystemParametersInfo(
UINT uiAction, // 该参数指定要查询或设置的系统级参数,MSDN有详细介绍
UINT uiParam, //与查询或设置的系统参数有关。
PVOID pvParam, //与查询或设置的系统参数有关
UINT fWinIni //如果设置系统参数,则它用来指定是否更新用户配置文件(Profile)
);
知道并懂得怎么用这些函数后就可以开始设置我们的滚动条了。一般滚动条分为水平滚动条和垂直滚动条。所以一般我们有一下几个步骤来设置滚动条:
1.向需要设置滚动条的窗口添加滚动条风格(WS_HSCROLL|WS_VSCROLL)这个是最主要的,因为你不添加这个风格无论你在窗口过程写多少代码都是无用功。
2.在WM_CREATE消息中获取当前字体大小。
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
cyChar = tm.tmHeight + tm.tmExternalLeading;
3.在WM_SIZE消息中获取客户区宽度和高度:
cxClient=LOWORD(lParam);//宽度
cyClient=HIWORD(lParam);//高度
4.在WM_消息中设置滚动条范围和初始位置
SetScrollRange
SetScrollPos
5.在WM_VSCROLL和WM_HSCROLL消息中设置新的滑块位置
6.在WM_KETDOWN消息中利用SendMessage发送消息给WM_VSCROLL和WM_HSCROLL以设置新的滑块位置
7.滚轮滑动利用SendMessage发送消息给WM_VSCROLL和WM_HSCROLL以设置新的滑块位置
8.在WM_PAINT消息中显示滚动滑块后的内容
基本就以上8点就可以设置滚动条了。细节更改可以自己研究.
设置滚动条代码如下:
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//全局变量存储错误信息
DWORD dwError = 0;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Scroll");
HWND hwnd=nullptr;
MSG msg = {0};
WNDCLASS wndclass = {0};
//创建窗口类
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = nullptr;
wndclass.lpszClassName = szAppName;
//注册窗口类
if (!RegisterClass(&wndclass))
{
MessageBox(nullptr, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
//创建窗口
hwnd = CreateWindow(szAppName, TEXT("ScrollDemo"),
WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL,//添加滚动条风格
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, hInstance, nullptr);
if (!hwnd)
{
dwError = GetLastError();
return 0;
}
//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);
//获取队列中的消息
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//字体的宽度,高度,cxCaps是宽字符的宽度
static int cxChar = 0, cxCaps = 0, cyChar = 0;
//滚动条的垂直位置和水平位置
static int iVertPos = 0, iHorzPos = 0;
//获取当前客户区可容纳的行数和列数
static int cxColumn = 0, cyLine = 0;
//客户区宽度和高度
static int cxClient = 0, cyClient = 0;
//鼠标滑动信息
static int iDeltaPerLine = 0, iAccumDelta = 0;
ULONG ulScrollLines = 0;
int i = 0, temp = 0, ix = 0, iy = 0;
//设备环境句柄
HDC hdc = nullptr;
//绘图信息结构
PAINTSTRUCT ps = { 0 };
//字体信息结构
TEXTMETRIC tm = { 0 };
switch (message)
{
case WM_CREATE:
hdc = GetDC(hwnd);
//获取字体信息,一般系统默认
GetTextMetrics(hdc, &tm);
//字符宽度
cxChar = tm.tmAveCharWidth;
//判断是否是宽字符,宽字符就是1.5cxChar
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
//字符高度
cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
case WM_SETTINGCHANGE:
//SPI_GETWHEELSCROLLLINES:用于Windows NT 4.0及以后版本、
//Windows 98。当前轨迹球转动时,获取滚动的行数。参数pvParam
//必须指向UINT类型变量以接收行数。缺省值是3。
if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0))
{
dwError = GetLastError();
break;
}
// 增量数值用WHEEL_DELTA来标识,它等于120
if (ulScrollLines)//每滚动一行的增量是40
iDeltaPerLine = WHEEL_DELTA / ulScrollLines;
else
iDeltaPerLine = 0;
return 0;
case WM_SIZE:
//获取客户区宽度和高度,每当你改变窗口大小的时候它
//都会发生改变。
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
//获取当前客户区可容纳的列数
cxColumn = cxClient / cxChar;
//获取当前客户区可容纳的行数
cyLine = cyClient / cyChar;
//设置垂直滚动条范围
if (!SetScrollRange(hwnd, SB_VERT, 0, cyClient / cyChar, FALSE))
{
dwError = GetLastError();
break;
}
//设置垂直滚动条初始位置
SetScrollPos(hwnd, SB_VERT, iVertPos, TRUE);
//设置水平滚动条范围
if (!SetScrollRange(hwnd, SB_HORZ, 0, cxClient / cxChar, TRUE))
{
dwError = GetLastError();
break;
}
//设置垂直滚动条初始位置
SetScrollPos(hwnd, SB_HORZ, iHorzPos, TRUE);
return 0;
//垂直滚动条消息
case WM_VSCROLL:
//记录未改变时的滑块位置
temp = iVertPos;
//接收到用户拖动滑块消息
switch (LOWORD(wParam))
{
//向上移动一个单位
case SB_LINEUP:
iVertPos -= 1;
break;
//想下移动一个单位
case SB_LINEDOWN:
iVertPos += 1;
break;
//翻上一页
case SB_PAGEUP:
iVertPos -= cyClient / cyChar;
break;
//翻下一页
case SB_PAGEDOWN:
iVertPos += cyClient / cyChar;
break;
case SB_TOP:
iVertPos = 0;
break;
case SB_BOTTOM:
iVertPos = cyClient / cyChar;
break;
case SB_THUMBTRACK:
iVertPos = HIWORD(wParam);
break;
default:
break;
}
//防止范围超出
iVertPos = max(0, min(iVertPos, cyClient / cyChar));
//当滑块改变的时候重新显示它
if (temp != iVertPos)
{
//设置新的滑块位置
SetScrollPos(hwnd, SB_VERT, iVertPos, TRUE);
//使客户区无效,以便重绘它
InvalidateRect(hwnd, nullptr, TRUE);
}
return 0;
//水平滚动条消息
case WM_HSCROLL:
//记录未改变时的滑块位置
temp = iHorzPos;
//接收到用户拖动滑块消息
switch (LOWORD(wParam))
{
//向左移动一个单位
case SB_LINELEFT:
iHorzPos -= 1;
break;
//想下移动一个单位
case SB_LINERIGHT:
iHorzPos += 1;
break;
//翻上一页
case SB_PAGELEFT:
iHorzPos -= cyClient / cyChar;
break;
//翻下一页
case SB_PAGERIGHT:
iHorzPos += cyClient / cyChar;
break;
case SB_THUMBTRACK:
iHorzPos = HIWORD(wParam);
break;
default:
break;
}
//防止范围超出
iHorzPos = max(0, min(iHorzPos, cxClient / cxChar));
//当滑块改变的时候重新显示它
if (temp != iHorzPos)
{
//设置新的滑块位置
SetScrollPos(hwnd, SB_HORZ, iHorzPos, TRUE);
//使客户区无效,以便于重绘
InvalidateRect(hwnd, nullptr, TRUE);
}
return 0;
//键盘消息
case WM_KEYDOWN:
//如果有按下方向键
switch (LOWORD(wParam))
{
//如果按下方向键上
case VK_UP:
//想窗口过程发送消息
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
break;
case VK_DOWN:
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
break;
case VK_LEFT:
SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
break;
case VK_RIGHT:
SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
break;
//PageUp
case VK_PRIOR:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
break;
//PageDown
case VK_NEXT:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
//Bottom
case VK_END:
SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
break;
case VK_HOME:
SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
break;
default:
break;
}
return 0;
//鼠标滚轮消息
case WM_MOUSEWHEEL:
//如果增量等于0就是没有滚动就退出
if (iDeltaPerLine == 0)
break;
iAccumDelta += (short)HIWORD(wParam); // 120 or -120
while (iAccumDelta >= iDeltaPerLine)
{
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
iAccumDelta -= iDeltaPerLine;
}
while (iAccumDelta <= -iDeltaPerLine)
{
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
iAccumDelta += iDeltaPerLine;
}
return 0;
//重绘
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
for (;i < cyLine;i++)
{
//当滑块位置改变时,输出的起始位置也随之改变
//其原理就是把不需要输出的输出到屏幕外面
ix = cxChar*(1 - iHorzPos);
iy = cyChar * (i - iVertPos);
TextOutW(hdc, ix, iy, L"一二三四五六", wcslen(L"一二三四五六"));
}
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
代码中已经有很详细的注释了,所以这里大概再理清一下代码就行了。
1.
当我们创建窗口的时候会先触发WM_CREATE消息,这时,字体大小信息被获取到了,因为没有这个字体大小的信息,你根本不懂一个客户区可以容纳的行数和列数。
2.
接着会触发WM_SIZE消息,我们的客户区大小也能获取到了,然后就可以设置滚动条的范围和初始位置了。
3.
将滚动条初始化后就会触发WM_PAINT消息,文本和滚动条就会在窗口显示了。但是WM_HSCROLL和WM_VSCROLL这两个消息还没被触发,因为你并没有移动滑块。
4.
当你移动滑块后就会触发对应的水平或垂直滚动条消息,接着滑块的位置就会在里面被改变。
5.最后就会被重绘在客户区中。
键盘消息和鼠标滚轮消息对滑块的改变是通过SendMessage函数发送对应的消息到滚动条消息中,这样就可以达到键盘鼠标移动滑块的效果了。
滚动条消息如下:
#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_THUMBTRACK 和SB_THUMBPOSITION只能处理一个,前者是设置滑块及时显示,后者是设置滑块被释放后显示.
WM_VSCROLL和WM_HSCROLL中的wParam和lParam参数:
wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。
而一般lParam在控件窗口中才有意义,所以这里不必管它.
关于虚拟键消息可以百度。这里不详细讲。滚动条的设置还可以利用一个滚动条信息结构和两个函数。这种方法是比较常用的,下次会详细讲解它。