WPF Web浏览器控件和DPI缩放

时间:2016-08-03 22:00:44

标签: c# wpf webbrowser-control dpi

我正在使用使用Web浏览器控件的WPF应用程序,而且我遇到了高DPI缩放问题。

看起来Web浏览器控件没有正确地遵循系统的DPI设置,而WPF应用程序的其余部分正在正确扩展UI。这意味着在更高级别的级别上,WPF界面变得更大,而Web浏览器内容保持原始的,现在看起来更小。

以下是使用两个Web浏览器控件的WPF应用程序中的屏幕截图示例。

100%缩放:

enter image description here

150%缩放:

enter image description here

请注意,在第二张图像中,Web浏览器缩放比第一张图片相对于主窗体内容(工具栏/菜单/状态栏)要小得多。

是否有某种方法可以强制Web浏览器控件正确使用从应用程序继承的高DPI设置?

此MSDN链接: Addressing DPI Issues

显示了一个真正低级的方法(在doc的底部)实现自定义Web浏览器COM接口,但我想知道是否有更清晰的方法来解决这个问题。

4 个答案:

答案 0 :(得分:7)

我已经找到了我认为实现所需功能的最佳方式(前提是,您无论如何需要在注册表中指定FEATURE_BROWSER_EMULATION以使用最新的IE版本)。< / p>

您需要做的就是在名为HKCU\Software\Microsoft\Internet Explorer\Main\FeatureControl的{​​{1}}中创建一个新密钥,并在其中添加类型为FEATURE_96DPI_PIXEL的可执行条目,并将应用程序exe作为密钥名称和值DWORD (32-bit)

在实际实例化WebBrowser组件之前检查应用程序启动时的设置,你应该没问题。

原帖(包含其他可能的功能): https://www.reddit.com/r/dotnet/comments/2j5m6m/wpf_webbrowser_control_alternatives/

答案 1 :(得分:5)

以下是实用程序类的代码,它允许您停用WPF的WebBrowser上下文菜单。它还允许您抑制脚本错误(WPF WebBrowser control - how to supress script errors?)并更改IE DOCHOSTUIFLAG

使用示例:

public partial class Player : Window
{
    private WebBrowserHostUIHandler _wbHandler;

    public Player()
    {
        InitializeComponent();
        ...
        _wbHandler = new WebBrowserHostUIHandler(MyWebBrower);
        _wbHandler.IsWebBrowserContextMenuEnabled = true;
    }
}

实用程序代码:

public class WebBrowserHostUIHandler : Native.IDocHostUIHandler
{
    private const uint E_NOTIMPL = 0x80004001;
    private const uint S_OK = 0;
    private const uint S_FALSE = 1;

    public WebBrowserHostUIHandler(WebBrowser browser)
    {
        if (browser == null)
            throw new ArgumentNullException("browser");

        Browser = browser;
        browser.LoadCompleted += OnLoadCompleted;
        browser.Navigated += OnNavigated;
        IsWebBrowserContextMenuEnabled = true;
        Flags |= HostUIFlags.ENABLE_REDIRECT_NOTIFICATION;
    }

    public WebBrowser Browser { get; private set; }
    public HostUIFlags Flags { get; set; }
    public bool IsWebBrowserContextMenuEnabled { get; set; }
    public bool ScriptErrorsSuppressed { get; set; }

    private void OnNavigated(object sender, NavigationEventArgs e)
    {
        SetSilent(Browser, ScriptErrorsSuppressed);
    }

    private void OnLoadCompleted(object sender, NavigationEventArgs e)
    {
        Native.ICustomDoc doc = Browser.Document as Native.ICustomDoc;
        if (doc != null)
        {
            doc.SetUIHandler(this);
        }
    }

    uint Native.IDocHostUIHandler.ShowContextMenu(int dwID, Native.POINT pt, object pcmdtReserved, object pdispReserved)
    {
        return IsWebBrowserContextMenuEnabled ? S_FALSE : S_OK;
    }

    uint Native.IDocHostUIHandler.GetHostInfo(ref Native.DOCHOSTUIINFO info)
    {
        info.dwFlags = (int)Flags;
        info.dwDoubleClick = 0;
        return S_OK;
    }

    uint Native.IDocHostUIHandler.ShowUI(int dwID, object activeObject, object commandTarget, object frame, object doc)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.HideUI()
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.UpdateUI()
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.EnableModeless(bool fEnable)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.OnDocWindowActivate(bool fActivate)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.OnFrameWindowActivate(bool fActivate)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.ResizeBorder(Native.COMRECT rect, object doc, bool fFrameWindow)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.TranslateAccelerator(ref System.Windows.Forms.Message msg, ref Guid group, int nCmdID)
    {
        return S_FALSE;
    }

