Full Web Browser Control for Windows Phone 7

Web vs Native vs Hybrid Applications

Windows Phone development has a lot of flavors. There are three different ways to create Windows Phone applications: web based, native and hybrid applications. The first, web based applications, are done and hosted completely on the web and accessed using the phone browser. This type of application is the most portable because it can be accessed through most of the web browsers. The problem with creating web based mobile applications is that you lose most of the functionality that the platform can give you. The second way of creating a Windows Phone application is developing a native application. This type of application is the most commonly used  when you want to  takes advantage of the features that the platform has to offer. The problem with native applications is that cross platform maintenance tends to be a challenge because of the time and knowledge it consumes. And last but not least the best of the both worlds, what I like to call the Hybrid Mobile Application. This type of application enjoys the portability of a web based application and lets us work with the features that the platform can offer us. A an example of the advantages of a hybrid application would be a search application that uses the GPS on the mobile to get more accurate results and a web browser to show the results.

The Problem

Developing hybrid applications for the Windows Phone platform requires that we include a web browser control and extend the application with any feature with the phone functionality. But the problem that I have encountered while developing this type of applications is that the Web Browser Control included with the Windows Phone Developer Tools does not include any history or navigation as in forward, back and refresh. The lack of this functionality makes the web browser control hard to develop with. In this post we are going to be creating a browser control that enables forward, back and refresh navigation and also saves the history. Also we are going to include a progress bar to let the user know that we are navigating and that the application did not freeze.

Creating the Control

First we are going to create a Windows Phone Class Library Project on Visual Studio 2010.

  1. Open Visual Studio 2010 go to File->New->Project…
  2. Select Windows Phone Class Library from the project templates.
  3. Add FullBrowserControl to the project name
  4. Press Ok to create the project.
New Windows Phone Class Library Project

New Windows Phone Class Library Project

After creating the project delete the Class1.cs auto generated file and add a new Windows Phone User Control.

  1. Right click on the FullBrowserControl solution, Add->New Item
  2. In the dialog add a Windows Phone User Control and name it FullBrowserControl.xaml
  3. Click Ok.
New Windows Phone User Control Item

New Windows Phone User Control Item

Creating the Interface of Our Control

Once we have added the new User Control we are going to create our Full Browser Control’s user interface. For this we first need to download and install the Silverlight Toolkit for Windows Phone which includes a lot of new controls for Windows Phone and in particular the PerformanceProgressBar that we are going to be using to alert the user that the browser is navigating.  Now let’s add some row definitions to the User Control content grid:

<Grid Name="ContentGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
</Grid>

Now we will add the PerformanceProgressBar and we are going to bind the IsIndeterminate property to a ShowProgress DependencyProperty that we are going to be creating in a moment.

<Grid Name="ContentGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <toolkit:PerformanceProgressBar             x:Name="performanceProgressBar"             IsIndeterminate="{Binding ShowProgress}"             Background="{StaticResource PhoneBackgroundBrush}"         />
 </Grid>

And last but not least we are going to add a regular WebBrowser control. it is very important that the IsScriptEnabled property is set to true because we are going to be invoking JavaScript from the browser in order to navigate between pages. Also we are going to need the Navigated, Navigating and Loaded event handlers.

<Grid Name="ContentGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <toolkit:PerformanceProgressBar             x:Name="performanceProgressBar"             IsIndeterminate="{Binding ShowProgress}"             Background="{StaticResource PhoneBackgroundBrush}"         />

        <phone:WebBrowser             Name="TheWebBrowser"             Grid.Row="1"             Background="{StaticResource PhoneBackgroundBrush}"             IsScriptEnabled="True"             Navigated="TheWebBrowser_Navigated"             Navigating="TheWebBrowser_Navigating"             Loaded="TheWebBrowser_Loaded"         />
 </Grid>

Now we need to open the FullWebBrowser.xaml.cs file to start working our logic.

Creating our Logic

In order to set up all the bindings correctly we need to set the DataContext of the grid to the FullWebBrowser object like this:

public partial class FullWebBrowser : UserControl
{
    //ctr
    public FullWebBrowser()
    {
        InitializeComponent();

        //Set the data context for the bindings.
        ContentGrid.DataContext = this;
    }
}

Now that we have set up the DataContext of our FullBrowserControl we are going to add some fields that we are going to be using for our logic. First we are going to add a Stack named  _NavigatingUrls that is going to be used to store the previous urls that we can navigate to. Then we need to add an ObservableCollection named _History that is where we are going to store the navigation history for our browser, we also need to create a readonly property named History that will let us access the _History field. And last but not least we need a flag to validate if the user is navigating back or not, for this we are going to use a bool variable named _IsNavigatingBackward with the false as the default value.

