如何为对话框 CDialog 增加滚动条

被刻印的时光 ゝ 提交于 2020-03-09 14:52:25

假设对话框类名 CMyDialog,继承自 CDialog 或 CDialogEx。

CMyDialog 添加WM_MOUSEWHEEL、WM_VSCROLL、WM_HSCROLL 三个消息映射函数。并添加如下一个自定义成员函数。

void UpdateScrollInfo(int nWidthNeed, int nLengthNeed);

之后添加或修改滚动条时直接调用该函数即可。

四个函数的实现代码如下:

BOOL CMyDialog::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL)
	{
		SCROLLINFO scrinf;
		GetScrollInfo(SB_VERT, &scrinf, SIF_ALL);

		// 旧窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosOld = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosOld = min(max(nWndPosOld, 0), scrinf.nMax);

		// 移动滚动条
		scrinf.nPos -= zDelta;
		SetScrollInfo(SB_VERT, &scrinf);

		// 新窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosNew = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosNew = min(max(nWndPosNew, 0), scrinf.nMax);

		// 滚动窗体
		ScrollWindow(0, nWndPosOld - nWndPosNew);
		return TRUE;
	}
	else if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL)
	{
		SCROLLINFO scrinf;
		GetScrollInfo(SB_HORZ, &scrinf, SIF_ALL);

		// 旧窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosOld = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosOld = min(max(nWndPosOld, 0), scrinf.nMax);

		// 移动滚动条
		scrinf.nPos -= zDelta;
		SetScrollInfo(SB_HORZ, &scrinf);

		// 新窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosNew = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosNew = min(max(nWndPosNew, 0), scrinf.nMax);

		// 滚动窗体
		ScrollWindow(nWndPosOld - nWndPosNew, 0);
		return TRUE;
	}

	return CDialog::OnMouseWheel(nFlags, zDelta, pt);
}

void CMyDialog::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL)
	{
		SCROLLINFO scrinf = { 0 };
		GetScrollInfo(SB_VERT, &scrinf, SIF_ALL);

		// 旧窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosOld = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosOld = min(max(nWndPosOld, 0), scrinf.nMax);

		// 滚动条最大滚动位置
		int nPosMax = scrinf.nMax - (int)scrinf.nPage;

		switch (nSBCode)
		{
		case SB_TOP:		scrinf.nPos = 0;						break; // 滑块滚动到最顶部
		case SB_BOTTOM:		scrinf.nPos = nPosMax;					break; // 滑块滚动到最底部
		case SB_LINEUP:		scrinf.nPos = scrinf.nPos - nPosMax;	break; // 单击上箭头
		case SB_LINEDOWN:	scrinf.nPos = scrinf.nPos + nPosMax;	break; // 单击下箭头
		case SB_PAGEUP:		scrinf.nPos -= nPosMax / 2;				break; // 单击滑块上方空白区域
		case SB_PAGEDOWN:	scrinf.nPos += nPosMax / 2;				break; // 单击滑块下方空白区域
		case SB_THUMBTRACK:	scrinf.nPos = nPos;						break; // 拖动滑块
		}

		// 移动滚动条
		scrinf.fMask = SIF_POS;
		scrinf.nPos = min(max(scrinf.nPos, 0), nPosMax);
		SetScrollInfo(SB_VERT, &scrinf);

		// 新窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosNew = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosNew = min(max(nWndPosNew, 0), scrinf.nMax);

		// 滚动窗体
		ScrollWindow(0, nWndPosOld - nWndPosNew);
	}

	CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}

void CMyDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL)
	{
		SCROLLINFO scrinf;
		GetScrollInfo(SB_HORZ, &scrinf, SIF_ALL);

		// 旧窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosOld = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosOld = min(max(nWndPosOld, 0), scrinf.nMax);

		// 滚动条最大滚动位置
		int nPosMax = scrinf.nMax - (int)scrinf.nPage;

		switch (nSBCode)
		{
		case SB_LEFT:		scrinf.nPos = 0;						break; // 滑块滚动到最左部
		case SB_RIGHT:		scrinf.nPos = nPosMax;					break; // 滑块滚动到最右部
		case SB_LINELEFT:	scrinf.nPos = scrinf.nPos - nPosMax;	break; // 单击左箭头
		case SB_LINERIGHT:	scrinf.nPos = scrinf.nPos + nPosMax;	break; // 单击右箭头
		case SB_PAGELEFT:	scrinf.nPos -= nPosMax / 2;				break; // 单击滑块左空白区域
		case SB_PAGERIGHT:	scrinf.nPos += nPosMax / 2;				break; // 单击滑块右空白区域
		case SB_THUMBTRACK:	scrinf.nPos = nPos;						break; // 拖动滑块
		}

		// 移动滚动条
		scrinf.fMask = SIF_POS;
		scrinf.nPos = min(max(scrinf.nPos, 0), nPosMax);
		SetScrollInfo(SB_HORZ, &scrinf);

		// 新窗体位置(窗体尺寸及滚动位置根据 SCROLLINFO::nMax、nPage、nPos 反算,省去增加并维护相关成员变量的代码)
		int nWndPosNew = scrinf.nPos * scrinf.nMax / (scrinf.nMax - (int)scrinf.nPage);
		nWndPosNew = min(max(nWndPosNew, 0), scrinf.nMax);

		// 滚动窗体
		ScrollWindow(nWndPosOld - nWndPosNew, 0);
	}

	CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}

