Introduction
In this tip, I will try to present a view model that can act as base for all our view models using WPF, or other technologies. The ideas behind can also be useful in case of entities, DTOs, or other type of object. The goal of this view model is to make our life easier with very common operations that we have to do when developing user interfaces.
Background
You have to be familiar with WPF and C# to understand the code and additionally, you have to know about MVVM pattern to understand the goal better.
Using the Code
The first thing that our ViewModel should do is let us work with WPF in implementing INotifyPropertyChanged
.
Additionally, many times, I face the situation in which I would like to know if the ViewModel
has been modified, and even binding it. An example could be to show/hide save or cancel buttons, mark something in red to let the user know that he has a pending change to save, ...
Of course, we always need a validation system, as easy as possible and the most close to .NET validation standard systems.
Another common point, but this is something that can be discussed, is to know when a ViewModel is busy, getting data, saving,,...
And the most important, all of these operations should work via Binding and should be extremely easy to use.
Architecture Overview
We will simply have a couple of interfaces (segregation), one for validating and the other to represent the view model itself. The result is that IViewModel
should implement INotifyPropertyChanged
, IValidatable
and IDataErrorInfo
. The ViewModel
will be an abstract
class that inherits from Validatable
, which implements nearly the whole validation system.
Modified View Model
Based on the property IsModified
and a bit special handling of the property changed event, we will be able to know automatically when a ViewModel
is modified.
protected virtual void RaisePropertyChanged([CallerMemberName] string property = "")
{
RaisePropertyChanged(false, property);
}
protected virtual void RaisePropertyChanged(bool causesModification,
[CallerMemberName] string property = "")
{
IsModified = IsModified || causesModification;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
I use the CallerMemberAttribute
to avoid specifying the property name (you will need .NET 4.5 if I´m not wrong). By default, a property changed does not modify the view model, you can switch this behaviour if you want and set the default to true
. Whenever we want to modify our view model, we should use it in this way.
RaisePropertyChanged(true);
Usually, when we fill our view models, at the end, we will set the IsModified
property to false
.
Validation
To have an extensible system, the validation will be done by DataAnnotations
attributes, that are used in many other .NET components.
To integrate it with WPF and validate the controls automatically, we will use IDataErrorInfo
, also common part of the .NET.
Sometimes, DataAnnotations
is not enough to validate, so we will add a custom way to validate complex stuff. To accomplish this, we will need an additional attribute, that we call ValidatableProperty
.
[System.AttributeUsage(AttributeTargets.Property)]
public class ValidatableProperty : Attribute
{
}
The base class to validate is the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ViewModel
{
public abstract class Validatable : IValidatable
{
[Browsable(false)]
public virtual bool IsValid
{
get { return string.IsNullOrWhiteSpace(Error); }
}
public virtual string Error
{
get
{
string retVal = string.Empty;
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo info in properties)
{
if (info.Name != "Error" && info.Name != "IsValid")
{
retVal = GetError(info.Name);
if (!string.IsNullOrWhiteSpace(retVal))
{
break;
}
}
}
return retVal;
}
}
public abstract string this[string columnName] { get; }
protected virtual string OnCustomPropertyValidation(string propertyName)
{
return string.Empty;
}
protected virtual string GetError(string propertyName)
{
string retVal = string.Empty;
PropertyInfo propertyInfo = this.GetType().GetProperty(propertyName);
var results = new List<ValidationResult>();
if (propertyInfo.GetCustomAttribute<ValidatableProperty>() != null)
{
var result = Validator.TryValidateProperty(
propertyInfo.GetValue(this, null),
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result)
{
retVal = results.First().ErrorMessage;
}
else
{
retVal = OnCustomPropertyValidation(propertyInfo.Name);
}
}
return retVal;
}
}
}
As you see, the method GetError
gets by reflection all ValidatableProperties
and validates the DataAnnotations
on it. Additionally, if there is no error, it will call the custom "OnCustomPropertyValidation
" that we can override in our view models.
Finally, we have to implement the indexer required by IDataErrorInfo
, that I leave abstract
to implement it in our base view model. In this way, we can trigger a property change of the property IsValid
, and for example, disable a save button.
public override string this[string columnName]
{
get
{
string retVal = GetError(columnName);
RaisePropertyChanged(false, "IsValid");
return retVal;
}
}
An example could be:
[ValidatableProperty]
[Required]
public string UserName
{
get
{
return _userName;
}
set
{
if (_userName != value)
{
_userName = value;
RaisePropertyChanged(true);
}
}
}
Loading Flag
To avoid having a big article, I will simply leave it here as a property you can set in your "async
" methods. It could also be split to separate base view models or interfaces, that contain methods for loading data, saving,.. but the goal here is to remark this flag just as an idea. How we usually use it, is by binding it to a busy control that becomes visible when the loading is set to true
. In this way, we don´t need to disable the whole UI, just those parts that are asynchronously getting data, or saving or so on.
A XAML example
<Window x:Class="MyWPFApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="boolToVisibility"/>
</Window.Resources>
<StackPanel Width="150">
<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Margin="5"/>
<Button Content="Save"
Visibility="{Binding IsModified, Converter={StaticResource boolToVisibility}}"
IsEnabled="{Binding IsValid}"/>
<Button Content="Cancel"
Visibility="{Binding IsModified, Converter={StaticResource boolToVisibility}}"/>
</StackPanel>
</Window>
Conclusions
This is, in my opinion, how a view model should look like and the basis it should contain. I also want to remark the validation system as it is extremely comfortable and powerful. We also have similar approaches for our entities and DTOs, but with some additional properties, like an Id
. Please let me know your opinion and don´t forget to download the full source (from the link at the top of the tip) with a little example to understand it better.