I’m working on a WPF MVVM application. I’m looking to databind a WebBrowser control to a view model which is in turn bound to a Tab. Following the advice in this article, I created a static helper class consisting of a static DependancyProperty:
public static class WebBrowserHelper
{
public static readonly DependencyProperty BodyProperty =
DependencyProperty.RegisterAttached("Body", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));
public static string GetBody(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(BodyProperty);
}
public static void SetBody(DependencyObject dependencyObject, string body)
{
dependencyObject.SetValue(BodyProperty, body);
}
private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string newValue = (string)e.NewValue;
var webBrowser = (WebBrowser)d;
webBrowser.NavigateToString(newValue);
}
}
XAML Binding WebBrowser to DependancyProperty:
<WebBrowser Grid.Column="2" HorizontalAlignment="Center" src:WebBrowserHelper.Body="{Binding HTMLBody}" VerticalAlignment="Center" Height="Auto" Width="Auto" />
ViewModel that bound to ItemsSource of Tab Control:
public class SomeVM : ViewModelBase, INotifyPropertyChanged
{
private string _htmlBody;
private SomeView _myView = new SomeView();
public SomeVM (string tabName)
{
TabName = tabName;
string contentsAsHTML = do_a_whole_bunch_of_stuff_to_generate_an_HTML_string();
HTMLBody = contentsAsHTML;
}
public string HTMLBody
{
get { return _htmlBody; }
set
{
if (_htmlBody != value)
{
_htmlBody = value;
RaisePropertyChanged("HTMLBody");
}
}
}
public SomeView View
{
get {return _myView;}
set { }
}
public string TabName { get; set; }
}
MainViewModel, Creating the Tab collection:
private ObservableCollection<SomeVM> _tabs;
public ObservableCollection<SomeVM> Tabs
{
get
{
if (_tabs== null)
{
_tabs= new ObservableCollection<SomeVM>();
_tabs.Add(new SomeVM("Tab 1"));
_tabs.Add(new SomeVM("Tab 2"));
_tabs.Add(new SomeVM("Tab 3"));
}
return _tabs;
}
}
MainWindow.xaml setting up the Tab Binding:
<TabControl ItemsSource="{Binding Tabs, Source={StaticResource vm}}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding View}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is that “OnBodyChanged” is fired multiple times on ever tab change. The HTML takes a few seconds to load, and I would rather it only loads when the property is actually modified in the viewmodel.
EDIT
Here’s the smallest sample project that recreates my problem.
Your problem is not relevant to attached properties or MVVM.
In fact, the real problem is that TabControl destroy and recreate its child every time you change the selected tab. That would explain why the handler is invoked more than once. The VisualTree only contains the selected Tab.
If you can try with another control, you will see there are no errors.
For solving this issue, I will redirect you to this post.