91

Does anyone know how to databind the .Source property of the WebBrowser in WPF ( 3.5SP1 )? I have a listview that I want to have a small WebBrowser on the left, and content on the right, and to databind the source of each WebBrowser with the URI in each object bound to the list item.

This is what I have as a proof of concept so far, but the "<WebBrowser Source="{Binding Path=WebAddress}"" does not compile.

<DataTemplate x:Key="dealerLocatorLayout" DataType="DealerLocatorAddress">                
    <StackPanel Orientation="Horizontal">
         <!--Web Control Here-->
        <WebBrowser Source="{Binding Path=WebAddress}"
            ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
            ScrollViewer.VerticalScrollBarVisibility="Disabled" 
            Width="300"
            Height="200"
            />
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Label Content="{Binding Path=CompanyName}" FontWeight="Bold" Foreground="Blue" />
                <TextBox Text="{Binding Path=DisplayName}" FontWeight="Bold" />
            </StackPanel>
            <TextBox Text="{Binding Path=Street[0]}" />
            <TextBox Text="{Binding Path=Street[1]}" />
            <TextBox Text="{Binding Path=PhoneNumber}"/>
            <TextBox Text="{Binding Path=FaxNumber}"/>
            <TextBox Text="{Binding Path=Email}"/>
            <TextBox Text="{Binding Path=WebAddress}"/>
        </StackPanel>
    </StackPanel>
</DataTemplate>
H.B.
  • 166,899
  • 29
  • 327
  • 400
Russ
  • 12,312
  • 20
  • 59
  • 78
  • You may also use a special [separate proxy control](http://www.11011.net/wpf-binding-properties). It's applicable not only to the WebBrowser case, but to any such control. – Massimiliano Dec 16 '09 at 10:52

6 Answers6

164

The problem is that WebBrowser.Source is not a DependencyProperty. One workaround would be to use some AttachedProperty magic to enable this ability.

public static class WebBrowserUtility
{
    public static readonly DependencyProperty BindableSourceProperty =
        DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserUtility), new UIPropertyMetadata(null, BindableSourcePropertyChanged));

    public static string GetBindableSource(DependencyObject obj)
    {
        return (string) obj.GetValue(BindableSourceProperty);
    }

    public static void SetBindableSource(DependencyObject obj, string value)
    {
        obj.SetValue(BindableSourceProperty, value);
    }

    public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        WebBrowser browser = o as WebBrowser;
        if (browser != null)
        {
            string uri = e.NewValue as string;
            browser.Source = !String.IsNullOrEmpty(uri) ? new Uri(uri) : null;
        }
    }

}

Then in your xaml do:

<WebBrowser ns:WebBrowserUtility.BindableSource="{Binding WebAddress}"/>
Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
Todd White
  • 7,870
  • 2
  • 35
  • 34
  • 9
    Getting an Exception on that "new Uri(uri)" as the string is "". Perhaps it should be.... browser.Source = string.IsNullOrEmpty(uri) ? null : new Uri(uri); – midspace Oct 02 '11 at 23:45
  • 5
    Note that this is only a one way binding and the BindableSource property will not change when the web browser page changes. – Kurren Mar 16 '15 at 12:11
  • 1
    as a newbie this was a little hard for me to follow - writing something about having a private-public getter setter for "WebAddress" in the bound ViewModel with an update event for Property changed would have helped. this project has a similar example. https://github.com/thoemmi/WebBrowserHelper – pgee70 Oct 09 '15 at 10:40
  • thanks. I took this and modified it to implement a "Html" property, which calls NavigateToString under the hood. – Antony Scott Aug 24 '18 at 09:37
  • @pgee70 thanks, i followed your github example and works a treat – percentum Jul 30 '20 at 08:57
33

I've amended Todd's excellent answer a little to produce a version that copes with either strings or Uris from the Binding source:

public static class WebBrowserBehaviors
{
    public static readonly DependencyProperty BindableSourceProperty =
        DependencyProperty.RegisterAttached("BindableSource", typeof(object), typeof(WebBrowserBehaviors), new UIPropertyMetadata(null, BindableSourcePropertyChanged));

