This blog post describes a simple helper class that can be used to supress scrolling and pinch zoom of the Windows Phone 7 WebBrowser control.

Colin Eberhardt is a Scott Logic technical Evangelist and a Technical Architect for Visiblox, suppliers of high-performance WPF and Silverlight charts.

Developers of Windows Phone 7 application have the full power of IE9 at their disposal in the shape of the WebBrowser control. This control allows you to render both local and remote HTML and JavaScript content within your Silverlight applications. You will find this control used to great effect in RSS readers and Twitter applications such as Rowi, where websites are viewed from within the application rather than by launching the full-blown IE. The WebBrowser control is also used as the host / container for HTML5 based applications, such as Property Finder, the PhoneGap-based application I developed recently.

One problem them people frequently hit against with the WebBrowser control is managing the manipulation events. For example, if you place a WebBrowser control within a Pivot control, how can you pass the horizontal swipe gesture form the WebBrowser to the Pivot?

With HTML5 applications you have a certain amount of control over the browser's pan and zoom behaviour via the viewport metadata HTML element:

<meta name="viewport" content="user-scalable=no" />

Adding the above meta-tag to a HTML page will prohibit the user from zooming via the pinch gesture. However, it does this in a rather clumsy way - the user can still pinch the page, but when they release, it snaps back to its original scale.

Whilst this might be OK in some contexts, for a HTML5 application where intention is to replicate the feel of a native application as closely as possible, this is just not good enough. Another little browser quirk that has a similar effect is the scroll behaviour. Even if a page fits entirely within the area occupied by the WebBrowser control, they can still scroll the content. Again, it simply snaps back to its original location.

The Solution

I initially thought that there would be no way for me to control the behaviour of the WebBrowser control, it is after-all a very thin .NET wrapper around a native control. However, I stumbled across some StackOveflow answers from quetzalcoatl who had done some digging around in the .NET wrapper and had identified an interesting control called the PanZoomContainer.

If you inspect the visual tree of the WebBrowser control you will find that it is assembled as follows:

\-WebBrowser
  \-Border
    \-Border
      \-PanZoomContainer
        \-Grid
          \-Border (*)
            \-ContentPresenter
              \-TileHost

(visual tree dump generated via the oh-so-useful Linq to VisualTree utility!)

The visual tree is quite simple, composed of a few grids and borders. The significant parts are the TileHost, which is the native IE9 component, and the PanZoomContainer. The TileHost does not handle the mouse manipulation events, these are instead handled by the PanZoomContainer, where they are then translated into gestures (i.e. pinch-zoom) with the result fed back to the TileHost.

What this means is that we can intercept the manipulation events as they bubble up to the PanZoomContainer, cancelling them before they are turned into gestures.

The utility class which I have written handles the events on the Border indicated above. When events are received various conditions are checked to identify pan or scroll interactions, with the events being cancelled accordingly.

The complete class is given below:

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using LinqToVisualTree;
using Microsoft.Phone.Controls;

/// <summary>
/// Suppresses pinch zoom and optionally scrolling of the WebBrowser control
/// </summary>
public class WebBrowserHelper
{
  private WebBrowser _browser;

  /// <summary>
  /// Gets or sets whether to suppress the scrolling of
  /// the WebBrowser control;
  /// </summary>
  public bool ScrollDisabled { get; set; }

  public WebBrowserHelper(WebBrowser browser)
  {
    _browser = browser;
    browser.Loaded += new RoutedEventHandler(browser_Loaded);
  }

  private void browser_Loaded(object sender, RoutedEventArgs e)
  {
    var border = _browser.Descendants<Border>().Last() as Border;

    border.ManipulationDelta += Border_ManipulationDelta;
    border.ManipulationCompleted += Border_ManipulationCompleted;
  }

  private void Border_ManipulationCompleted(object sender,
                                            ManipulationCompletedEventArgs e)
  {
    // suppress zoom
    if (e.FinalVelocities.ExpansionVelocity.X != 0.0 ||
        e.FinalVelocities.ExpansionVelocity.Y != 0.0)
      e.Handled = true;
  }

  private void Border_ManipulationDelta(object sender,
                                        ManipulationDeltaEventArgs e)
  {
    // suppress zoom
    if (e.DeltaManipulation.Scale.X != 0.0 ||
        e.DeltaManipulation.Scale.Y != 0.0)
      e.Handled = true;

    // optionally suppress scrolling
    if (ScrollDisabled)
    {
      if (e.DeltaManipulation.Translation.X != 0.0 ||
        e.DeltaManipulation.Translation.Y != 0.0)
        e.Handled = true;
    }
  }
}

Within Property Finder, my HTML5 application, as the user navigates from one page to the next, the JavaScript code notifies the Silverlight container whether the current page should have scrolling disabled, setting the ScrollDisabled property of the above helper class accordingly.

I hope other people find this simple utility class useful. To use it, just cut and paste the code given above. Note, it uses Linq to VisualTree to navigate the WebBrowser visual tree, so you will need to go and grab that also.

Regards, Colin E.