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

VSTO Outlook插件高DPI启动时GetWindowRect返回错误坐标问题

Outlook VSTO插件DPI缩放异常问题

我正在开发一款带窗口覆盖层的Outlook VSTO插件,UI基于WPF构建。遇到的问题是:

  • 当系统缩放比例大于100%时启动插件,调用GetWindowRect获取Outlook窗口矩形来附着WPF窗口(匹配Left/Top/Width/Height),得到的矩形值是错误的。
  • 但如果以100%缩放比例启动插件,之后运行时修改任意缩放比例,所有功能都正常,且具备DPI感知能力。
  • 两种场景下,GetDpiForWindow都能返回正确的DPI值,这一点非常奇怪。
  • 我在创建窗体时通过SetThreadDpiAwareness设置了DPI感知,实在找不到问题根源,求各位指点。

相关代码

附着窗口代码

public void AttachTo(IntPtr src, AttachFlagEnum flags)
{
    var nativeRectangle = new WinAPI.RECT();
    if (!WinAPI.GetWindowRect(src, ref nativeRectangle))
    {
        // throw new Win32Exception(Marshal.GetLastWin32Error());
        return;
    }

    AttachToCoords(new Rectangle(nativeRectangle.Left, nativeRectangle.Top, nativeRectangle.Right - nativeRectangle.Left, nativeRectangle.Bottom - nativeRectangle.Top), flags);
}

窗体创建代码

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    StateManager.Init();
    OutlookUtils.WaitOutlookLoading();
    using (var ctx = new DPIContextBlock(WinAPI.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
    { 
        new Forms.One().Show();
        new Forms.Overlay().Show();
        new Forms.Two();
    }
}

覆盖层定时附着代码

private void OverlayThink(object ob)
{
    if (Managers.StateManager.OutlookState == OutlookStateEnum.MINIMIZED || Managers.StateManager.UiState == UIStateEnum.DESCWND)
    {
        if (this.IsVisible)
        {
            this.Dispatcher.Invoke(() => this.Hide());
        }
        return;
    }

    this.Dispatcher.Invoke(() => this.AttachTo(Utils.OutlookUtils.GetWordWindow(), AttachFlagEnum.OVERLAY));
    this.Dispatcher.Invoke(() => this.Show());
}

解决方案建议

  1. 调整DPI感知设置时机
    不要用临时上下文块包裹窗体创建逻辑,直接在插件启动初期全局设置进程级DPI感知,确保线程DPI状态在调用GetWindowRect前已生效:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    // 先设置进程级Per-Monitor DPI感知
    WinAPI.SetProcessDpiAwarenessContext(WinAPI.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
    StateManager.Init();
    OutlookUtils.WaitOutlookLoading();
    
    new Forms.One().Show();
    new Forms.Overlay().Show();
    new Forms.Two();
}
  1. 手动转换矩形的DPI适配值
    当进程DPI状态与窗口所在显示器不匹配时,GetWindowRect返回的是虚拟像素值,需要用GetDpiForWindow获取的DPI手动转换为物理像素:
public void AttachTo(IntPtr src, AttachFlagEnum flags)
{
    var nativeRectangle = new WinAPI.RECT();
    if (!WinAPI.GetWindowRect(src, ref nativeRectangle))
    {
        return;
    }
    
    uint dpi = WinAPI.GetDpiForWindow(src);
    double scaleFactor = dpi / 96.0;
    
    int left = (int)(nativeRectangle.Left * scaleFactor);
    int top = (int)(nativeRectangle.Top * scaleFactor);
    int width = (int)((nativeRectangle.Right - nativeRectangle.Left) * scaleFactor);
    int height = (int)((nativeRectangle.Bottom - nativeRectangle.Top) * scaleFactor);
    
    AttachToCoords(new Rectangle(left, top, width, height), flags);
}
  1. 开启WPF窗口的Per-Monitor DPI感知
    在WPF窗口构造函数中显式启用Per-Monitor DPI支持:
public Overlay()
{
    InitializeComponent();
    // 启用Per-Monitor DPI感知
    System.Windows.Media.VisualTreeHelper.SetDpiAwarenessContext(this, WinAPI.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
    this.UseLayoutRounding = true;
}

也可以在插件的配置文件中添加全局设置:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
    </windowsSettings>
</application>
  1. 确保在Outlook完全加载后操作窗口
    替换OutlookUtils.WaitOutlookLoading(),改用监听Outlook的StartupComplete事件,在事件触发后再创建和附着窗口,避免在Outlook未完成DPI适配时获取窗口矩形。

内容的提问来源于stack exchange,提问作者UniqueUlysees

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 18:35:30