#region Fields

    //The navigation urls of the browser.
    private readonly Stack<Uri> _NavigatingUrls = new Stack<Uri>();

    //The history for the browser
    private readonly ObservableCollection<string> _History =
       new ObservableCollection<string>();

    //Flag to check if the browser is navigating back.
    bool _IsNavigatingBackward = false;

    #endregion Fields

    #region Properties

    ///
<summary>
    /// Gets the History property for the browser.
    /// </summary>

    public ObservableCollection<string> History
    {
        get { return _History; }
    }

#endregion Properties

Now we are going to create three dependency properties. One is a ShowProgress boolean value that is the flag we are going to use to display our progress bar while we navigate in our browser also as you remember is the value that we binded to the IsIndefinite property of the PerformanceProgressBar. The second, a CanNavigateBack boolean value to determine if the browser is able to navigate back and the third a InitialUri string that is going to be the initial uri that our browser should navigate when the browser loads for the first time.

#region Dependency Properties

    ///
<summary>
    /// ShowProgress Dependency Property
    /// </summary>

    public static readonly DependencyProperty ShowProgressProperty =
        DependencyProperty.Register("ShowProgress", typeof(bool),
        typeof(FullWebBrowser), new PropertyMetadata((bool)false));

    ///
<summary>
    /// Gets or sets the ShowProgress property. This dependency property
    /// indicates whether to show the progress bar.
    /// </summary>

    public bool ShowProgress
    {
        get { return (bool)GetValue(ShowProgressProperty); }
        set { SetValue(ShowProgressProperty, value); }
    }

    ///
<summary>
    /// CanNavigateBack Dependency Property
    /// </summary>

    public static readonly DependencyProperty CanNavigateBackProperty =
        DependencyProperty.Register("CanNavigateBack", typeof(bool),
        typeof(FullWebBrowser), new PropertyMetadata((bool)false));

    ///
<summary>
    /// Gets or sets the CanNavigateBack property. This dependency property
    /// indicates whether the browser can go back.
    /// </summary>

    public bool CanNavigateBack
    {
        get { return (bool)GetValue(CanNavigateBackProperty); }
        set { SetValue(CanNavigateBackProperty, value); }
    }

    ///
<summary>
    /// InitialUri Dependency Property
    /// </summary>

    public static readonly DependencyProperty InitialUriProperty =
        DependencyProperty.Register("InitialUri", typeof(string),
        typeof(FullWebBrowser), new PropertyMetadata((string)String.Empty));

    ///
<summary>
    /// Gets or sets the InitialUri property. This dependency property
    /// indicates the initial uri for the browser.
    /// </summary>

    public string InitialUri
    {
        get { return (string)GetValue(InitialUriProperty); }
        set { SetValue(InitialUriProperty, value); }
    }

#endregion Dependency Properties

Now that we have all our Fields, Properties and Dependency Properties set up we need to create the event handlers that we added to the WebBrowser control.

#region Event Handlers

    void TheWebBrowser_Navigating(object sender,
        Microsoft.Phone.Controls.NavigatingEventArgs e)
    {
    }

    void TheWebBrowser_Navigated(object sender,
        System.Windows.Navigation.NavigationEventArgs e)
    {
    }

    private void TheWebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
    }

 #endregion Event Handlers

First let’s start with the Loaded event. We are going to use the InitialUri and navigate to it.

#region Event Handlers

    void TheWebBrowser_Navigating(object sender,
        Microsoft.Phone.Controls.NavigatingEventArgs e)
    {
    }

    void TheWebBrowser_Navigated(object sender,
        System.Windows.Navigation.NavigationEventArgs e)
    {
    }

    private void TheWebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
        //When we load our browser if we specified an initial uri
        //we navigate to it.
        if(!String.IsNullOrEmpty(InitialUri))
            TheWebBrowser.Navigate(new Uri(InitialUri));
    }

 #endregion Event Handlers

By using the Navigate method of the browser we activate the Navigating event. Every time that we start navigating we want to show the PerformanceProgressBar to alert the user that the browser is navigating. To do this we set the ShowProgress dependency property to true.

#region Event Handlers

    void TheWebBrowser_Navigating(object sender,
        Microsoft.Phone.Controls.NavigatingEventArgs e)
    {
        //We show the progress bar when we start navigating.
        ShowProgress = true;
    }

    void TheWebBrowser_Navigated(object sender,
        System.Windows.Navigation.NavigationEventArgs e)
    {
    }

    private void TheWebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
        //When we load our browser if we specified an initial uri
        //we navigate to it.
        if(!String.IsNullOrEmpty(InitialUri))
            TheWebBrowser.Navigate(new Uri(InitialUri));
    }

 #endregion Event Handlers

