Set general text size option in WinForms WebBrowser control

1k views Asked by At

We have a WinForms app for the classic .NET Framework 4.7. One of the forms contains an instance of the WebBrowser control to render HTML documents. It turned out that if someone changes the Text Size option in Microsoft Help Viewer in Visual Studio

Text Size option in Microsoft Help Viewer

, this also affects the size of text in our WebBrowser-based viewer.

I suspect it is a global setting for the MSIE rendering engine the WebBrowser control is based on. As such, there can be other apps in which the user may change this setting and this will affect our app.

Is there a way to completely ignore this setting while rendering our HTMLs? We tried to specify explicit text sizes for the HTML tags in our HTML pages, but it seems, this does not help. It looks like the MSIE HTML renderer applies its scale factor after pages were rendered using the specified text sizes.

If it is not possible to ignore that global setting, is there an API we can use to control this Text Size parameter from our app? We could use it to provide settings like Microsoft Help Viewer does, and at least could provide the user with a similar choice list to adjust text size for comfort reading.

If possible, provide a solution in C# or VB.NET.

2

There are 2 answers

7
Jimi On

An example about synchronizing the WebBrowser Control's viewport scale, both Zoom and Text Size, to the settings applied by other applications (e.g., Internet Explorer or, as in this case, Help Viewer).

The Problem:

  • When Zoom a FontSize scale options are applied by an application that uses the Internet Explorer Engine to render HTML content, the WebBrowser Control, when first initialized, applies these settings to its view.

Expected Behavior:

  • The Application should provide means to change Zoom and FontSize scale, independently of what other applications may have set.
  • The new settings should be applied without recreating the Control's Handle

In the sample code, a specialized class, WebBrowserHelper, contains a method that reads from the Registry the current Zoom and FontSize settings, to update the MenuItems to the current value:

HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Zoom
=> ZoomFactor Key

HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\International\Scripts\3
=> IEFontSize Key

The ZoomFactor Key stores Zoom levels multiplied by 1000: e.g., 150% Zoom has a value of 150000, 75% Zoom has a value of 75000

The WebBrowserHelper's SetZoom() method uses the WebBrowser ActiveX instance to set the Zoom level, calling its ExecWb method, passing as OLECMDID argument the OLECMDID_OPTICAL_ZOOM value, the OLECMDEXECOPT argument is set to OLECMDEXECOPT_DONTPROMPTUSER and the pvaIn argument (the Zoom value) is set to the Integer value of the Zoom level specified.

▶ This also updates the ZoomFactor Registry Key.

public static void SetZoom(WebBrowser browser, int zoomValue)
{
    dynamic activex = browser.ActiveXInstance;
    activex.ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, zoomValue, 0);
}

The IEFontSize and IEFontSizePrivate Keys store a binary value, in the rage [0-4]. These values loosely correspond to the x-small, small, medium, large, x-large CSS settings.
The FontSize scale is applied to the Document Body, using a multiplier: [Value + 1] * 4.

The SetTextScale() method uses the WebBrowser ActiveX instance to set the FonSize scale, calling its ExecWb() method, passing as OLECMDID argument the OLECMDID_ZOOM value, the OLECMDEXECOPT argument is set to OLECMDEXECOPT_DONTPROMPTUSER and the pvaIn argument (the Font scale value) is set to an Integer value in the range [0, 4] as specified.

▶ This also updates the IEFontSize Registry Key.

public static void SetTextScale(WebBrowser browser, int textValue)
{
    dynamic activex = browser.ActiveXInstance;
    activex.ExecWB(OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, textValue, 0);
}

Another option is to reset both Zoom and FontSize to default values before a WebBrowser session is started.
You can call the WebBrowserHelper.InternetResetZoomAndFont() method. Setting both arguments to true, it will reset the Zoom level to 100% and FontSize to Medium.

WebBrowserHelper.InternetResetZoomAndFont(true, true);

