创建一个尺寸过大的对话框时,可将其作为子控件创建在父对话框的一个更小的区域,并为该子控件(子对话框)增加滚动条。
要增加滚动条的子对话框资源需设置 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 中销毁该子对话框。