// 更新滚动条(不改变原滚动位置)
void CMyDialog::UpdateScrollInfo(int nWidthNeed, int nLengthNeed)
{
	ASSERT(nWidthNeed > 0);
	ASSERT(nLengthNeed > 0);

	int nCxVsCroll = ::GetSystemMetrics(SM_CXVSCROLL);	// 系统垂直滚动条宽度
	int nCyHsCroll = ::GetSystemMetrics(SM_CYHSCROLL);	// 系统水平滚动条高度

	// 可用客户区是去掉滚动条的客户区,计算滚动条参数时需要知道更新前和后滚动条是否存在,以在计算时减去或加上滚动条的尺寸

	// 当前客户区,这是含滚动条的尺寸(如果有)
	CRect rcClient;
	GetClientRect(rcClient);

	// 如果当前有垂直滚动条,则可用客户区宽度加上垂直滚动条宽度,如果当前有水平滚动条,则可用客户区高度加上水平滚动条高度
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL) rcClient.right += nCxVsCroll;
	if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL) rcClient.bottom += nCyHsCroll;

	// 1、计算是否需要添加垂直滚动条
	BOOL bVsCrollAdd = nLengthNeed > rcClient.Height();

	// 如果需要垂直滚动条,客户区宽度减去垂直滚动条宽度
	if (bVsCrollAdd)
		rcClient.right -= nCxVsCroll;

	// 2、计算是否需要添加水平滚动条
	BOOL bHsCrollAdd = nWidthNeed > rcClient.Width();

	// 如果需要水平滚动条,客户区高度减去水平滚动条宽度
	if (bHsCrollAdd)
		rcClient.bottom -= nCyHsCroll;

	// 3、如果需要水平滚动条并且前面计算不需要垂直滚动条,则由于可用客户区高度已变化,需要重新计算是否需要垂直滚动条
	if (bHsCrollAdd && !bVsCrollAdd)
	{
		bVsCrollAdd = nLengthNeed > rcClient.Height();

		// 如果需要垂直滚动条,客户区宽度减去垂直滚动条宽度
		if (bVsCrollAdd)
			rcClient.right -= nCxVsCroll;
	}

	SCROLLINFO scinfo = { 0 };
	scinfo.cbSize = sizeof(SCROLLINFO);
	scinfo.fMask = SIF_RANGE | SIF_PAGE;

	// 更新垂直滚动条(不改变原滚动位置)
	scinfo.nMax = max(0, nLengthNeed - rcClient.Height());			// 垂直滚动最大值 = 需求高度 - 客户区高度
	scinfo.nPage = scinfo.nMax * rcClient.Height() / nLengthNeed;	// 垂直滚动条内的滚动块高度
	SetScrollInfo(SB_VERT, &scinfo);

	// 更新水平滚动条(不改变原滚动位置)
	scinfo.nMax = max(0, nWidthNeed - rcClient.Width());			// 水平滚动最大值 = 需求宽度 - 客户区宽度
	scinfo.nPage = scinfo.nMax * rcClient.Width() / nWidthNeed;		// 水平滚动条内的滚动块长度
	SetScrollInfo(SB_HORZ, &scinfo);
}

最后需注意在计算可用客户区时要注意加减滚动条所占用的尺寸,如:

	CRect rcClient;
	GetClientRect(rcClient);

	// 如果当前有垂直滚动条,则可用客户区减去垂直滚动条宽度
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL)
		rcClient.right -= ::GetSystemMetrics(SM_CXVSCROLL);

	// 如果当前有水平滚动条,则可用客户区减去水平滚动条高度
	if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL)
		rcClient.bottom -= ::GetSystemMetrics(SM_CYHSCROLL);
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!