转自:https://blog.csdn.net/hd770c/article/details/17975927
https://www.cnblogs.com/javawebsoa/p/3226173.html
一、不可变长的滚动条
所需四个重要函数:
- 设置滚动条的范围:SetScrollRange
- 设置滚动滑块的位置:SetScrollPos
- 获取滚动条的范围:GetScrollRange
- 获取滚动滑块的位置:GetScrollPos
图片是最终的窗口效果,下面用一张图来讲解滚动条的作用和区域设置。
黄线矩形为窗口区域,红线区域为图片大小,只有当图片的宽或者高大于客户区的时候才需要相应的滚动条,滚动条的作用即为显示图片在客户区以外的部分,将滚动条位置和范围与像素关联,水平滚动条的最大范围即为cxBitmap-cxClient,最小范围当然是0啦,当滚动条位置发生变化时,图片进行相应的偏移,窗口的大小发生变化时,即cxClient发生变化,需要重新设置水平滚动条的范围,并判断当前滚动条位置是否在新的cxBitmap-cxClient范围内,如果超出范围,需要进行纠正;垂直滚动条的调整原理和水平是一样的。
实例整体很简单,定义一个典型的WINDOWS窗体:1.定义窗体属性;2.定义窗体处理程序;3.循环处理进程消息队列。详见代码注释。
注意:图片显示移动的方向和滚动条的滚动方向应该是相反的,所以代码中你会看到贴位图的位置是一个负值
BitBlt(hdc, -iHScrollBarPos, -iVScrollBarPos, cxBitmap, cyBitmap, hdcSrc, 0, 0, SRCCOPY);
#include <Windows.h>
//WINDOWS程序的标准范式:定义WINDOWS入口函数WinMain
//注册一个窗口对象
//定义窗口的属性,即WNDCLASS结构的队员
//定义窗口对应的处理函数,即WndProc回调函数
//WinMain中循环处理进程消息队列
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("AppName");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
int cxclient, cyclient;
wndclass.hInstance = hInstance;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.cbClsExtra = NULL;
wndclass.cbWndExtra = NULL;
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.lpfnWndProc = WndProc;
wndclass.style = CS_HREDRAW|CS_VREDRAW;
//窗口在创建之前需要先注册窗口类
if(!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("Need Windows NT"), TEXT(""),MB_HELP);
return 0;
}
//获取屏幕属性的一个很有用的函数,F12可以看到详细用法,这里是获取屏幕的宽和高
cxclient = GetSystemMetrics(SM_CXSCREEN);
cyclient = GetSystemMetrics(SM_CYSCREEN);
hwnd = CreateWindow(szAppName,
TEXT(""), //窗口标题,可以设置为空,TEXT()为微软提供的字节处理宏,当定了_UNICODE的时候即代表了UNICODE字符,否则为ASC11字符
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, //第一个宏为窗口有标题栏,有最大化,有最小化,有退出,后面两个指定了窗口具有垂直和水平滚动条
cxclient/3, //设定窗口起点为屏幕三分之一高和三分之一宽
cyclient/3,
cxclient/3, //设定窗口宽和高分别为屏幕的三分之一
cyclient/3,
NULL,
NULL,
NULL, //hInstance
NULL);
//创建窗口以后显示窗口
ShowWindow(hwnd, TRUE);
UpdateWindow(hwnd);
//GetMessage消息循环从进程消息队列中匹配消息并转换后分发给对应窗口,窗口收到消息后调用注册的窗口处理函数处理翻译后的消息。
//需要注意的是,如果想要从进程消息队列中获取所有消息,需要第二个参数设置成NULL,否则GetMessage无法获取WM_QUIT消息,即无法退出程序。
//WM_QUIT属于进程但不属于进程的任意一个窗口,只有第二个参数NULL可以获取,GetMessage获取WM_QUIT后进程退出
while(GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
static int cxClient, cyClient, cxBitmap, cyBitmap;
static HBITMAP bitmap;
static int iHScrollBarPos, iVScrollBarPos;
switch(message)
{
case WM_CREATE:
BITMAP bmpinfo;
bitmap = (HBITMAP)LoadImage(NULL, TEXT("view.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if(!bitmap)
{
MessageBox(hwnd, TEXT("Load Image Error"), TEXT("My Demo"), MB_ICONERROR);
}
GetObject(bitmap, sizeof(BITMAP), &bmpinfo);
cxBitmap = bmpinfo.bmWidth;
cyBitmap = bmpinfo.bmHeight;
break;
case WM_PAINT:
HDC hdc, hdcSrc;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
hdcSrc = CreateCompatibleDC(hdc);
SelectObject(hdcSrc, bitmap);
BitBlt(hdc, -iHScrollBarPos, -iVScrollBarPos, cxBitmap, cyBitmap, hdcSrc, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
DeleteDC(hdcSrc);
break;
case WM_SIZE:
//窗口大小改变时收到此消息,F12可查看消息参数意思,msdn查到获取新窗口宽和高的方法
cxClient = LOWORD(lparam);
cyClient = HIWORD(lparam);
//设定滚动条的范围
SetScrollRange(hwnd, SB_HORZ, 0, cxBitmap - cxClient, FALSE);
SetScrollRange(hwnd, SB_VERT, 0, cyBitmap - cyClient, FALSE);
//获取滚动条的新位置,即判断窗口大小改变以后滚动条是否超出了应有的最大范围
iHScrollBarPos = min(cxBitmap - cxClient, max(0, iHScrollBarPos));
iVScrollBarPos = min(cyBitmap - cyClient, max(0, iHScrollBarPos));
//如果滚动条超过了最大范围,则重设滚动条范围并刷新窗口
if(iHScrollBarPos != GetScrollPos(hwnd, SB_HORZ))
{
SetScrollPos(hwnd, SB_HORZ, iHScrollBarPos, TRUE);
InvalidateRect(hwnd, NULL, FALSE);
}
if(iVScrollBarPos != GetScrollPos(hwnd, SB_VERT))
{
SetScrollPos(hwnd, SB_VERT, iVScrollBarPos, SB_VERT);
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_VSCROLL:
//垂直滚动条消息,F12查看消息可以从MSDN获取消息详细参数
switch(LOWORD(wparam))
{
case SB_LINEUP:
//每次滚动图片变化10个像素
iVScrollBarPos -= 10;
break;
case SB_LINEDOWN:
iVScrollBarPos += 10;
break;
case SB_PAGEUP:
//每次翻页都滚动一整个客户区的大小
iVScrollBarPos -= cyClient;
break;
case SB_PAGEDOWN:
iVScrollBarPos += cyClient;
break;
case SB_THUMBTRACK:
iVScrollBarPos = HIWORD(wparam);
break;
default :
break;
}
//判断滚动后的滚动条是否超过最大值或最小值,如果超过最大值或者最小值,则取最大值或0,否则等于当前值
iVScrollBarPos = min(cyBitmap - cyClient, max(0, iVScrollBarPos));
//如果滚动条位置发生变化,则设置滚动条位置和刷新屏幕
if(iVScrollBarPos != GetScrollPos(hwnd, SB_VERT))
{
SetScrollPos(hwnd, SB_VERT, iVScrollBarPos, TRUE);
//最后参数设置为FALSE可以大幅度减少屏幕闪烁,可以尝试一下。
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_HSCROLL:
switch(LOWORD(wparam))
{
case SB_LINEUP:
iHScrollBarPos -= 10;
break;
case SB_LINEDOWN:
iHScrollBarPos += 10;
break;
case SB_PAGEUP:
iHScrollBarPos -= cxClient;
break;
case SB_PAGEDOWN:
iHScrollBarPos += cxClient;
break;
case SB_THUMBTRACK:
iHScrollBarPos = HIWORD(wparam);
break;
default :
break;
}
iHScrollBarPos = min(cxBitmap - cxClient, max(0, iHScrollBarPos));
if(iHScrollBarPos != GetScrollPos(hwnd, SB_HORZ))
{
SetScrollPos(hwnd, SB_HORZ, iHScrollBarPos, TRUE);
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_DESTROY:
//点击窗口右上角的X,系统会向窗口发出WM_DESTROY消息,该消息并不能退出窗口,真正退出窗口的是WM_QUIT消息,调用PostQuitMessage向进程消息队列发送WM_QUIT消息
PostQuitMessage(1);
DeleteObject(bitmap);
return 0;
break;
default :
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
二、可变长的滚动条
Win32的标准风格
它要稍微复杂点儿,它同样有一套API来维护,以下是相关的API函数:
设定滚动条信息
int SetScrollInfo( HWND hwnd, // 窗口句柄 int fnBar, // 滚动条类型 LPCSCROLLINFO lpsi, // 滚动条信息结构体(稍后详解) BOOL fRedraw // 是否重绘滚动条方块 );
获取滚动条信息
BOOL GetScrollInfo( HWND hwnd, // handle to window int fnBar, // scroll bar type LPSCROLLINFO lpsi // scroll bar parameters );
滚动窗口(这里是指窗口的客户区)
BOOL ScrollWindow( HWND hWnd, // handle to window int XAmount, // 水平滚动距离 int YAmount, // 垂直滚动距离 CONST RECT *lpRect, // 滚动区域范围(一般就是设为NULL,指客户区) CONST RECT*lpClipRect // 剪裁区域(今天用不到,设定为NULL) );
SCROLLINFO结构体
typedef struct tagSCROLLINFO { UINT cbSize;//SCROLLINFO类型大小(主要是windows为了以后能兼容),也就是sizeof(SCROLLINFO) UINT fMask; //设定滚动条需要设置的参数 int nMin; //滚动条上边界 int nMax; //滚动条下边界 UINT nPage; //每一页的大小(主要用于计算滚动条块的大小) int nPos; //滚动条的位置 int nTrackPos; //滚动条滚动的位置 } SCROLLINFO, *LPSCROLLINFO; typedef SCROLLINFO CONST *LPCSCROLLINFO;
我们先来看看它的效果:
你会发现随着窗口大小的变化,滚动条方块也随着变化了,它变化的依据如下:
有一点需要注意的是:windows帮你做了以下一个设计 —— 滚动条实际滚动的范围是:nMax - nPage + 1,这主要是避免过多的滚动,当显示内容在后一行在客户区最后一行就行了,所以我们只需要从我们的视角来设定nMin和nMax,不要自己去考虑滚动条滚动的实际范围,windows都为我们做好了。
主要代码详解:
初始化滚动条参数
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
//设定垂直滚动条范围和页面大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = cyBitmap;
si.nPage = cyClient;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
//设定水平滚动条范围和页面大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = cxBitmap;
si.nPage = cxClient;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
return 0;
处理滚动条消息(同样也实现垂直的)
//处理垂直滚动条消息
case WM_VSCROLL:
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos;
switch(LOWORD(wParam))
{
case SB_TOP: //置顶(先按下Shift键不放,然后点击滚动条方块上侧区域就能置顶)
si.nPos = si.nMin ;
break ;
case SB_BOTTOM://置底(同置顶)
si.nPos = si.nMax ;
break ;
case SB_LINEUP:
si.nPos -= 10 ;//每一行滚动10个像素
break ;
case SB_LINEDOWN:
si.nPos += 10 ;
break ;
case SB_PAGEUP://翻页就是一个客户区大小
si.nPos -= cyClient ;
break ;
case SB_PAGEDOWN:
si.nPos += cyClient ;
break ;
case SB_THUMBTRACK:
si.nPos = HIWORD (wParam) ;
break ;
default :
break ;
}
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
if(iVertPos != si.nPos)
{
//这里InvalidateRect、ScrollWindow,效果相同
InvalidateRect(hwnd, NULL, FALSE);
//ScrollWindow(hwnd, 0, iVertPos - si.nPos, NULL, NULL);
}
return 0;