Monday, January 10, 2011

UpdateSourceTrigger = "Explicit" for MVVM Puritans

Recently, a new term has been evolved and established in WPF community. This is Pure MVVM. MVVM puritans is a belief system in MVVM. The believers of this faith have clean heart and no code behind :)

There are so many techniques developed by WPF community to adjust the believer of this faith in our society. One limitation that I have seen these people experiencing, is how to practice their religion with Explicit Binding. Just to recall, you can see this from msdn:

http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger.aspx

With explicit binding, this has always been expected to update the binding source using BindingExpression. You might be familiar with this code:

private void SubmitButtonClick(object sender, RoutedEventArgs e)
{
textBoxName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

But the thing is that this code is written in code behind which is not allowed under pure MVVM methodology. In this post, we are going to look at one technique which could be used to overcome such situation. We will be using CommandParameters for our DelegateCommand in the DataContext (ViewModels) of our View.

Let's assume a sample view, StudentView. This form has two fields. They are student's First and Last names. This view should also have a button to update the Student details to the view model. The organization has zero tolerance to code behind so it is part of design requirement to avoid it.



We would be using RelayCommand for this post. You can get the code of RelayCommand from Josh Smith's article:

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

The XAML definition of Student view is as follows:

<Window x:Class="WpfAppMVVM_Puritans_ExplicitBinding.StudentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAppMVVM_Puritans_ExplicitBinding"
Title="StudentView" Height="300" Width="555">
<Window.Resources>
<local:StudentConverter x:Key="studentParameterConverter" />
</Window.Resources>
<Window.DataContext>
<local:StudentViewModel />
</Window.DataContext>
<Grid>
<Label Content="First Name" Height="32" HorizontalAlignment="Left" Margin="13,11,0,0" Name="label1" VerticalAlignment="Top" Width="127" />
<TextBox Height="32" HorizontalAlignment="Left" Margin="148,10,0,0" Name="textBoxFirstName" VerticalAlignment="Top" Width="349" >
<TextBox.Text>
<Binding Path="FirstName" UpdateSourceTrigger="Explicit" />
</TextBox.Text>
</TextBox>
<Label Content="Last Name" Height="32" HorizontalAlignment="Left" Margin="13,49,0,0" Name="label2" VerticalAlignment="Top" Width="127" />
<TextBox Height="32" HorizontalAlignment="Left" Margin="148,48,0,0" Name="textBoxLastName" VerticalAlignment="Top" Width="349" >
<TextBox.Text>
<Binding Path="LastName" UpdateSourceTrigger="Explicit" />
</TextBox.Text>
</TextBox>
<Button Content="Save" Height="30" HorizontalAlignment="Left" Margin="148,86,0,0" Name="button1" VerticalAlignment="Top" Width="153"
Command="{Binding SaveCommand}" >
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource studentParameterConverter}">
<Binding ElementName="textBoxFirstName" Path="Text" />
<Binding ElementName="textBoxLastName" Path="Text" />
</MultiBinding>
</Button.CommandParameter>
</Button>
</Grid>
</Window>

As you can see the above view has two text boxes. The Text property of these text boxes are bound to FirstName and LastName property of the DataContext. Please notice UpdateSourceTrigger = Explicit with binding. An instance of StudentViewModel has been created and assigned as DataContext of this view. This also has a Update button. StudentViewModel is expected to have a command SaveCommand bound with the Command property of the button.

The most interesting thing and the reason of this post is the CommandParameter for the button. The CommandParameter has been bound with the Text properties of the two text boxes. We have to use MultiBinding for this. In order to get the value of this binding, we have to use converter. We have instantiate StudentConverter in the Resources section of the Window and used this as a StaticResource for this. Alternatively, We could have used StringFormat but WPF has this issue of StringFormat when bound to the property of System.Object type. StringFormat mainly works with string based properties for MultiBinding.

The definition of StudentViewModel is as follows:

