Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A better WPF-Browser-Control (IE-Wrapper)

0.00/5 (No votes)
16 Aug 2013 2  
This article provides a better browser control than the one included in WPF

Introduction

This article provides a better browser control than the one included in WPF. It provides bindable Properties and commands, support for text scaling and offers some more features commonly desired.

This is however not a fully blown control with WPF graphics (i.e. support for Opacity, RenderTransform, retarded drawing). Instead it still uses the IE-COM control with it's old fashioned GDI-graphics, WPF-hosted and all it's disadvantages. But hey, it's interface is comparatively lovely. 

Background

The provided WPF Browser control (System.Windows.WebBrowser) is hardly a real WPF control. Neither does it provide the necessary and bindable properties, nor does it provide commands nor enough methods. Thus it is hardly usable as such and not usable in an MVVM environment. Customization and programming of this control is also quite cumbersome, since this control's philosophy is to let the user deal with all the IE-COM-Interface-clutter. The WinForms WebBrowser in contrast does provide a nice class shell around the COM-Interface, which makes its usage quite convenient. There are ways to address some problems like a bindable Source with attached properties, however. For a fully usable control, a wrapper is needed.

Solution

This article will show how to create a wrapper for the WPF WebBrowser control that makes it for the majority of the cases easily usable as MVVM control. At the foremost it creates a bindable Source Dependency Property and binds some of the NativationCommands commands. It does not provide a similar COM-Interface wrapper as the one of WinForms - even Microsoft decided that this is just too much. And of course this wrapper will still not make the embedded Internet Explorer become a native WPF control that is able to rotate, blend and animate its content. It will still run in its own HWND and thus not respecting WPF settings that deal with opacity or transformation. In order to accomplish this, Awsomium is suggested. This is a Chromium based browser control that renders to a bitmap and comes as a native WPF-Control.


Using the code

The provided example can be called quick and dirty. But it also shows how WPF like the wrapper control actually is. There is no code behind. Everything has been done using bindings within the window. You will have to use tab to make it navigate once you entered an URI in the URL-bar. However in a real world scenario it is suggested to implement a ViewModel with two Url properties that are bound to CurrentUrl and BindableSource. Then assign and use them as desired. The separation is necessary, because otherwise a change to BindableSource will always trigger a new navigation.

<Grid>
<Button Content="&lt;"    Command="NavigationCommands.BrowseBack" 
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content="↕"     Command="NavigationCommands.Refresh" 
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content="►"     IsDefault="True"/>
<Button Content="■"     Command="NavigationCommands.BrowseStop" 
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content="&gt;"     Command="NavigationCommands.BrowseForward" CommandTarget="{Binding ElementName=webBrowser}"/>
<TextBox Text="{Binding ElementName=webBrowser,Path=BindableSource}" />
<Slider Margin="0,1,0,68" Name="slider1" Orientation="Vertical" 
                HorizontalAlignment="Right"
                Width="25" Minimum="20" Maximum="400"
        Value="{Binding ElementName=webBrowser,Path=Zoom}"/>
<local:WpfWebBrowserWrapper Margin="12,42,31,41" x:Name="webBrowser" />
<CheckBox Content="Navigating" IsChecked="{Binding ElementName=webBrowser,Path=IsNavigating}" Height="23" VerticalAlignment="Bottom" IsEnabled="False" />
<TextBlock Height="23" Margin="84,25,67,0" Name="textBlock1" Text="{Binding ElementName=webBrowser,Path=CurrentUrl}" VerticalAlignment="Top" />
<Button Command="NavigationCommands.GoToPage" CommandParameter="http://codeproject.com"
        CommandTarget="{Binding ElementName=webBrowser}" 
                Content="Hme" Height="23" HorizontalAlignment="Left" /> 
</Grid> 

This is the core source of the basic web browser. It works pretty much out of the box.

Technical