▶ Here, I'm adding some ToolStripMenuItems that allow to set the Zoom value and FontSize - which is set to a pre-defined scale - as applied by Internet Explorer 11.

  • All ToolStripMenuItem that sets the Zoom are grouped in a collection, named zoomMenuItems. All use the same Click event handler, zoomToolStripMenuItems_Click
  • All ToolStripMenuItem that sets the FontSize scale are grouped in a collection named textMenuItems. All use the same Click event handler, textToolStripMenuItems_Click
  • The Zoom and Fontscale values are set to the Tag property of each ToolStripMenuItem (you can of course use any other means to associate a value to a specific ToolStripMenuItem)
  • The current values of Zoom and FontSize as stored in a named Value Tuple, browserViewSettings

Assume the WebBrowser Control is named webBrowser1:

public partial class SomeForm : Form
{
    private List<ToolStripMenuItem> textMenuItems = null;
    private List<ToolStripMenuItem> zoomMenuItems = null;
    private (int Zoom, int TextSize) browserViewSettings = (100, 2);

    public SomeForm()
    {
        InitializeComponent();

        textMenuItems = new List<ToolStripMenuItem> { textSmallestMenuItem, textSmallerMenuItem, textMediumMenuItem, textLargerMenuItem, textLargestMenuItem };
        zoomMenuItems = new List<ToolStripMenuItem> { zoom75MenuItem, zoom100MenuItem, zoom125MenuItem, zoom150MenuItem, zoom175MenuItem, zoom200MenuItem, zoom250MenuItem, zoom300MenuItem, zoom400MenuItem };
        // On startup, reads the current settings from the Registry
        // and updates the MenuItems, to reflect the current values
        UpdateMenus(true);
    }

    // MenuItems that sets the Font scale value
    private void textToolStripMenuItems_Click(object sender, EventArgs e)
    {
        var item = sender as ToolStripMenuItem;
        int newTextValue = Convert.ToInt32(item.Tag);
        if (newTextValue != browserViewSettings.TextSize) {
            browserViewSettings.TextSize = newTextValue;
            UpdateDocumentSettings(this.webBrowser1);
            UpdateMenus(false);
        }
    }

    // MenuItems that sets the Zoom level value
    private void zoomToolStripMenuItems_Click(object sender, EventArgs e)
    {
        var item = sender as ToolStripMenuItem;
        int newZoomValue = Convert.ToInt32(item.Tag);
        if (newZoomValue != browserViewSettings.Zoom) {
            browserViewSettings.Zoom = newZoomValue;
            UpdateDocumentSettings(this.webBrowser1);
            UpdateMenus(false);
        }
    }

    // Sets the new selected values and the related MenuItem
    private void UpdateDocumentSettings(WebBrowser browser)
    {
        if (browser == null || browser.Document == null) return;
        WebBrowserFeatures.WebBrowserHelper.SetZoom(browser, browserViewSettings.Zoom);
        WebBrowserFeatures.WebBrowserHelper.SetTextScale(browser, browserViewSettings.TextSize);
    }

    // Updates the MenuItem to the current values 
    private void UpdateMenus(bool refresh)
    {
        if (refresh) {
            browserViewSettings = WebBrowserFeatures.WebBrowserHelper.InternetGetViewScale();
        }
        
        zoomMenuItems.ForEach(itm => {
            int refValue = Convert.ToInt32(itm.Tag);
            itm.Checked = refValue == browserViewSettings.Zoom;
            if (itm.Checked) {
                zoomToolStripMenuItem.Text = $"Zoom ({refValue}%)";
            }
        });
        textMenuItems.ForEach(itm => itm.Checked = Convert.ToInt32(itm.Tag) == browserViewSettings.TextSize);
    }

WebBrowserHelper class:

using System.Security;
using System.Security.AccessControl;
using Microsoft.Win32;

public class WebBrowserHelper
{
    private const int OLECMDID_ZOOM = 19;
    private const int OLECMDID_OPTICAL_ZOOM = 63;
    private const int OLECMDEXECOPT_DONTPROMPTUSER = 2;

