WIN32滚动条的创建和使用

转自:https://blog.csdn.net/hd770c/article/details/17975927

https://www.cnblogs.com/javawebsoa/p/3226173.html

一、不可变长的滚动条

所需四个重要函数:

  1. 设置滚动条的范围:SetScrollRange
  2. 设置滚动滑块的位置:SetScrollPos
  3. 获取滚动条的范围:GetScrollRange
  4. 获取滚动滑块的位置: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;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值