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="<" 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=">" 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 <a href="http://msdn.microsoft.com/en-au/library/system.windows.dependencyobject.setcurrentvalue%28v=vs.100%29.aspx">SetCurrentValue</a>()
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.