    // Applies the Zoom value. It also updates the Registry
    public void SetZoom(WebBrowser browser, int zoomValue)
    {
        dynamic activex = browser.ActiveXInstance;
        activex.ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, zoomValue, 0);
    }

    // Applies the FontSize scale. It also updates the Registry
    public void SetTextScale(WebBrowser browser, int textValue)
    {
        dynamic activex = browser.ActiveXInstance;
        activex.ExecWB(OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, textValue, 0);
    }

    private static string keyZoomName = @"Software\Microsoft\Internet Explorer\Zoom";
    private static string keyTextName = @"Software\Microsoft\Internet Explorer\International\Scripts\3";
    private static string keyValueTextReset = "ResetZoomOnStartup";
    private static string keyValueZoomReset = "ResetZoomOnStartup2";

    public static  (int Zoom, int TextSize) InternetGetViewScale()
    {
        int zoomValue, textValue;
        using (var zoomKey = Registry.CurrentUser.OpenSubKey(keyZoomName,
            RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey)) {
            zoomValue = (int)zoomKey.GetValue("ZoomFactor", 100000) / 1000;
        }
      
        using (var textKey = Registry.CurrentUser.OpenSubKey(keyTextName,
            RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey)) {
            var keyBValue = BitConverter.GetBytes(2);
            textValue = BitConverter.ToInt32((byte[])textKey.GetValue("IEFontSize", keyBValue), 0);
        }
        return (zoomValue, textValue);
    }

    public static void InternetResetZoomAndFont(bool resetZoom, bool resetFontSize)
    {
        int keyZoomValue = resetZoom ? 1 : 0;
        int keyFontValue = resetFontSize ? 1 : 0;
        using (var zoomKey = Registry.CurrentUser.OpenSubKey(keyZoomName,
            RegistryKeyPermissionCheck.ReadWriteSubTree,
            RegistryRights.WriteKey)) {
            zoomKey.SetValue(keyValueZoomReset, keyZoomValue, RegistryValueKind.DWord);
            zoomKey.SetValue(keyValueTextReset, keyFontValue, RegistryValueKind.DWord);
        }
        var current = InternetGetViewScale();
        if (resetZoom) current.Zoom = 100;
        if (resetFontSize) current.TextSize = 2;
        InternetSetViewScale(current.Zoom, current.TextSize);
    }
}

This is how it works:

WebBrowser control Zoom and FontSize

0
TecMan On

Here is the setter of the TextSize property of our enhanced WebBrowser control that does what we need:

set
{
    IOleCommandTarget m_WBOleCommandTarget = GetOleCommandTarget();
    if (m_WBOleCommandTarget != null)
    {
        if (((int)value > (int)-1) && ((int)value < (int)5))
        {
            IntPtr pRet = m_NullPointer;
            try
            {
                pRet = Marshal.AllocCoTaskMem((int)1024);
                Marshal.GetNativeVariantForObject((int)value, pRet);

                int hr = m_WBOleCommandTarget.Exec(m_NullPointer,
                    (uint)OLECMDID.OLECMDID_ZOOM,
                    (uint)OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
                    pRet, m_NullPointer);
                Marshal.FreeCoTaskMem(pRet);
                pRet = m_NullPointer;
                if (hr == Hresults.S_OK)
                    m_enumTextSize = (TopicTextSize)value;
            }
            catch (Exception)
            {
            }
            finally
            {
                if (pRet != m_NullPointer)
                    Marshal.FreeCoTaskMem(pRet);
            }
        }
    }
}

public enum OLECMDID
{
    ...
    OLECMDID_ZOOM = 19,
    ...
}

public IOleCommandTarget GetOleCommandTarget()
{
    dynamic ax = this.ActiveXInstance;

    // IHtmlDocument2 also implements IOleCommandTarget
    var qi = (IOleCommandTarget)ax.Document;
    return qi;
}

We use the OLECMDID_ZOOM parameter to work with the IEFontSize registry setting.

The source code for our solution was found in the following CodeProject article:

The most complete C# Webbrowser wrapper control

It contains other code snippets that can be helpful in other situations.