databind the Source property of the WebBrowser in WPF

C#WpfXamlData BindingBrowser

C# Problem Overview


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>

C# Solutions


Solution 1 - C#

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}"/>

Solution 2 - C#

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;
    }

Solution 3 - C#

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

Solution 4 - C#

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}"/>

Solution 5 - C#

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.

Solution 6 - C#

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" 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRussView Question on Stackoverflow
Solution 1 - C#Todd WhiteView Answer on Stackoverflow
Solution 2 - C#Samuel JackView Answer on Stackoverflow
Solution 3 - C#RoelFView Answer on Stackoverflow
Solution 4 - C#Olaf JappView Answer on Stackoverflow
Solution 5 - C#ΩmegaManView Answer on Stackoverflow
Solution 6 - C#WilliamView Answer on Stackoverflow