class StudentViewModel : INotifyPropertyChanged
{
#region Private Fields
private string _firstName;
private string _lastName;
#endregion

#region Public Properties
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}

public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
#endregion

#region Commands
RelayCommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand((param) => this.Save(param),
param => this.CanSave);
}
return _saveCommand;
}
}

public void Save(object parameter)
{
string[] Names = ((string)parameter).Split(new char[] { ':' });
FirstName = Names[0];
LastName = Names[1];
}

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

#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implementation
}

As expected by StudentView. It has two properties FirstName and LastName. It also has a RelayCommand based property SaveCommand. The view model is implementing INotifyPropertyChanged to propagate change notifications to the view.

Please notice the definition of Execute method of SaveCommand (named Save). This is expecting FirstName and LastName joined together using colon ":". This code is just splitting the two from parameter (passed through CommandParameter) and assigning to FirstName and LastName properties. It is also possible that these values are not assigned directly but they are updated before being assigned. Whatever you would assign to the property bound to the view, the changes will be propagated to the view because of implementation of INotifyPropertyChanged.

As seen from above, the responsibility of CommandParameter's converter is to combine first and last name with a colon. The definition of converter is as follows:

public class StudentConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string firstName = (string)values[0];
string lastName = (string)values[1];

return string.Format("{0}:{1}", firstName, lastName);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Now run the application. Enter first and last names in the assigned textboxes and click Save button. Those values will be converted and passed to the SaveCommand in the view model. Here the view model's properties will be updated and changes are propagated to the View.


Download:

8 comments:

Anonymous said...

Hi,

nice approach. Thanks for sharing.
But I think IEditableObject is more MVVM-like.

Anonymous said...

Hi muhammad,

Very interesting post!.

I was looking for info about Explicit update.
Your post is very helpful.
But i cant understand why textboxes get updated OnPropertyChanged, if they are set up as Explicit,

MSDN States:

"When you set the UpdateSourceTrigger value to Explicit, the source value only changes when the application calls the UpdateSource method."

So, why is your example working this way?

Thank u very much in advance.

Anonymous said...

I just realized that i was wrong this is about updating your model from the view not on the contrary.

That makes me another question, how to handle explicit updates on the control from the source?

Muhammad Shujaat Siddiqi said...

Basically the updates from the source to the view are always explicit. It is done when you raise PropertyChanged for a property bound with your view.

If you want to throttle the updates from view model to the view then I have a post about using Reactive Extensions just for the same purpose.

I would be glad to help you further on this.

Anonymous said...

Thank you very much.

Anonymous said...

Hi Shujaat, I have a View ,this has a field say txtSearchName,this bind to my viewModels SearchName property .This view also have a listview to list the items by filtering through the field.When I enter text in the field this will list matching entries on the listview.I bind listview's SelectedItem to a property in my VM say theSelectedItem,I used UpdateSourceTrigger=explicit here to update the Property only when the user select an item from the listview and press enter . I Update it in my Code behind on listview's KeyDown handler .Now I realized that its against MVVM .But how to Update it using MVVM .
Thanks in advance

Muhammed Muzammil

Muhammad Shujaat Siddiqi said...

Hi Muhammad,
As I see, you have two string properties in your view model (searchExpression and selectedItem) and one collection to hold the items for the listview.


If I understood it right you want to filter only when user hits enter after entering the searchExpression. You seem to update listView when user selects an item from the listView.

I don't think you need any event here. You can just define a OneWayToSourceBinding from SelectedItem to bind to the property in the view model to get the item selected in the list.

For the search expression, you can bind Text property with the searchExpression property in the view model. In order to filter the collection, you can define input binding on the control with command you want to execute on the view model when user hits enter. You can filter when the command gets executed. You can refer to this post for reference for input binding:

http://www.shujaat.net/2011/03/wpf-binding-inputbindings-to-view-model.html

sahana tambi said...

For some reason the parameter object in the Save method is null in my implementation. I am just doing the same thing as the code snippet.