    uint Native.IDocHostUIHandler.GetOptionKeyPath(string[] pbstrKey, int dw)
    {
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.GetDropTarget(object pDropTarget, out object ppDropTarget)
    {
        ppDropTarget = null;
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.GetExternal(out object ppDispatch)
    {
        ppDispatch = Browser.ObjectForScripting;
        return S_OK;
    }

    uint Native.IDocHostUIHandler.TranslateUrl(int dwTranslate, string strURLIn, out string pstrURLOut)
    {
        pstrURLOut = null;
        return E_NOTIMPL;
    }

    uint Native.IDocHostUIHandler.FilterDataObject(IDataObject pDO, out IDataObject ppDORet)
    {
        ppDORet = null;
        return E_NOTIMPL;
    }

    public static void SetSilent(WebBrowser browser, bool silent)
    {
        Native.IOleServiceProvider sp = browser.Document as Native.IOleServiceProvider;
        if (sp != null)
        {
            Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
            Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");

            object webBrowser;
            sp.QueryService(ref IID_IWebBrowserApp, ref IID_IWebBrowser2, out webBrowser);
            if (webBrowser != null)
            {
                webBrowser.GetType().InvokeMember("Silent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.PutDispProperty, null, webBrowser, new object[] { silent });
            }
        }
    }
}

internal static class Native
{
    [ComImport, Guid("BD3F23C0-D43E-11CF-893B-00AA00BDCE1A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IDocHostUIHandler
    {
        [PreserveSig]
        uint ShowContextMenu(int dwID, POINT pt, [MarshalAs(UnmanagedType.Interface)] object pcmdtReserved, [MarshalAs(UnmanagedType.Interface)] object pdispReserved);

        [PreserveSig]
        uint GetHostInfo(ref DOCHOSTUIINFO info);

        [PreserveSig]
        uint ShowUI(int dwID, [MarshalAs(UnmanagedType.Interface)] object activeObject, [MarshalAs(UnmanagedType.Interface)] object commandTarget, [MarshalAs(UnmanagedType.Interface)] object frame, [MarshalAs(UnmanagedType.Interface)] object doc);

        [PreserveSig]
        uint HideUI();

        [PreserveSig]
        uint UpdateUI();

        [PreserveSig]
        uint EnableModeless(bool fEnable);

        [PreserveSig]
        uint OnDocWindowActivate(bool fActivate);

        [PreserveSig]
        uint OnFrameWindowActivate(bool fActivate);

        [PreserveSig]
        uint ResizeBorder(COMRECT rect, [MarshalAs(UnmanagedType.Interface)] object doc, bool fFrameWindow);

        [PreserveSig]
        uint TranslateAccelerator(ref System.Windows.Forms.Message msg, ref Guid group, int nCmdID);

        [PreserveSig]
        uint GetOptionKeyPath([Out, MarshalAs(UnmanagedType.LPArray)] string[] pbstrKey, int dw);

        [PreserveSig]
        uint GetDropTarget([In, MarshalAs(UnmanagedType.Interface)] object pDropTarget, [MarshalAs(UnmanagedType.Interface)] out object ppDropTarget);

        [PreserveSig]
        uint GetExternal([MarshalAs(UnmanagedType.IDispatch)] out object ppDispatch);

        [PreserveSig]
        uint TranslateUrl(int dwTranslate, [MarshalAs(UnmanagedType.LPWStr)] string strURLIn, [MarshalAs(UnmanagedType.LPWStr)] out string pstrURLOut);

        [PreserveSig]
        uint FilterDataObject(IDataObject pDO, out IDataObject ppDORet);
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct DOCHOSTUIINFO
    {
        public int cbSize;
        public int dwFlags;
        public int dwDoubleClick;
        public IntPtr dwReserved1;
        public IntPtr dwReserved2;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct COMRECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class POINT
    {
        public int x;
        public int y;
    }

    [ComImport, Guid("3050F3F0-98B5-11CF-BB82-00AA00BDCE0B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface ICustomDoc
    {
        [PreserveSig]
        int SetUIHandler(IDocHostUIHandler pUIHandler);
    }

    [ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IOleServiceProvider
    {
        [PreserveSig]
        uint QueryService([In] ref Guid guidService, [In] ref Guid riid, [MarshalAs(UnmanagedType.IDispatch)] out object ppvObject);
    }
}

[Flags]
public enum HostUIFlags
{
    DIALOG = 0x00000001,
    DISABLE_HELP_MENU = 0x00000002,
    NO3DBORDER = 0x00000004,
    SCROLL_NO = 0x00000008,
    DISABLE_SCRIPT_INACTIVE = 0x00000010,
    OPENNEWWIN = 0x00000020,
    DISABLE_OFFSCREEN = 0x00000040,
    FLAT_SCROLLBAR = 0x00000080,
    DIV_BLOCKDEFAULT = 0x00000100,
    ACTIVATE_CLIENTHIT_ONLY = 0x00000200,
    OVERRIDEBEHAVIORFACTORY = 0x00000400,
    CODEPAGELINKEDFONTS = 0x00000800,
    URL_ENCODING_DISABLE_UTF8 = 0x00001000,
    URL_ENCODING_ENABLE_UTF8 = 0x00002000,
    ENABLE_FORMS_AUTOCOMPLETE = 0x00004000,
    ENABLE_INPLACE_NAVIGATION = 0x00010000,
    IME_ENABLE_RECONVERSION = 0x00020000,
    THEME = 0x00040000,
    NOTHEME = 0x00080000,
    NOPICS = 0x00100000,
    NO3DOUTERBORDER = 0x00200000,
    DISABLE_EDIT_NS_FIXUP = 0x00400000,
    LOCAL_MACHINE_ACCESS_CHECK = 0x00800000,
    DISABLE_UNTRUSTEDPROTOCOL = 0x01000000,
    HOST_NAVIGATES = 0x02000000,
    ENABLE_REDIRECT_NOTIFICATION = 0x04000000,
    USE_WINDOWLESS_SELECTCONTROL = 0x08000000,
    USE_WINDOWED_SELECTCONTROL = 0x10000000,
    ENABLE_ACTIVEX_INACTIVATE_MODE = 0x20000000,
    DPI_AWARE = 0x40000000
}

答案 2 :(得分:0)

对我来说,解决方案最终是使用更高版本的.NET-4.6.2改进了DPI支持,因此当在应用程序清单中应用了高DPI设置时,我曾指导过的应用程序中的此问题已自行解决。

如果您以.NET 4.6.2或更高版本为目标,则隐式启用DPI Scaling。您不需要其他任何东西。

如果您定位到较早的版本,请添加到清单中:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
</application>

并在您的Properties.cs中启用DPI意识:

[assembly: DisableDpiAwareness]

或(由于各种原因,我正在做的)使用必须从app.xaml.cs调用的代码:

    public static bool SetPerMonitorDpiAwareness(ProcessDpiAwareness type = ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware)
    {
        try
        {
            // for this to work make sure [assembly: DisableDpiAwareness]
            ProcessDpiAwareness awarenessType;
            GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awarenessType);
            var result = SetProcessDpiAwareness(type);
            GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awarenessType);

            return awarenessType == type;
        }
        catch
        {
            return false;
        }            
    }

要在App.xaml.cs启动代码中的某处调用

        try
        {   // Multi-Monitor DPI awareness for screen captures
            // requires [assembly: DisableDpiAwareness] set in assemblyinfo
            bool res = WindowUtilities.SetPerMonitorDpiAwareness(ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware);
        }
        catch {  /* fails not supported on Windows 7 and older */ }

同样,在定位到.NET 4.6.2或更高版本之后,所有这些都不再需要,并且一切正常。显式代码使您可以更好地控制要使用的配置文件。

.NET 4.6.2引入了DPI缩放方面的许多改进,包括多监视器缩放支持(以前仅支持主监视器),并且自动对大多数托管控件(包括Web浏览器控件)执行正确的操作。鉴于这些天的大多数计算机都位于基于Windows更新4.6.2的.NET 4.7.x或4.6.2上,因此应被视为WPF IMHO的基准。

  

注意:如果在应用程序运行时在Windows中切换DPI设置,则还有一些可以捕获的事件可以告诉您DPI更改。除了重新启动外,您无法执行其他任何操作,因为该应用程序实际上并未获取更改,但至少您可以让用户知道DPI已更改,并且他们必须重新启动才能适应新的DPI设置。 / p>

答案 3 :(得分:0)

您需要从OS dpi确定浏览器缩放百分比,然后通过其ActiveX包装器设置浏览器缩放。不幸的是,该包装器未在WPF中公开,因此您需要添加对“ Microsoft Internet控件”的引用,并使用反射来获取它。

Private Sub Browser_LoadCompleted(sender As Object, e As NavigationEventArgs)
    Dim source = PresentationSource.FromDependencyObject(sender)
    Dim matrix As Matrix = source.CompositionTarget.TransformToDevice
    If matrix.Determinant > 1 Then
        Dim zoomLevel As Integer = matrix.Determinant * 100
        Dim ie As SHDocVw.InternetExplorer = GetType(WebBrowser).GetField("_axIWebBrowser2", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(sender)
        ie.ExecWB(SHDocVw.OLECMDID.OLECMDID_OPTICAL_ZOOM, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, zoomLevel, IntPtr.Zero)
    End If
End Sub

其他阅读内容:

https://dzimchuk.net/best-way-to-get-dpi-value-in-wpf/

https://weblog.west-wind.com/posts/2016/Aug/22/Detecting-and-Setting-Zoom-Level-in-the-WPF-WebBrowser-Control