关于滚动条的学习

最近在工作中遇到了滚动条的问题。但是不是很懂,于是写下记录学习一下。
我们使用滚动条可以查看超过窗口之外的数据。关于滚动条的详细情况可以直接查看微软的官方文档。滚动条
我们首先来介绍一下滚动条:窗口可以显示大于窗口工作区的数据。例如文档和位图。提供滚动条后,我们可以滚动数据对象,以便查看超出窗口的部分。
1.滚动条的定义:滚动条由两端有箭头按钮的阴影轴和一个 滚动框 (有时称为箭头按钮之间的拇指) 组成。
2.有关滚动条的函数介绍如下:函数说明

// 启用或禁用一个或两个滚动条箭头。
BOOL EnableScrollBar(
  [in] HWND hWnd,
  [in] UINT wSBflags,
  [in] UINT wArrows
);
// 函数检索有关指定滚动条的信息。
BOOL GetScrollBarInfo(
  [in]  HWND           hwnd,
  [in]  LONG           idObject,
  [out] PSCROLLBARINFO psbi
);
// 检索滚动条的参数,包括最小和最大滚动位置、页面大小以及滚动框的位置 (拇指) .
BOOL GetScrollInfo(
  [in]      HWND         hwnd,
  [in]      int          nBar,
  [in, out] LPSCROLLINFO lpsi
);
// 函数检索指定滚动条中滚动框的当前位置 (拇指) 。 当前位置是一个相对值,取决于当前滚动范围。 例如,如果滚动范围为 0 到 100,并且滚动框位于条中间,则当前位置为 50。
int GetScrollPos(
  [in] HWND hWnd,
  [in] int  nBar
);
// 检索当前最小和最大滚动框, (拇指) 指定滚动条的位置。
BOOL GetScrollRange(
  [in]  HWND  hWnd,
  [in]  int   nBar,
  [out] LPINT lpMinPos,
  [out] LPINT lpMaxPos
);
// 水平和垂直滚动一个位矩形。
BOOL ScrollDC(
  [in]  HDC        hDC,
  [in]  int        dx,
  [in]  int        dy,
  [in]  const RECT *lprcScroll,
  [in]  const RECT *lprcClip,
  [in]  HRGN       hrgnUpdate,
  [out] LPRECT     lprcUpdate
);
// 滚动指定窗口的工作区的内容。// 新应用程序应使用 ScrollWindowEx 函数。
BOOL ScrollWindow(
  [in] HWND       hWnd,
  [in] int        XAmount,
  [in] int        YAmount,
  [in] const RECT *lpRect,
  [in] const RECT *lpClipRect
);
// 设置滚动条的参数,包括最小和最大滚动位置、页面大小和滚动框的位置 (拇指) 。 如果请求,该函数还会重新绘制滚动条
int SetScrollInfo(
  [in] HWND          hwnd,
  [in] int           nBar,
  [in] LPCSCROLLINFO lpsi,
  [in] BOOL          redraw
);
// 显示或隐藏指定的滚动条。
BOOL ShowScrollBar(
  [in] HWND hWnd,
  [in] int  wBar,
  [in] BOOL bShow
);

3.与滚动条条有关的消息:SBM_ENABLE_ARROWS、 SBM_GETPOS、SBM_GETRANGE、SBM_GETSCROLLBARINFO、SBM_SETPOS、SBM_SETRANGE、SBM_SETRANGEREDRAW、SBM_SETSCROLLINFO.各个消息的具体细节请参考官方文档:
滚动条消息
4. 与滚动条条有关的通知消息:WM_CTLCOLORSCROLLBAR 、WM_HSCROLL、WM_VSCROLL。
5. 与滚动条条有关的结构体:SCROLLBARINFO 、SCROLLINFO。
二、关于滚动条的一些介绍:关于滚动条

