Monday, August 30, 2010

WPF - Attached behaviors (Closing View from ViewModel)

There are certain times when View Model has to use the behavior of View. It has to call methods on it. You might say, we should use Binding with the properties in the view model but how to bind to cause a method to be called. As you might have thought that we should be keeping a reference of View in our ViewModel and should use it to call these methods on the view. But doing this we would be breaking the sanctity of MVVM.

The answer to the above problem is Attached Behaviors. With this technique, View Model still does not directly reference to View to call its methods. But it causes it to be executed using the same feature of Binding provided by WPF. The View causes a Data Trigger to be fired for this value change. We update the Value of one of the dependency properties in the View which cause this method to be executed from the ValueChanged event handler of PropertyMetaData for the property.

Simple Example:
Let's see this in action through an example. Let's we have a view model, named ..., and a view, named ... We have a button on the view, We want to close the view when the use clicks the button. Since we want to write the Click action on the View Model, we use RelayCommand and bind it to the button in the View using Binding. The RelayCommand is described by Josh Smith in this article about MVVM:

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030

Let's create a ViewModel which has a boolean dependency property, named IsClose. It also has a RelayCommand, named CloseCommand which has its canExecute (CanClose) and Execute (Close) actions. The Close action just sets the IsClose dependency property as true.

class AttachedBehaviorViewModel : DependencyObject
{
#region Dependency Properties
public static DependencyProperty IsCloseProperty =
DependencyProperty.Register("IsClose", typeof(bool),
typeof(AttachedBehaviorViewModel), new PropertyMetadata(false));

public bool IsClose
{
get { return (bool)GetValue(IsCloseProperty); }
set { SetValue(IsCloseProperty, value); }
}
#endregion Dependency Properties

#region Commands
RelayCommand _closeCommand;
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new RelayCommand(param => this.Close(),
param => this.CanClose);
}
return _closeCommand;
}
}

public void Close()
{
IsClose = true;

}

bool CanClose
{
get
{
return true;
}
}
#endregion Commands
}


We create an attached property IsCloseView in a static class WindowAttachedBehavior. It has a PropertyChangedCallBack. It just closes the DependencyObject (Window) causing it to be executed.

public static class WindowAttachedBehavior
{
public static DependencyProperty IsCloseViewProperty =
DependencyProperty.RegisterAttached("IsCloseView", typeof(bool),
typeof(WindowAttachedBehavior), new UIPropertyMetadata(false, OnIsCloseView));


public static bool GetIsCloseView(DependencyObject obj)
{
return (bool)obj.GetValue(IsCloseViewProperty);
}

public static void SetIsCloseView(DependencyObject obj, bool value)
{
obj.SetValue(IsCloseViewProperty, value);
}

public static void OnIsCloseView(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Window wnd = (Window)d;
if ((bool)e.NewValue)
{
wnd.Close();
}
}
}


Now we create a View, named AttachedBehaviorView which uses the view model described above (AttachedBehaviorViewModel) and the attached command. The most interesting thing is the DataTrigger where it is waiting for a property to be set in the View Model. As soon as this trigger fires, then the attached property IsCloseView is set, which causes our window to be closed.

<Window x:Class="WpfAttachedBehaviors.AttachedBehaviorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAttachedBehaviors"
DataContext="{DynamicResource ViewModel}"
Title="AttachedBehaviorView" Height="180" Width="300">
<Window.Resources>
<local:AttachedBehaviorViewModel x:Key="ViewModel" />
</Window.Resources>
<Window.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsClose}" Value="true">
<Setter Property="local:WindowAttachedBehavior.IsCloseView" Value="true" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<Grid>
<Button Margin="12,21,12,28" Name="button1" Content="Close"
Command="{Binding CloseCommand}">

</Button>
</Grid>
</Window>


4 comments:

Valo said...

Very nice, thank you!

Unknown said...

Thank you for this post.

What are your thoughts of implementing DependencyObject versus INotifyPropertyChanged? Once of the posts showed that DependencyObject provided better performance.

Unknown said...

Thank you for your excellent post.

We noticed that in the example DependencyObject implement versus INotifyPropertyChanged. Please comment on the advantages of implementing DependencyObject over the later.

Regards,
Michael Perini

Muhammad Shujaat Siddiqi said...

Hi Michael,

Generally you don't have any dependency properties in your view models. so you don't need to inherit from DependencyObject. Plus, you might not need to access dispatcher in view models.

There is an msdn article which compares binding to CLR objects (implementing and not implementing INotifyPropertyChanged) and DependencyObject.

http://msdn.microsoft.com/en-us/library/bb613546(v=vs.110).aspx