MFC 对话框 CDialog 增加滚动条

创建一个尺寸过大的对话框时,可将其作为子控件创建在父对话框的一个更小的区域,并为该子控件(子对话框)增加滚动条。

要增加滚动条的子对话框资源需设置 WS_CHILD 样式,去除标题栏和边框,不要设置 WS_VSCROLL 和 WS_HSCROLL 样式。

正常生成子对话框类,假设类名为 CXDialogEx,继承自 CDialog 或 CDialogEx,如下为其添加三个消息映射函数和一个自定义函数。

XDialogEx.h 中函数声明:

afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
void UpdateScrollInfo(int nWidthNeed, int nLengthNeed);

XDialogEx.cpp 中消息映射:

BEGIN_MESSAGE_MAP(CXDialogEx, CDialog)
	ON_WM_MOUSEWHEEL()
	ON_WM_VSCROLL()
	ON_WM_HSCROLL()
END_MESSAGE_MAP()

XDialogEx.cpp 中四个函数的实现代码如下:

BOOL CXDialogEx::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL)
	{
		SCROLLINFO scinfo = { 0 };
		scinfo.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(SB_VERT, &scinfo, SIF_ALL);

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

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

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

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

		// 滚动窗体
		ScrollWindow(0, nWndPosOld - nWndPosNew);
	}
	else if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL)
	{
		SCROLLINFO scinfo = { 0 };
		scinfo.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(SB_HORZ, &scinfo, SIF_ALL);

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

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

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

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

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

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

void CXDialogEx::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if ((GetStyle() & WS_VSCROLL) == WS_VSCROLL)
	{
		SCROLLINFO scinfo = { 0 };
		scinfo.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(SB_VERT, &scinfo, SIF_ALL);

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

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

		switch (nSBCode)
		{
		case SB_TOP:		scinfo.nPos = 0;				break; // 滑块滚动到最顶部
		case SB_BOTTOM:		scinfo.nPos = nPosMax;			break; // 滑块滚动到最底部
		case SB_LINEUP:		scinfo.nPos -= nPosMax / 10;	break; // 单击上箭头(10 次结束,可视需求修改)
		case SB_LINEDOWN:	scinfo.nPos += nPosMax / 10;	break; // 单击下箭头(10 次结束,可视需求修改)
		case SB_PAGEUP:		scinfo.nPos -= nPosMax / 2;		break; // 单击滑块上方空白区域(2 次结束,可视需求修改)
		case SB_PAGEDOWN:	scinfo.nPos += nPosMax / 2;		break; // 单击滑块下方空白区域(2 次结束,可视需求修改)
		case SB_THUMBTRACK:	scinfo.nPos = nPos;				break; // 拖动滑块
		}

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

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

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

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

void CXDialogEx::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if ((GetStyle() & WS_HSCROLL) == WS_HSCROLL)
	{
		SCROLLINFO scinfo = { 0 };
		scinfo.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(SB_HORZ, &scinfo, SIF_ALL);

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

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

		switch (nSBCode)
		{
		case SB_LEFT:		scinfo.nPos = 0;				break; // 滑块滚动到最左部
		case SB_RIGHT:		scinfo.nPos = nPosMax;			break; // 滑块滚动到最右部
		case SB_LINELEFT:	scinfo.nPos -= nPosMax / 10;	break; // 单击左箭头(10 次结束,可视需求修改)
		case SB_LINERIGHT:	scinfo.nPos += nPosMax / 10;	break; // 单击右箭头(10 次结束,可视需求修改)
		case SB_PAGELEFT:	scinfo.nPos -= nPosMax / 2;		break; // 单击滑块左空白区域(2 次结束,可视需求修改)
		case SB_PAGERIGHT:	scinfo.nPos += nPosMax / 2;		break; // 单击滑块右空白区域(2 次结束,可视需求修改)
		case SB_THUMBTRACK:	scinfo.nPos = nPos;				break; // 拖动滑块
		}

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

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

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

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

///<summary>更新对话框滚动条(未改变原滚动位置)</summary>
///<param name="nWidthNeed">需要滚动的区域宽度</param>
///<param name="nHeightNeed">需要滚动的区域高度</param>
void CXDialogEx::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);
}

此外需要屏蔽子对话框的 IDOK 和 IDCANCEL消息,最简单的方法是重载虚拟函数 OnOK 和 OnCancel 后清除其中内容。

在父对话框中声明该子对话框一个变量,然后在父对话框的 OnInitDialog 中创建该子对话框到父对话框的固定位置(宽度和高度至少一项小于子对话框的原始宽度或高度)。

然后在父对话框中调用子对话框的:
void UpdateScrollInfo(int nWidthNeed, int nLengthNeed);
其中 nWidthNeed/nLengthNeed分别是子对话框的原始宽度和高度,直接指定时需要进行分辨率转换,建议通过以下方式获取。

SIZE szRes = { 0 }; 
CDialogTemplate dlgt;
dlgt.Load(MAKEINTRESOURCE(IDD_DIAOLG...)); // IDD_DIAOLG... 是子对话框的资源 ID
dlgt.GetSizeInPixels(&szRes);`// 直接从资源读取对话框获取原始像素尺寸,无需分辨率转换
m_dlgChild.UpdateScrollInfo(szRes.cx, szRes.cy); // m_dlgChild 为已创建的子对话框

nWidthNeed < 固定位置宽度时将自动产生水平滚动条。
nLengthNeed < 固定位置高度时将自动产生垂直滚动条。

最后在父对话框的 OnDestroy 中销毁该子对话框。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值