In this post we are going to discuss how we can avoid the use of magic strings and still be able to use INotifyPropertyChanged. For those who needs some glimpse of the problem, INotifyPropertyChanged based properties appear something like this in general.
private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged("FirstName"); } }And that is what we want to avoid. We don’t want an OnPropertyChanged with a magic string.
Let’s create a simple view with a simple TextBox for entering user information (First Name). In order to prove that the property change notifications are working, we add one TextBlock. This TextBlock is bound to the same property. This is a OneWay binding which is one-way from Source to Target property. We have set the UpdateSourceTrigger on TextBox’s binding to PropertyChanged so that we don’t have to lose focus from the TextBox in order to trigger the copying of text to the view model’s property.
<Window x:Class="WpfApp_INotifyWithoutMagicString.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp_INotifyWithoutMagicString" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Grid> <Label Content="First Name" Height="28" HorizontalAlignment="Left" Margin="22,54,0,0" Name="label1" VerticalAlignment="Top" Width="80" /> <TextBox Height="28" HorizontalAlignment="Left" Margin="108,54,0,0" Name="textBox1" VerticalAlignment="Top" Width="356" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Height="121" HorizontalAlignment="Left" Margin="108,101,0,0" Name="textBlock1" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Background ="Thistle" VerticalAlignment="Top" Width="356" /> </Grid> </Window>We are instantiating MainWindowViewModel and using it as DataContext of the Window. The view model is assumed to have a property, named FirstName. You can find the definition of MainWindowViewModel as follows:
&class MainWindowViewModel : BaseViewModel<MainWindowViewModel> { private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(viewModel => viewModel.FirstName); } } }As expected, the above view model has a public property FirstName. The difference is how we have called OnPropertyChanged. As we mentioned in the start of this post, there is no magic string. The definition of OnPropertyChanged is inherited from ViewModelBase. But one thing is apparent, the definition of the method should be dealing with the handling of the lambda expression provided as method argument.
class MainWindowViewModel : ViewModelBase<MainWindowViewModel> { private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(viewModel => viewModel.FirstName); } } }The definition of ViewModelBase is as follows:
class ViewModelBase<T> : INotifyPropertyChanged where T : class { #region Implementation INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged = delegate { }; protected void OnPropertyChanged(Expression<Func<T, object>> expression) { if (expression == null || !(expression.Body is MemberExpression)) { return; } PropertyChanged(this, new PropertyChangedEventArgs(((MemberExpression)expression.Body).Member.Name)); } #endregion Implementation INotifyPropertyChanged }ViewModelBase is implemented as generic type. This is to allow the user to avoid casting. The other implementation might have been as follows:
class ViewModelBase : INotifyPropertyChanged { #region Implementation INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged = delegate { }; protected void OnPropertyChanged(Expression<Func<object, object>> expression) { if (expression == null || !(expression.Body is MemberExpression)) { return; } PropertyChanged(this, new PropertyChangedEventArgs(((MemberExpression)expression.Body).Member.Name)); } #endregion Implementation INotifyPropertyChanged }But since this is based on object type of argument for lambda expression. In order to access the FirstName property from the argument, the developer need to typecast. Wrong typecasting might result in runtime exceptions if the object being type casted doesn’t have the property with same name.
public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(viewModel => ((MainWindowViewModel)viewModel).FirstName); } }The developer might be just using the code similar to the following code. But this must be remembered that we are not passing the current instance of View Model. We are just passing the lambda expression as argument to OnPropertyChanged. Its evaluation would result in raising PropertyChanged event for the same property.
public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(viewModel => FirstName); } }Which leads us to third implementation of ViewModelBase without even specifying the type of view model as the argument for lambda. I think this would be the simplest and easiest implementation of the Base View Model.
class ViewModelBase : INotifyPropertyChanged { #region Implementation INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged = delegate { }; protected void OnPropertyChanged(Expression<Func<object>> expression) { if (expression == null || !(expression.Body is MemberExpression)) { return; } PropertyChanged(this, new PropertyChangedEventArgs(((MemberExpression)expression.Body).Member.Name)); } #endregion Implementation INotifyPropertyChanged }If we implement our ViewModel as above then it might result in simplest OnPropertyChanged as follows:
public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(() => FirstName); } }No matter what implementation of ViewModelBase you are using, when you would run the application and enter text in the TextBox, it would update the FirstName property in the DataContext (MainWindowViewModel’s instance). This is proved through the same data being displayed in the TextBlock below.
No comments:
Post a Comment