Reputation: 3009
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
, 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.
Upvotes: 0
Views: 1269
Reputation: 32248
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:
Expected Behavior:
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.
zoomMenuItems
. All use the same Click
event handler, zoomToolStripMenuItems_Click
textMenuItems
. All use the same Click
event handler, textToolStripMenuItems_Click
Tag
property of each ToolStripMenuItem (you can of course use any other means to associate a value to a specific ToolStripMenuItem)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:
Upvotes: 1
Reputation: 3009
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.
Upvotes: 0