简体   繁体   English

WPF Web 浏览器控制和 DPI 缩放

[英]WPF Web Browser Control and DPI Scaling

I'm working with a WPF application that uses the Web Browser control and I'm having issues with High DPI scaling.我正在使用一个使用 Web 浏览器控件的 WPF 应用程序,但我遇到了高 DPI 缩放问题。

It looks like the Web Browser control is not properly respecting the DPI settings of the system, while the rest of the WPF application is properly scaling the UI.看起来 Web 浏览器控件没有正确遵守系统的 DPI 设置,而 WPF 应用程序的其余部分正在正确缩放 UI。 This means that on higher scale levels the WPF interface gets larger while the Web browser content stays on the original, now smaller looking size.这意味着在更高的比例级别上,WPF 界面会变得更大,而 Web 浏览器内容保持原来的外观,现在看起来更小。

Here's an example of screen captures from a WPF app that uses two Web Browser Controls.下面是使用两个 Web 浏览器控件的 WPF 应用程序的屏幕截图示例。

100% Scaling: 100% 缩放:

在此处输入图片说明

150% Scaling: 150% 缩放:

在此处输入图片说明

Notice in the second image the Web Browser scaling is much smaller than in the first picture relative to the main form content (toolbar/menu/statusbar).请注意,相对于主表单内容(工具栏/菜单/状态栏),第二张图片中的 Web 浏览器缩放比例比第一张图片小得多。

Is there some way to force the Web Browser control to properly use High DPI settings inherited from the application?是否有某种方法可以强制 Web 浏览器控件正确使用从应用程序继承的高 DPI 设置?

This MSDN link: Addressing DPI Issues此 MSDN 链接:解决 DPI 问题

shows a really low level approach (at the bottom of doc) implementing custom Web browser COM interfaces, but I'm wondering if there might be a cleaner way to address this.显示了一个非常低级的方法(在文档底部)实现自定义 Web 浏览器 COM 接口,但我想知道是否有更简洁的方法来解决这个问题。

I have found what I think is the best way to achieve the required functionality (provided, that you anyway need to specify FEATURE_BROWSER_EMULATION in registry to use most recent IE version) .我发现了我认为实现所需功能的最佳方法(前提是您无论如何都需要在注册表中指定FEATURE_BROWSER_EMULATION以使用最新的 IE 版本)

All you need to do, is to create a new key in HKCU\\Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl named FEATURE_96DPI_PIXEL and add your executable entry there of type DWORD (32-bit) , with application exe as a key name and value of 1 .您需要做的就是在HKCU\\Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl创建一个名为FEATURE_96DPI_PIXEL的新键,并在其中添加类型为DWORD (32-bit)可执行条目,以应用程序 exe 作为键名和1值。

Check the setting on startup of the application, before actually instantiating the WebBrowser component and you should be fine.在实际实例化 WebBrowser 组件之前检查应用程序启动时的设置,你应该没问题。

Original post (with other possible features): https://www.reddit.com/r/dotnet/comments/2j5m6m/wpf_webbrowser_control_alternatives/原始帖子(具有其他可能的功能): https : //www.reddit.com/r/dotnet/comments/2j5m6m/wpf_webbrowser_control_alternatives/

Here is the code of a utility class that allows you to deactivate WPF's WebBrowser context menu.下面是一个实用程序类的代码,它允许您停用 WPF 的 WebBrowser 上下文菜单。 It also allows you to suppress script errors ( WPF WebBrowser control - how to supress script errors ?) and change IE's DOCHOSTUIFLAG .它还允许您抑制脚本错误( WPF WebBrowser 控件 - 如何抑制脚本错误?)并更改 IE 的DOCHOSTUIFLAG

Usage sample:使用示例:

public partial class Player : Window
{
    private WebBrowserHostUIHandler _wbHandler;

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

Utility code:实用程序代码:

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
}

The solution for me eventually was to use a later version of .NET - 4.6.2 has improved DPI support and so this issue in the application I mentinoed resolved itself when High DPI settings are applied in the application manifest.我的解决方案最终是使用更高版本的 .NET - 4.6.2 改进了 DPI 支持,因此当在应用程序清单中应用高 DPI 设置时,我提到的应用程序中的这个问题会自行解决。

If you're targeting .NET 4.6.2 or later DPI Scaling is implicitly enabled.如果您的目标是 .NET 4.6.2 或更高版本,则隐式启用 DPI 缩放。 You shouldn't need anything else.你不应该需要其他任何东西。

If you target earlier versions either add to the manifest:如果您的目标是早期版本,请添加到清单中:

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

and enable DPI Awareness in your AssemblyInfo.cs :并在您的AssemblyInfo.cs启用 DPI Awareness:

[assembly: DisableDpiAwareness]

or (as I was doing for various reasons) using code that has to be call from app.xaml.cs :或者(正如我出于各种原因所做的那样)使用必须从app.xaml.cs调用的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;
        }            
    }

To call somewhere in App.xaml.cs startup code:要在 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 */ }

Again all of this was no longer necessary after targeting .NET 4.6.2 or later and things just work.在面向 .NET 4.6.2 或更高版本之后,所有这些都不再需要,并且一切正常。 The explicit code allowed more control over exactly what profile to use.显式代码允许对使用的配置文件进行更多控制。

.NET 4.6.2 introduces a host of improvements with DPI scaling including multi-monitor scaling support (previously only the main monitor was supported) and is automatically doing the right thing with most hosted controls including the Web browser control. .NET 4.6.2 引入了大量 DPI 缩放改进,包括多显示器缩放支持(以前只支持主显示器),并且自动对大多数托管控件(包括 Web 浏览器控件)执行正确的操作。 Given that most machines these days are either on .NET 4.7.x or 4.6.2 based on Windows update targeting 4.6.2 should be considered a baseline for WPF IMHO.鉴于现在大多数机器都基于 .NET 4.7.x 或 4.6.2,基于面向 4.6.2 的 Windows 更新应该被视为 WPF 恕我直言的基准。

Note: If you switch DPI settings in Windows while your app is running, there are also events you can trap that will tell you of the DPI change.注意:如果您在应用程序运行时在 Windows 中切换 DPI 设置,您还可以捕获一些事件,这些事件会告诉您 DPI 更改。 Not much that you can do with this other than restart as the app doesn't actually pick up the changes but at least you can let the user know that the DPI has changed and they have to restart to adjust to the new DPI settings.除了重新启动之外,您可以做的不多,因为应用程序实际上并没有接受更改,但至少您可以让用户知道 DPI 已更改,他们必须重新启动以适应新的 DPI 设置。

You need to determine the browser zoom percentage from the OS dpi, then set the browser zoom via its ActiveX wrapper.您需要根据操作系统 dpi 确定浏览器缩放百分比,然后通过其 ActiveX 包装器设置浏览器缩放比例。 Unfortunately, the wrapper is not exposed in WPF so you'll need to add a reference to "Microsoft Internet Controls" and use reflection to get it.不幸的是,WPF 中没有公开包装器,因此您需要添加对“Microsoft Internet Controls”的引用并使用反射来获取它。

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

Additional reading:补充阅读:

https://dzimchuk.net/best-way-to-get-dpi-value-in-wpf/ 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 https://weblog.west-wind.com/posts/2016/Aug/22/Detecting-and-Setting-Zoom-Level-in-the-WPF-WebBrowser-Control

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM