You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:17:31