I finally took the time (again) to get into MVVM with WPF.
There are plenty of resources on the web on this topic so I won’t go into details there. But I’ll show some helpers I created which (in my opinion) help a lot with doing correct MVVM in WPF.
Generic Commands with RelayCommand
Guess you’ve already heard of ICommand and possible helpers to make it easier to use. One of those is the famous RelayCommand.
Here’s my implementation of it which I think covers all that is needed:
public class RelayCommand : ICommand { private readonly Action<object> _methodToExecute; readonly Func<object, bool> _canExecuteEvaluator; public RelayCommand(Action<object> methodToExecute) : this(methodToExecute, null) { } public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecuteEvaluator) { _methodToExecute = methodToExecute; _canExecuteEvaluator = canExecuteEvaluator; } [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecuteEvaluator == null || _canExecuteEvaluator.Invoke(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _methodToExecute.Invoke(parameter); } }
And an example usage on how to use it with a ToggleButton:
public class SomeViewModel { private MyModel Model { get; private set; } public ICommand ClickCommand { get; private set; } public SomeViewModel(MyModel model) { Model = model; ClickCommand = new RelayCommand(param => { var isChecked = (bool)param; if (isChecked) { Model.Start(); } else { Model.Stop(); } }); } }
<ToggleButton Command="{Binding Path=ClickCommand}" CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}">
Simplifying INotifyPropertyChanged with ObservableObject
Every .Net UI programmer knows the problems with INotifyPropertyChanged. There are some improvements (like with CallerMemberName) but it’s still a little bulky to use.
So I created an abstract base class which should help with it. It allows you to use INotifyPropertyChanged with your own backing field or without. It also allows calling additional OnPropertyChanged with complete type safety. It also only calls the OnPropertyChanged if the property value really changed.
public abstract class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private readonly Dictionary<string, object> _backingFieldValues = new Dictionary<string, object>(); /// <summary> /// Gets a property value from the internal backing field /// </summary> protected T GetProperty<T>([CallerMemberName] string propertyName = null) { object value; if (_backingFieldValues.TryGetValue(propertyName, out value)) return value == null ? default(T) : (T)value; return default(T); } /// <summary> /// Saves a property value to the internal backing field /// </summary> protected bool SetProperty<T>(T newValue, [CallerMemberName] string propertyName = null) { if (IsEqual(GetProperty<T>(propertyName), newValue)) return false; _backingFieldValues[propertyName] = newValue; OnPropertyChanged(propertyName); return true; } /// <summary> /// Sets a property value to the backing field /// </summary> protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null) { if (IsEqual(field, newValue)) return false; field = newValue; OnPropertyChanged(propertyName); return true; } protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(GetNameFromExpression(selectorExpression))); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } private bool IsEqual<T>(T field, T newValue) { return EqualityComparer<T>.Default.Equals(field, newValue); // Alternative: return Equals(field, newValue); } private string GetNameFromExpression<T>(Expression<Func<T>> selectorExpression) { var body = (MemberExpression)selectorExpression.Body; var propertyName = body.Member.Name; return propertyName; } }
Here are some examples on how to use it in all it’s variations:
public class MainViewModel : ObservableObject { // Property without specifying a backing field public string SomeText { get { return GetProperty<string>(); } set { SetProperty(value); } } // Property with specifying an own backing field private int _someNumber; public int SomeNumber { get { return _someNumber; } set { SetProperty(ref _someNumber, value); } } // Calling OnPropertyChanged on dependent property (typesafe) public string Firstname { get { return GetProperty<string>(); } set { if (SetProperty(value)) { OnPropertyChanged(() => FullName); } } } // Calling OnPropertyChanged on dependent property (not typesafe) public string Lastname { get { return GetProperty<string>(); } set { if (SetProperty(value)) { OnPropertyChanged("FullName"); } } } // The dependent property public string FullName { get { return String.Format("{0} {1}", Firstname, Lastname); } } }