The last event is the browser Navigated handler. Here is where we are going to be giving our control the logic to determine if he can navigate backward or forward and save a history of our navigation every time we go to a different url or navigate within a website. The first thing we are going to do is verify if we are navigating back or forward and if we can navigate back. If we are navigating forward meaning that the _IsNavigatingBackward flag is set to false we are going to add the new url, the one that we are navigating to, to our url stack, also we will add it to our history but only if the url is not already in our history. But if the _IsNavigatingBackward flag is set to true and we can navigate back we just need to remove the top url from our _NavigatingUrls stack. Then we check if there is only one url in our stack remaining. If there is more than 1 url in our stack that means that we can navigate back and we set the CanNavigateBack flag to true, if there is only one left the we cannot navigate back so we set the CannavigateBack flag to false. And last but not least we can hide our PerformanceProgressBar setting our ShowProgress dependency property to false.

#region Event Handlers

    void TheWebBrowser_Navigating(object sender,
        Microsoft.Phone.Controls.NavigatingEventArgs e)
    {
        //We show the progress bar when we start navigating.
        ShowProgress = true;
    }

    void TheWebBrowser_Navigated(object sender,
        System.Windows.Navigation.NavigationEventArgs e)
    {
        //If we are Navigating Backward and we Can Navigate back,
        //remove the last uri from the stack.
        if (_IsNavigatingBackward == true &amp;amp;&amp;amp; CanNavigateBack)
            _NavigatingUrls.Pop();

        //Else we are navigating forward so we need to add the uri
        //to the stack.
        else
        {
            _NavigatingUrls.Push(e.Uri);

            //If we do not have the navigated uri in our history
            //we add it.
            if (!_History.Contains(e.Uri.ToString()))
                _History.Add(e.Uri.ToString());
        }

        //If there is one address left you can't go back.
        if (_NavigatingUrls.Count > 1)
            CanNavigateBack = true;
        else
            CanNavigateBack = false;

        //Finally we hide the progress bar.
        ShowProgress = false;
    }

    private void TheWebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
        //When we load our browser if we specified an initial uri
        //we navigate to it.
        if(!String.IsNullOrEmpty(InitialUri))
            TheWebBrowser.Navigate(new Uri(InitialUri));
    }

 #endregion Event Handlers

Finally we need to add our private methods to navigate back, forward and refresh our FullBrowserControl. Let’s start with the navigate forward method. To navigate forward we simply need to set the _IsNavigatingBackward to false and invoke the JavaScript code to navigate forward (“history,go(1)”).

 ///
<summary>
 /// Used to navigate forward.
 /// </summary>

 public void NavigateForward()
 {
     _IsNavigatingBackward = false;
     TheWebBrowser.InvokeScript("eval", "history.go(1)");
 }

Navigating back and refresh work in the same way as NavigateForward. When navigating back we set the _IsNavigatingBack to true and invoke the JavaScript code to navigate back (“history.go(-1)”).  To refresh our browser we only need to invoke the JavaScript code for refresh (“history.go()”)  and we do not need to set any flag because refreshing the browser does not activate neither the Navigating nor Navigated handlers of the browser so we do not change anything on our history or navigating uris.

 ///
<summary>
 /// Used to navigate back.
 /// </summary>

 public void NavigateBack()
 {
     _IsNavigatingBackward = true;
     TheWebBrowser.InvokeScript("eval", "history.go(-1)");
 }

 ///
<summary>
 /// Used to refresh the browser.
 /// </summary>

 public void RefreshBrowser()
 {
     TheWebBrowser.InvokeScript("eval", "history.go()");
 }

And the last method we are going to expose is a Navigate method that calls the internal Browser to navigate to a specified url.

 ///
<summary>
 /// Used to navigate to a specified url.
 /// </summary>

 /// <param name="Url">The web address.</param>
 public void Navigate(string Url)
 {
     TheWebBrowser.Navigate(new Uri(Url, UriKind.Absolute));
 }

That is it. Our FullBrowserControl is ready. We have a browser that navigates to urls, navigates back, navigates forward, refresh and saves the history for us and let us handle the back button marketplace requirement with the CanNavigateBackward property when developing hybrid applications. To see how it works I am including the source code here (FullBrowserSample.zip).

P.S. There are some methods remaining like the InvokeScript and the InvokeScriptCompleted handler that can be exposed as needed.

Thanks’ everyone for reading. Come back later for more posts on Francisco’s Development Corner. ENJOY!!!!