The control developed is a ContentControl. Thus it is a fully grown control that plays a normal role in WPF's logical tree. Since Inheritance cannot be used, wrapping is the method to go. And the content control's content is the original, well known WPF-WebBrowser-control. By setting it's Horizontal- and Vertical alignment to Stretch, it will always fill the wrapper control.

Properties

Once in control of the WebBrowser control, it's possible to garnish it with Dependency Properties.

The BindableSource property is the actual URL, that can be assigned or bound. Reading the value will provide the original string. Therefore there is another property with the navigated url CurrentUrl. This can also be bound but shlould be read only (Mode=OneWayToSource). So quick binding support is there. But the experience will not be awesome. For a real world program, there is no way around a proper ViewModel with several Properties and usage of commands.

The Zoom Property seamlessly encapsulates the built in zooming functionality and hides all the COM clutter.

The IsNavigating Property switches between true and false and thus allows binding to visibility of spinners or similar controls. Here a check box has been abbused.

Commands

For explicitly navigating, a NavigationCommands.GoToPage command has been added. The parameter is the url. Other NavigationCommands that can be assigned to buttons are: BrowseForward, BrowseBack and Refresh and BrowseStop. Command Binding on different branches of the same logical tree does not always work in WPF. Actually rather seldom. To overcome this, use the CommandTarget property on those controls.

<Button Command="NavigationCommands.GoToPage" CommandParameter="http://codeproject.com" 
        CommandTarget="{Binding ElementName=webBrowser}" Content="Hme" />  

The advantage of commands is that they can also be "called" from the control.

COM

In order to do more COM programming and customisation, a ActiveXControl -property was added. It exposes a private field of the original control to directly operate on the SVxxxxx.InternetExplorer interface.

Error Handling

One painful property of this embedded IE is its loudness. By default script errors pop up as error windows and disturb the usage. One solution is to set the Silent Property of InternetExplorer to true

ActiveXControl.Silent=true;

The downside of this is that no dialog pops up anymore. Not even a HTTP-Authentication dialog. But this is in most cases desired. Therefore this wrapper control catches and swallows script errors by default. It does so by subscribing a C#-event handler to the DOM-Event document.window.onerror, which is also exposed by a COM-interface (IHTMLWindowEvents_onerror). However, the provided interface is wrong/insufficient and does not define the bool return value that is actually there. Therefore this wrapper uses its own Interface definition. Returning true in the onerror handler prevents those messages from popping up. It is not configurable. Instead a Dependency Property LastError will be set. This can be subscribed to and thus all errors can be either collected or the last error can be bound to a control.

Developing WPF-Controls

This article also shows on a small scale, how to build WPF controls using Dependency Properties and how to set them ‘from the inside’. Since .Net 4.0 SetCurrentValue() must be used. So the bindings of the corresponding DepProps will not be destroyed. This project therefore is minimum .NET 4.0. In .NET 3.5 assignments or SetValue() must be used. This will not destroy bindings in that case. The project can be downgraded by using the other semantics.

Usage of dynamic and .Net-Versions

By using dynamic in the code a reference to MSHTML (Html documents) can be avoided. It’s possible to descent into an object without knowing it by declaration. However, to supporting dynamic, a reference to Microsoft.CSharp becomes necessary. It contains the needed reflection utilities. At last… its usage ties it to C# 4.0. This can alternatively be done using reflection and so a downgrade to .NET 3.5 is also possible.

Assemblies

To make all used symbols available, the following COM and .Net references are needed:

  • SHDocVw / Microsoft Internet Controls: The ActiveX-Control
  • MSHTML / Microsoft HTML Object Library (with dynamic it is optional)
  • System.Windows.Form – only for the nice AxHost.

Thanks

Thanks to my company to have given me the task to elaborate this. Any improvements or comments are highly regarded and will follow in as updates. The hardest part was the wood cutting in the COM-Interface forest.

History

First upload.

16.8.2013: Text updates. 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here