    public static object GetBindableSource(DependencyObject obj)
    {
        return (string)obj.GetValue(BindableSourceProperty);
    }

    public static void SetBindableSource(DependencyObject obj, object value)
    {
        obj.SetValue(BindableSourceProperty, value);
    }

    public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        WebBrowser browser = o as WebBrowser;
        if (browser == null) return;

        Uri uri = null;

        if (e.NewValue is string )
        {
            var uriString = e.NewValue as string;
            uri = string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString);
        }
        else if (e.NewValue is Uri)
        {
            uri = e.NewValue as Uri;
        }

        browser.Source = uri;
    }}
Waleed
  • 3,105
  • 2
  • 24
  • 31
Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
33

I wrote a wrapper usercontrol, which makes use of the DependencyProperties:

XAML:

<UserControl x:Class="HtmlBox">
    <WebBrowser x:Name="browser" />
</UserControl>

C#:

public static readonly DependencyProperty HtmlTextProperty = DependencyProperty.Register("HtmlText", typeof(string), typeof(HtmlBox));

public string HtmlText {
    get { return (string)GetValue(HtmlTextProperty); }
    set { SetValue(HtmlTextProperty, value); }
}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
    base.OnPropertyChanged(e);
    if (e.Property == HtmlTextProperty) {
        DoBrowse();
    }
}
 private void DoBrowse() {
    if (!string.IsNullOrEmpty(HtmlText)) {
        browser.NavigateToString(HtmlText);
    }
}

and use it like so:

<Controls:HtmlBox HtmlText="{Binding MyHtml}"  />

The only trouble with this one is that the WebBrowser control is not "pure" wpf... it is actually just a wrapper for a win32 component. This means that the control won't respect the z-index, and will always overlay other element (eg: in a scrollviewer this might cause some trouble) more info about these win32-wpf issues on MSDN

RoelF
  • 7,483
  • 5
  • 44
  • 67
  • This is exactly what I needed as I want to display my own html. Neat, simple, and I almost understand what its doing (-: – Murph Jun 16 '11 at 18:58
3

Cool idea Todd.

I have done similar with the RichTextBox.Selection.Text in Silverlight 4 now. Thanks for your post. Works fine.

public class RichTextBoxHelper
{
    public static readonly DependencyProperty BindableSelectionTextProperty =
       DependencyProperty.RegisterAttached("BindableSelectionText", typeof(string), 
       typeof(RichTextBoxHelper), new PropertyMetadata(null, BindableSelectionTextPropertyChanged));

    public static string GetBindableSelectionText(DependencyObject obj)
    {
        return (string)obj.GetValue(BindableSelectionTextProperty);
    }

    public static void SetBindableSelectionText(DependencyObject obj, string value)
    {
        obj.SetValue(BindableSelectionTextProperty, value);
    }

    public static void BindableSelectionTextPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        RichTextBox rtb = o as RichTextBox;
        if (rtb != null)
        {
            string text = e.NewValue as string;
            if (text != null)
                rtb.Selection.Text = text;
        }
    }
}    

Here is the Xaml-Code.

<RichTextBox IsReadOnly='False' TextWrapping='Wrap' utilities:RichTextBoxHelper.BindableSelectionText="{Binding Content}"/>
Olaf Japp
  • 480
  • 5
  • 19
0

This is a refinement to Todd's and Samuel's answer to take advantage of some basic logic premises as well as use the null coalescing operator..

public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    WebBrowser browser = o as WebBrowser;

    if ((browser != null) && (e.NewValue != null))
        browser.Source = e.NewValue as Uri ?? new Uri((string)e.NewValue);

}
  1. If the browser is null or the location is null, we cannot use or navigate to a null page.
  2. When the items in #1 are not null then when assigning, if the new value is a URI then use it. If not and the URI is null, then coalesce for it has to be a string which can be put into a URI; since #1 enforces that the string cannot be null.
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
-3

You need to declare it at the first few lines of the xaml file which is pointing to the class file

xmlns:reportViewer="clr-namespace:CoMS.Modules.Report" 
mrcktz
  • 251
  • 3
  • 16