1.滚动条的各个部分:
滚动条由一个阴影轴组成,每个端都有一个箭头按钮,一个 滚动框 (有时称为拇指) 箭头按钮之间。
滚动条表示窗口工作区中数据对象的总体长度或宽度;滚动框表示对象在工作区中可见的部分。系统还会调整滚动条滚动框的大小,以便指示整个数据对象的哪个部分当前在窗口中可见。 如果大多数对象可见,则滚动框将占用滚动条轴的大部分。 同样,如果只有一小部分对象可见,则滚动框将占用滚动条轴的一小部分。
如下图所示的滚动条:
在这里插入图片描述[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ApjQqM9w-1688916673872)(https://imgblog.csdnimg.cn/d72a834017224b5fb8ba603a0164f148.png)]

2.标准滚动条和滚动条控件
标准滚动条:滚动条作为标准滚动条或滚动条控件包含在窗口中。 标准滚动条位于窗口的非工作区中。 它是使用 窗口创建的,并在显示窗口时显示。 标准滚动条的唯一用途是使用户能够生成滚动请求来查看工作区的整个内容。创建窗口时,可以通过指定 WS_HSCROLL样式、 WS_VSCROLL样式或同时指定两种样式,在窗口中包括标准滚动条。
滚动条控件:滚动条控件是属于 SCROLLBAR 窗口类的控件窗口。 此时将显示一个滚动条控件,其功能类似于标准滚动条,但它是一个单独的窗口。 滚动条控件作为单独的窗口直接获取输入焦点。 与标准滚动条不同,滚动条控件还具有内置键盘界面。

3.滚动框位置和滚动范围
滚动框的位置表示为整数; 位置必须在滚动范围的最小值和最大值内。 标准滚动条的初始范围为 0 到 100;可以使用 SetScrollInfo 函数设置范围值,使用 GetScrollInfo 函数检索当前区域值。可以设置滚动条的页面大小。 页面大小表示根据所有者窗口的当前大小,可以容纳在工作区中的数据单位数。 系统使用页面大小以及滚动条轴的滚动范围和长度来设置滚动框的大小。 每当调整包含滚动条的窗口的大小时,应用程序都应调用 SetScrollInfo 函数来设置页面大小。 应用程序可以通过调用发送 GetScrollInfo 函数来检索当前页大小。滚动条可以报告的最大值 (,即最大滚动位置) 取决于页面大小。 如果滚动条的页面大小大于 1,则最大滚动位置小于最大范围值。 可以使用以下公式计算最大滚动位置:
MaxScrollPos = MaxRangeValue - (PageSize - 1)。
4.滚动条的请求(详细的解释可以参考微软的官方文档:滚动套)
用户通过单击滚动条的各个部分发出滚动请求。 系统以 WM_HSCROLL 或 WM_VSCROLL 消息的形式将请求发送到指定的窗口。 水平滚动条发送 WM_HSCROLL 消息;垂直滚动条发送 WM_VSCROLL 消息。 每条消息都包含一个请求代码,该代码对应于用户的操作、滚动条的句柄 (滚动条控件仅) ,在某些情况下,还对应于滚动框的位置。
下图显示了用户在单击滚动条的各个部分时生成的请求代码。
在这里插入图片描述
SB_值指定用户执行的操作。 应用程序检查 WM_HSCROLL 附带的代码并 WM_VSCROLL 消息,然后执行相应的滚动操作。 下表为每个值指定了用户操作,后跟应用程序的响应。 在每种情况下,应用程序都会根据数据定义一个单元。 例如,垂直滚动文本的典型单位是一行文本。
SB_值指定用户执行的操作。 应用程序检查 WM_HSCROLL 附带的代码并 WM_VSCROLL 消息,然后执行相应的滚动操作。 下表为每个值指定了用户操作,后跟应用程序的响应。 在每种情况下,应用程序都会根据数据定义一个单元。 例如,垂直滚动文本的典型单位是一行文本。

请求 操作 响应
SB_LINEUP 用户单击顶部滚动箭头。 递减滚动框位置;向数据顶部滚动一个单位。
SB_LINEDOWN 用户单击底部滚动箭头。 递增滚动框位置;向数据底部滚动一个单位。
SB_LINELEFT 用户单击向左滚动箭头。 递减滚动框位置;向数据左端滚动一个单位。
SB_LINERIGHT 用户单击向右滚动箭头。 递增滚动框位置;向数据右端滚动一个单位。
SB_PAGEUP 用户单击滚动框上方的滚动条轴。 按窗口中的数据单位数递减滚动框位置;按相同数量的单位向数据顶部滚动。
SB_PAGEDOWN 用户单击滚动框下方的滚动条轴。 按窗口中的数据单位数递增滚动框位置;按相同数量的单位向数据底部滚动。
SB_PAGELEFT 用户单击滚动框左侧的滚动条轴。 按窗口中的数据单位数递减滚动框位置;向数据左端滚动,单位数相同。
SB_PAGERIGHT 用户单击滚动框右侧的滚动条轴。 按窗口中的数据单位数递增滚动框位置;向数据右端滚动,单位数相同。
SB_THUMBPOSITION 用户拖动滚动框后释放它。 将滚动框设置为消息中指定的位置;按滚动框移动的相同数量来滚动数据。
SB_THUMBTRACK 用户拖动滚动框。 将滚动框设置为消息中指定的位置,并按滚动框为快速绘制数据的应用程序移动的相同单位数滚动数据。 无法快速绘制数据的应用程序必须在移动滚动框和滚动数据之前等待SB_THUMBPOSITION请求代码。
SB_ENDSCROLL 用户按住箭头或滚动条轴上的鼠标后松开鼠标。 无需响应。

当用户单击并拖动滚动框时,滚动条会生成SB_THUMBPOSITION和SB_THUMBTRACK请求代码。 应将应用程序编程为处理SB_THUMBTRACK或SB_THUMBPOSITION请求代码。

当用户单击滚动框后松开鼠标按钮时,将发生SB_THUMBPOSITION请求代码。 处理此消息的应用程序在用户将滚动框拖动到所需位置并释放鼠标按钮后执行滚动操作。

SB_THUMBTRACK请求代码在用户拖动滚动框时发生。 如果应用程序处理SB_THUMBTRACK请求代码,则可以在用户拖动滚动框时滚动窗口的内容。 但是,滚动条可以在短时间内生成许多SB_THUMBTRACK请求代码,因此,仅当应用程序可以快速重新绘制窗口内容时,才应处理这些请求代码。
下面直接以实力来说明具体的用法:
具体的代码如下:

#include <iostream>
#include <windows.h>
#include <winuser.h>
#include <windowsx.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

LRESULT CALLBACK ScrollProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR pCmdLine,int nCmdShow)
{

	static TCHAR szAppName[] = TEXT("scrollcalctest");

	HWND hWnd;

	MSG msg;

	WNDCLASS wndClass;
	wndClass.style = CS_HREDRAW| CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
	wndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	if (!RegisterClass(&wndClass))
	{
		return 0;
	}

	wndClass.style = 0;
	wndClass.lpfnWndProc = ScrollProc;
	wndClass.lpszClassName = TEXT("MyScroll");
	wndClass.hbrBackground = 0;

	if (!RegisterClass(&wndClass))
	{
		return 0;
	}

	hWnd = CreateWindow(szAppName,TEXT("测试滚动条"), WS_OVERLAPPEDWINDOW | WS_VSCROLL,500,200,
		350,250,NULL,NULL, hInstance,NULL);

	ShowWindow(hWnd,nCmdShow);

	UpdateWindow(hWnd);

	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// 滚动条句柄
	static HWND hScroll;
	static int nRowCount, nRowHeight, nXClient, nYClient;
	HDC hDC;
	int i, y, nVertPos, nPrintBeg, nPrintEnd;
	PAINTSTRUCT ps;
	SCROLLINFO scrollinfo;
	TCHAR szBuffer[100];
	TEXTMETRIC tm;
	RECT rc;

	switch (uMsg)
	{
	case WM_CREATE:
	{
		// 测试当前数据行数,用于测试滚动量
		nRowCount = 100;
		// 获取一行的高度
		hDC = GetDC(hWnd);
		GetTextMetrics(hDC,&tm);
		// 获取当行的高度
		nRowHeight = tm.tmHeight + tm.tmExternalLeading;
		ReleaseDC(hWnd,hDC);
		// 创建滚动条
		hScroll = CreateWindow(TEXT("MyScroll"), TEXT("MyScroll"),WS_CHILD| WS_VISIBLE| BS_OWNERDRAW,
			300,10,16,300,hWnd,0, ((LPCREATESTRUCT)lParam)->hInstance,NULL);

	    return 0;
	}
	case WM_SIZE:
	{
		// 获取当前窗口的大小
		nXClient = LOWORD(lParam);
		nYClient = HIWORD(lParam);

		// 设置滚动条
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_PAGE | SIF_RANGE;
		scrollinfo.nMin = 0;

		// 只要超过1行就能滚动,因此需要多加1页的行数 最大滚动范围值:111
		scrollinfo.nMax = nRowCount - 1 + nYClient / nRowHeight - 1;
		// 设置一页的大小13
		scrollinfo.nPage = nYClient / nRowHeight;

		SetScrollInfo(hWnd,SB_VERT, &scrollinfo,TRUE);

		// 修正滚动条的位置
		MoveWindow(hScroll, nXClient-100,10,16, nYClient-20,true);
		
		SendMessage(hScroll,SBM_SETSCROLLINFO,TRUE,(LPARAM)(&scrollinfo));

		return 0;
	}
	case WM_VSCROLL:
	{
		// 获取竖滚动条状态
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_ALL;
		GetScrollInfo(hWnd, SB_VERT ,&scrollinfo);

		// 保存原来的位置,用于计算滚动当前画布
		nVertPos = scrollinfo.nPos;

		// lParam 参数为滚动条控件,标准滚动条控件没有这个参数
		if (lParam)
			SendMessage(hScroll, SBM_GETSCROLLINFO,NULL,(LPARAM)&scrollinfo);

		// 设置滚动量
		switch (LOWORD(wParam))
		{
		case SB_TOP:
			scrollinfo.nPos = scrollinfo.nMin;
			break;
		case SB_BOTTOM:
			scrollinfo.nPos = scrollinfo.nMax;
			break;
		case SB_LINEUP:
			scrollinfo.nPos -= 1;
			break;
		case SB_LINEDOWN:
			scrollinfo.nPos += 1;
			break;
		case SB_PAGEUP:
			scrollinfo.nPos -= scrollinfo.nPage;
			break;
		case SB_PAGEDOWN:
			scrollinfo.nPos += scrollinfo.nPage;
			break;
		case SB_THUMBTRACK:
			scrollinfo.nPos = scrollinfo.nTrackPos;
			break;
		default:
			break;
		}

	    // 更新系统滚动状态
		scrollinfo.fMask = SIF_POS;
		SetScrollInfo(hWnd,SB_VERT,&scrollinfo,true);
		GetScrollInfo(hWnd, SB_VERT, &scrollinfo);

		// 滚动界面画布
		if (scrollinfo.nPos != nVertPos)
		{
			GetClientRect(hWnd,&rc);

			// 不要刷到滚动条位置,否则会闪
			rc.right -= 150;

			hDC = GetDC(hWnd);
			ScrollDC(hDC,0, nRowHeight*(nVertPos - scrollinfo.nPos),&rc,NULL,NULL,NULL);
			ReleaseDC(hWnd,hDC);

			// 判断是向上滚动还是向下滚动,nVertPos - scrollinfo.nPos > 0表示向上滚动
			// nVertPos - scrollinfo.nPos < 0 : 表示向下滚动
			if (nRowHeight * (nVertPos - scrollinfo.nPos) < 0)
				rc.top = rc.bottom + nRowHeight * (nVertPos - scrollinfo.nPos);
			else
				rc.bottom = rc.top + nRowHeight * (nVertPos - scrollinfo.nPos);

			InvalidateRect(hWnd,&rc,FALSE);
		}

		// 通知自定义滚动条滚动
		SendMessage(hScroll, SBM_SETSCROLLINFO,TRUE,(LPARAM)(&scrollinfo));
					
		return 0;
	}
	case WM_MOUSEWHEEL:
		// 消息发送到滚动条控件
		PostMessage(hScroll, WM_MOUSEWHEEL, wParam, lParam);
		return 0;
	case WM_PAINT:
	{
		hDC = BeginPaint(hWnd, &ps);
		// 获取竖滚动条的状态
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_ALL;
		GetScrollInfo(hWnd, SB_VERT, &scrollinfo);
		nVertPos = scrollinfo.nPos;

		// 重回修改位置(减少重绘量)
		nPrintBeg = max(0, nVertPos + ps.rcPaint.top/ nRowHeight);
		nPrintEnd = min(nRowCount-1, nVertPos + ps.rcPaint.bottom / nRowHeight);

		// 输出当前行告
		for (i= nPrintBeg;i<= nPrintEnd;i++)
		{
			y = nRowHeight * (i - nVertPos);

			TextOut(hDC, 22, y, szBuffer,wsprintf(szBuffer, TEXT("%5d   "), i + 1));
		}

		// 填充空白区域
		y = nRowHeight * (nPrintEnd + 1 - nVertPos);

		if (ps.rcPaint.bottom - y > 0)
		{
			rc = ps.rcPaint;
			rc.top = y;
			// 使用当前选定的字体、背景色、和文本颜色绘制文本
			ExtTextOut(hDC,0,0, ETO_OPAQUE,&rc,0,0,0);
		}
 
		EndPaint(hWnd, &ps);

		return 0;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

static void MyCGFillColor(HDC hDC,RECT* pRect,unsigned int Color)
{
	// 实际使用ExtTextOut要比FillRect效果要好,能减少绘制中的闪烁问题
	SetBkColor(hDC, Color);

	ExtTextOut(hDC,0,0, ETO_OPAQUE, pRect,0,0,0);
}

// 计算滚动条的位置 
// 参数:nPos:鼠标位置;pScrollinfo:包含当前滑块信息;nHeight: 滑块的总高度
static int CalculateThumPos(int nPos,SCROLLINFO* pScrollinfo,int nHeight)
{
	// 滑块的大小
	float dbThumSize;
	// 每像素的滑动量
	float dbPxSize;
	// 可滑动数量
	int nScrollCnt;

	// 滑块的尺寸 = 滚动条高度/有效范围 * 每页数量
	// 最小是20
	dbThumSize = max((float)((nHeight / (pScrollinfo->nMax - pScrollinfo->nMin))* pScrollinfo->nPage),20.0f);

	if (nPos <= dbThumSize/2)
	{
		return 0;
	}
	
	if(nPos >= nHeight - (dbThumSize / 2))
	{
		// 返回 可滑动数量
		return (pScrollinfo->nMax - pScrollinfo->nMin + 1) - pScrollinfo->nPage;
	}

	// 计算方法:
	// 每像素滑动量 = 有效高度 / 可滑动量
	// 有效高度 = 滑块总高度 - 滑块大小
	// 可滑动量 = 可滑动总量 - 每页数量
	// 可滑动总量 = 滑块最大值 - 滑块最小值 + 1
	// 有效范围 = 滑块最大值 - 滑块最小值
	
	// 可滑动数量
	nScrollCnt = (pScrollinfo->nMax - pScrollinfo->nMin + 1) - pScrollinfo->nPage;
	// 每像素的滑动量
	dbPxSize = (float)(nHeight - dbThumSize) / (float)nScrollCnt;

	// 计算方法
	// 位置 = (鼠标位置 - 半滑块尺寸)/每像素滑动量
	return (int)((nPos - dbThumSize / 2.0) / dbPxSize);

}


LRESULT CALLBACK ScrollProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;

	PAINTSTRUCT ps;

	SCROLLINFO* pScrollinfo;

	RECT rect;

	TCHAR szBuffer[100];

	// 滑块的尺寸
	float dbSize;
	// 滑块界面Top位置
	int nTop;
	// 用来保存滚动条信息
	static SCROLLINFO scrollinfo;
	// 拖拽状态
	static int nDragState;
	// 滚动区域高度
	static int nHeight;

	int nAccumDelta;

	// 滚动条设置消息
	// SBM_GETSCROLLINFO SBM_SETSCROLLINFO消息参数
	// wParam:是否要刷新(SBM_GETSCROLLINFO 消息中该参数无用)
	// lParam:SCROLLINFO* 信息

	switch (uMsg)
	{
	case SBM_SETSCROLLINFO:
	{
		if (!lParam)
			return 0;
		// 设置滚动条的信息
		pScrollinfo = (SCROLLINFO*)lParam;
		// 获取当前滚轮的范围
		if (pScrollinfo->fMask & SIF_RANGE)
		{
			// 设置内容行数
			scrollinfo.nMax = pScrollinfo->nMax;
			scrollinfo.nMin = pScrollinfo->nMin;
		}
		// 获取当前页的大小
		if (pScrollinfo->fMask & SIF_PAGE)   // 每页能滚动多少行
			scrollinfo.nPage = pScrollinfo->nPage;
		// 当前显示的位置
		if (pScrollinfo->fMask & SIF_POS)   // 行显示位置
			scrollinfo.nPos = pScrollinfo->nPos;

		wsprintf(szBuffer,TEXT("Y Pos:%3d"), scrollinfo.nPos);

		SetWindowText(GetParent(hWnd), szBuffer);

		if (wParam)
			InvalidateRect(hWnd,NULL,false);
	    return 0;
	}
	case SBM_GETSCROLLINFO:
	{
		if (lParam)
			*((SCROLLINFO*)lParam) = scrollinfo;
		return 0;
	}
	case WM_SIZE:
	{
		nHeight = HIWORD(lParam);
		break;
	}
	case WM_LBUTTONDOWN:
	{
		scrollinfo.nTrackPos = CalculateThumPos(GET_Y_LPARAM(lParam), &scrollinfo, nHeight);

		// by 2023/06/28 将当前滚动值反馈给父窗口滚动条
		if (scrollinfo.nPos != scrollinfo.nTrackPos)
			PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);
		nDragState = 1;
		InvalidateRect(hWnd, NULL, false);
		break;
	}
	
	case WM_MOUSEMOVE:
	{
		if (nDragState == 1)
		{
			SetCapture(hWnd);
			nDragState = 2;

		}
		else if(nDragState == 2)
		{
			if (!(wParam & MK_LBUTTON))
			{
			    nDragState = 0;
			    
				if (GetCapture() == hWnd)
					ReleaseCapture();
			}
			else
			{
				scrollinfo.nTrackPos = CalculateThumPos(GET_Y_LPARAM(lParam),&scrollinfo, nHeight);

				if (scrollinfo.nPos != scrollinfo.nTrackPos)
					PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);
			}

		}
		break;
	}
	case WM_LBUTTONUP: // 鼠标弹起,滚动到了指定的位置
	{
		if (nDragState == 2)
			ReleaseCapture();

		if (nDragState)
		{
			nDragState = 0;
			InvalidateRect(hWnd, NULL, false);
		}
		break;
	}
	case WM_MOUSEWHEEL:
	{
		nAccumDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;

		// 每滚一次3行
		scrollinfo.nTrackPos = scrollinfo.nPos - nAccumDelta * 3;

		if (scrollinfo.nTrackPos < 0)
			scrollinfo.nTrackPos = 0;
		else if (scrollinfo.nTrackPos > ((int)((scrollinfo.nMax - scrollinfo.nMin + 1) - scrollinfo.nPage)))
			scrollinfo.nTrackPos = (scrollinfo.nMax - scrollinfo.nMin + 1) - scrollinfo.nPage;

		// by 2023/06/28 将当前滚动值反馈给父窗口滚动条
		if (scrollinfo.nTrackPos != scrollinfo.nPos)
			PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);

		return 0;
	}
	
	case WM_PAINT:
	{
		hDC = BeginPaint(hWnd,&ps);

		// 绘制背景色
		GetClientRect(hWnd,&rect);

		MyCGFillColor(hDC,&rect,0xCCCCCC);

		// 计算滑块大小,过小时不绘制
		if ((rect.bottom - rect.top) > 30 && scrollinfo.nMax && (scrollinfo.nMax - scrollinfo.nMin) >= (int)scrollinfo.nPage)
		{
			// 滑块大小计算
			// 滑块大小 = 滑块的高度/有效范围 * 每页的数量
			dbSize = max((float)((rect.bottom - rect.top)/ (scrollinfo.nMax - scrollinfo.nMin)* scrollinfo.nPage),20.0f);

			// 实际滑动位置 = (滚动条高度 - 滑块大小)/(有效范围 - 每页数量 + 1) * 当前行位置
			// 实际滚动的位置会比实际的少一页的数量

			nTop = 0;

			if (scrollinfo.nPos > 0)
				nTop = (int)((rect.bottom - rect.top - dbSize) / (scrollinfo.nMax - scrollinfo.nMin + 1 - scrollinfo.nPage ) * scrollinfo.nPos);

			// 由于精度问题,滑块可能会越界,越界就取最大值
			if (nTop && (nTop + (int)dbSize > rect.bottom))
			{
				nTop = rect.bottom - (int)dbSize;
			}

			rect.left++;
			rect.right--;
			rect.top = nTop;
			rect.bottom = rect.top + (int)dbSize;

			MyCGFillColor(hDC, &rect,nDragState ? 0x999999 : 0x666666e);
			InflateRect(&rect,-1,-1);
			MyCGFillColor(hDC, &rect, nDragState ? 0x666666e : 0x999999);
		}
		EndPaint(hWnd,&ps);
		break;
	}
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

代码运行后的效果如下:
在这里插入图片描述源代码项目地址:Gitee

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值