Dialog应用中CMFCToolBar加载与Windows缩放适配问题
问题:高DPI缩放(125%)下CMFCToolBar工具栏被对话框裁剪
我通过以下代码为对话框加载32位PNG格式的工具栏:
void CMeetingScheduleAssistantDlg::CreateToolbar() { constexpr DWORD dwCtrlStyle = TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | CBRS_SIZE_DYNAMIC; DWORD dwStyle = AFX_DEFAULT_TOOLBAR_STYLE; CMFCToolBar::m_dblLargeImageRatio = 1; // AJT v20.1.7 Bug fix if (m_ToolBar.CreateEx(this, dwCtrlStyle, dwStyle, CRect(1, 1, 1, 1), IDR_TOOLBAR)) { dwStyle = CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC; m_ToolBar.SetPaneStyle(m_ToolBar.GetPaneStyle() | dwStyle); CMFCToolBarInfo info; info.m_uiColdResID = IDB_PNG_MAIN_TOOLBAR; info.m_uiHotResID = IDB_PNG_MAIN_TOOLBAR; info.m_uiLargeColdResID = IDB_PNG_MAIN_TOOLBAR; info.m_uiLargeHotResID = IDB_PNG_MAIN_TOOLBAR; m_ToolBar.LoadToolBarEx(IDR_TOOLBAR, info, FALSE); const CSize sizeToolBar = m_ToolBar.CalcFixedLayout(TRUE, TRUE); m_ToolBar.SetWindowPos(nullptr, 0, 0, sizeToolBar.cx, sizeToolBar.cy, SWP_NOACTIVATE | SWP_NOZORDER); // Move all controls down const CPoint ptOffset(0, sizeToolBar.cy); CRect rcChild; CWnd* pwndChild = GetWindow(GW_CHILD); while (pwndChild) { if (pwndChild->GetSafeHwnd() != m_ToolBar.GetSafeHwnd()) { pwndChild->GetWindowRect(rcChild); ScreenToClient(rcChild); rcChild.OffsetRect(ptOffset); pwndChild->MoveWindow(rcChild, FALSE); } pwndChild = pwndChild->GetNextWindow(); } // Resize the window CRect rcWindow; GetWindowRect(rcWindow); rcWindow.bottom += sizeToolBar.cy; MoveWindow(rcWindow, FALSE); } }
该函数在OnInitDialog中调用。Windows 11系统缩放设置为100%时显示正常:

但在缩放设置为125%的Windows 11笔记本电脑上,工具栏出现裁剪:

其中m_Toolbar为CMFCToolBar类型。请问如何确保对话框不会裁剪工具栏?
补充信息:对话框继承自自定义类CResizingDialog,该类派生自CDialogEx,相关代码如下:
void CResizingDialog::OnGetMinMaxInfo(MINMAXINFO* lpMMI) { // Set the minimum window size to initial size. if (!m_rcInit.IsRectEmpty()) { lpMMI->ptMinTrackSize.x = m_rcInit.Width(); lpMMI->ptMinTrackSize.y = m_rcInit.Height(); if (m_bLimitToHorizontalResizing) lpMMI->ptMaxTrackSize.y = m_rcInit.Height(); } __super::OnGetMinMaxInfo(lpMMI); }
BOOL CResizingDialog::OnInitDialog() { __super::OnInitDialog(); // Save Initial window size to m_rcInit GetWindowRect(&m_rcInit); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
主窗口具备保存/恢复位置的功能,相关代码如下:
void CResizingDialog::RestoreWindowPosition(CString strWindow, CWnd* pWindow, bool bOverrideState) { int max_x{}, max_y{}; RECT rtWindow{}; if (pWindow == nullptr) return; // Only restore if there is a previously saved position rtWindow.top = AfxGetApp()->GetProfileInt(strWindow, _T("Top"), -1); rtWindow.left = AfxGetApp()->GetProfileInt(strWindow, _T("Left"), -1); rtWindow.bottom = AfxGetApp()->GetProfileInt(strWindow, _T("Bottom"), -1); rtWindow.right = AfxGetApp()->GetProfileInt(strWindow, _T("Right"), -1); if (rtWindow.top != -1 && rtWindow.left != -1 && rtWindow.bottom != -1 && rtWindow.right != -1) { max_x = rtWindow.right - rtWindow.left; max_y = rtWindow.bottom - rtWindow.top; // Get a handle to the monitor HMONITOR hMonitor = ::MonitorFromPoint( CPoint(rtWindow.left, rtWindow.top), MONITOR_DEFAULTTONEAREST); // Get the monitor info MONITORINFO monInfo{}; monInfo.cbSize = sizeof(MONITORINFO); if (::GetMonitorInfo(hMonitor, &monInfo) == 0) AfxMessageBox(_T("GetMonitorInfo failed")); else { // Adjust for work area rtWindow.left += monInfo.rcWork.left - monInfo.rcMonitor.left; rtWindow.top += monInfo.rcWork.top - monInfo.rcMonitor.top; // Ensure top left point is on screen const CRect rcMonWork(monInfo.rcWork); const CRect rtRestored(rtWindow.left, rtWindow.top, rtWindow.right, rtWindow.bottom); // Deflate the rectangle so we have a margin if (rcMonWork.PtInRect(rtRestored.TopLeft()) == FALSE || rcMonWork.PtInRect(rtRestored.BottomRight()) == FALSE) { rtWindow.left = monInfo.rcWork.left; rtWindow.top = monInfo.rcWork.top; } if (m_bOnlyStorePosition) { // We must override the dimensions of the dialog. This is because the user // might have text scale in effect. CRect rtOrg; GetWindowRect(&rtOrg); // But we want to show it in the previous place (albeit slightly off position) pWindow->MoveWindow(rtWindow.left, rtWindow.top, rtOrg.Width(), rtOrg.Height()); } else { rtWindow.right = rtWindow.left + max_x; rtWindow.bottom = rtWindow.top + max_y; // Restore window size pWindow->MoveWindow(&rtWindow, FALSE); } } if (bOverrideState) { // Let us override by restoring the window state const int iState = AfxGetApp()->GetProfileInt(strWindow, _T("ShowCmd"), SW_SHOWNORMAL); pWindow->ShowWindow(iState); } } } void CResizingDialog::SaveWindowPosition(CString strWindow, const CWnd* pWindow) { WINDOWPLACEMENT wp{}; if (pWindow == nullptr) return; pWindow->GetWindowPlacement(&wp); // Commit to registry AfxGetApp()->WriteProfileInt(strWindow, _T("Top"), wp.rcNormalPosition.top); AfxGetApp()->WriteProfileInt(strWindow, _T("Left"), wp.rcNormalPosition.left); AfxGetApp()->WriteProfileInt(strWindow, _T("Bottom"), wp.rcNormalPosition.bottom); AfxGetApp()->WriteProfileInt(strWindow, _T("Right"), wp.rcNormalPosition.right); AfxGetApp()->WriteProfileInt(strWindow, _T("ShowCmd"), wp.showCmd); }
没有更多相关代码可以提供。
解决方案
1. 修复高DPI下的窗口尺寸计算逻辑
问题核心是高DPI缩放时,CalcFixedLayout返回的是物理像素尺寸,但对话框调整时未适配DPI比例,导致窗口高度增量计算错误。修改CreateToolbar中的窗口调整代码:
// 替换原有的窗口调整代码 CRect rcWindow; GetWindowRect(rcWindow); // 获取当前窗口的DPI缩放比例 UINT dpi = GetDpiForWindow(m_hWnd); float scale = static_cast<float>(dpi) / 96.0f; // 对工具栏高度进行DPI适配后的调整 int adjustedHeight = static_cast<int>(sizeToolBar.cy / scale); rcWindow.bottom += adjustedHeight; MoveWindow(rcWindow, FALSE);
2. 修正CResizingDialog的初始尺寸保存逻辑
OnInitDialog中保存的m_rcInit是物理像素尺寸,高DPI下会导致最小窗口尺寸限制错误,需转换为逻辑像素:
BOOL CResizingDialog::OnInitDialog() { __super::OnInitDialog(); // 保存初始窗口的逻辑尺寸 CRect rcPhysical; GetWindowRect(&rcPhysical); UINT dpi = GetDpiForWindow(m_hWnd); float scale = static_cast<float>(dpi) / 96.0f; m_rcInit.left = static_cast<int>(rcPhysical.left / scale); m_rcInit.top = static_cast<int>(rcPhysical.top / scale); m_rcInit.right = static_cast<int>(rcPhysical.right / scale); m_rcInit.bottom = static_cast<int>(rcPhysical.bottom / scale); return TRUE; }
同步修改OnGetMinMaxInfo,确保最小/最大尺寸适配当前DPI:
void CResizingDialog::OnGetMinMaxInfo(MINMAXINFO* lpMMI) { if (!m_rcInit.IsRectEmpty()) { UINT dpi = GetDpiForWindow(m_hWnd); float scale = static_cast<float>(dpi) / 96.0f; // 转换为物理像素设置最小尺寸 lpMMI->ptMinTrackSize.x = static_cast<int>(m_rcInit.Width() * scale); lpMMI->ptMinTrackSize.y = static_cast<int>(m_rcInit.Height() * scale); if (m_bLimitToHorizontalResizing) lpMMI->ptMaxTrackSize.y = static_cast<int>(m_rcInit.Height() * scale); } __super::OnGetMinMaxInfo(lpMMI); }
3. 调整窗口位置恢复逻辑
在RestoreWindowPosition中,当m_bOnlyStorePosition为true时,确保使用当前DPI适配后的窗口尺寸:
if (m_bOnlyStorePosition) { CRect rtOrg; GetWindowRect(&rtOrg); // 获取当前DPI比例 UINT dpi = GetDpiForWindow(pWindow->m_hWnd); float scale = static_cast<float>(dpi) / 96.0f; // 转换为逻辑尺寸后再调整为物理像素 int width = static_cast<int>(rtOrg.Width() / scale); int height = static_cast<int>(rtOrg.Height() / scale); pWindow->MoveWindow(rtWindow.left, rtWindow.top, width * scale, height * scale); }
4. 启用MFC高DPI支持
- 在项目
stdafx.h中添加清单依赖:#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Shell' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") - 打开项目属性,将"高DPI感知"设置为"每监视器高DPI感知 v2"。
内容的提问来源于stack exchange,提问作者Andrew Truckle
相关产品推荐
相关产品推荐

