In this post I’ll be showing how you can use a DesignInstance to populate controls when you’re designing Silverlight or WPF applications in Expression Blend or Visual Studio.
The image below shows a user control in Expression Blend which allows users take part in a poll to find out what their favorite colors of the rainbow are.
As you can see it’s difficult if not impossible to imagine what we are actually designing here.
The user control doesn’t look any better in Visual Studio 2010.
Creating the ViewModel to bind the data context using DesignInstance
The code below shows the DesignTimeViewModel class to be used as the data context during design time. It inherits from the ViewModel class which will be used when the application is actually running.
public class DesignTimeViewModel : ViewModel
{
public DesignTimeViewModel()
{
Name = "Roy G Biv";
RainbowColors = new ObservableCollection<RainbowColor>()
{
new RainbowColor() {Description = "Red", Color = "#FFFF0000"},
new RainbowColor() {Description = "Orange", Color = "#FFFFA500"},
new RainbowColor() {Description = "Yellow", Color = "#FFFFFF00"},
new RainbowColor() {Description = "Green", Color = "#FF008000"},
new RainbowColor() {Description = "Blue", Color = "#FF0000FF"},
new RainbowColor() {Description = "Indigo", Color = "#FF4B0082"},
new RainbowColor() {Description = "Violet", Color = "#FFEE82EE"}
};
}
}
public class ViewModel : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public ObservableCollection<RainbowColor> RainbowColors { get; set; }
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
public class RainbowColor
{
public string Description { get; set; }
public string Color { get; set; }
}
Setting the DataContext to use DesignInstance in XAML
In order to bind the DesignTimeViewModel to the user control during design time we need to add a few more lines in the opening tag.
The lines that use a DesignInstance to bind data in the designer are highlighted in the XAML for the user control below.
As you can see in the code below we are throwing exceptions in the setters for the two values we want to add together.
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("FirstValue");
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
try
{
int.Parse(_secondValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("SecondValue");
}
}
Implementing the INotifyDataErrorInfo interface
As we only want the calculate button to be enabled when the user has entered valid numbers to add together, we need to keep track of any validation errors.
Similar to how we have done in other posts, we will use a class for storing validation errors instead of making it the responsibility of the ViewModel.
And that class is the EntityBase class, which I borrowed from Mike Taulty. Cheers Mike!
The code below shows how the EntityBase class implements the ErrorsChanged event, GetErrors method and the HasErrors property defined in the INotifyDataErrorInfo interface.
public class EntityBase : INotifyDataErrorInfo
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
readonly Dictionary<string, List<string>> _currentErrors;
public EntityBase()
{
_currentErrors = new Dictionary<string, List<string>>();
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return (_currentErrors.Values);
}
MakeOrCreatePropertyErrorList(propertyName);
return _currentErrors[propertyName];
}
public bool HasErrors
{
get
{
return (_currentErrors.Where(c => c.Value.Count > 0).Count() > 0);
}
}
void FireErrorsChanged(string property)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
}
}
public void ClearErrorFromProperty(string property)
{
MakeOrCreatePropertyErrorList(property);
_currentErrors[property].Clear();
FireErrorsChanged(property);
}
public void AddErrorForProperty(string property, string error)
{
MakeOrCreatePropertyErrorList(property);
_currentErrors[property].Add(error);
FireErrorsChanged(property);
}
void MakeOrCreatePropertyErrorList(string propertyName)
{
if (!_currentErrors.ContainsKey(propertyName))
{
_currentErrors[propertyName] = new List<string>();
}
}
}
The EnitityBase class can be unit tested like this
[TestFixture]
public class When_using_the_EntityBase
{
private EntityBase _entityBase;
[SetUp]
public void SetUp()
{
_entityBase = new EntityBase();
}
[Test]
public void HasErrors_should_return_true_when_errors_exist()
{
// Arrange
_entityBase.AddErrorForProperty("X", "X");
// Act
var result = _entityBase.HasErrors;
// Assert
result.ShouldBeTrue();
}
[Test]
public void HasErrors_should_return_true_when_no_errors_exist()
{
// Arrange
// collection will be empty at this point
// Act
var result = _entityBase.HasErrors;
// Assert
result.ShouldBeFalse();
}
[Test]
public void Should_be_able_to_clear_error_from_property()
{
// Arrange
_entityBase.AddErrorForProperty("X", "X");
// Act
_entityBase.ClearErrorFromProperty("X");
var result = _entityBase.GetErrors("X") as List<string>;
// Assert
result.Count().ShouldEqual(0);
}
[Test]
public void Should_be_able_to_get_error_for_property()
{
// Arrange
const string errorMessage = "ErrorMessage";
_entityBase.AddErrorForProperty("X", "ErrorMessage");
// Act
var result = _entityBase.GetErrors("X") as List<string>;
// Assert
result[0].ShouldEqual(errorMessage);
}
}
How the ViewModel validates user input
We need to validate the users input each time the value of a property changes.
In the code for the CalculatorValidator class, the Validate method could have returned a collection of error messages, but to keep this example simple we’ll just return the first validation error we encounter.
[Export(typeof(ICalculatorValidator))]
public class CalculatorValidator : ICalculatorValidator
{
[ImportMany]
public IEnumerable<ICalculatorValidationRule> CalculatorValidationRules { get; set; }
public ValidationResult Validate(string value)
{
List<ValidationResult> validationResults = new List<ValidationResult>();
foreach (var calculatorValidationRule in CalculatorValidationRules)
{
if (!calculatorValidationRule.IsValid(value))
{
return new ValidationResult()
{
IsValid = false,
ErrorMessage = calculatorValidationRule.ErrorMessage
};
}
}
return new ValidationResult() { IsValid = true };
}
}
public class ValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
}
Using the INotifyDataErrorInfo interface in the ViewModel
In the code for the ViewModel shown below take note of how:
the ViewModel inherits from the EntityBase class
the CheckIfNumberIsValid method is called whenever a property value changes
the INotifyDataErrorInfo HasErrors property is used to determine if the button should be enabled in the CalculatorViewModel_ErrorsChanged handler for the ErrorsChanged event. This is one of the advantages INotifyDataErrorInfo has over IDataErrorInfo.
[Export]
public class CalculatorViewModel : EntityBase, INotifyPropertyChanged
{
private string _firstValue;
private string _secondValue;
private string _result;
private readonly ICalculator _calculator;
private readonly RelayCommand _calculateCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> FirstValueErrors { get; set; }
private readonly ICalculatorValidator _calculatorValidator;
[ImportingConstructor]
public CalculatorViewModel(ICalculator calculator, ICalculatorValidator calculatorValidator)
{
_calculator = calculator;
_calculatorValidator = calculatorValidator;
_calculateCommand = new RelayCommand(Calculate) { IsEnabled = true };
ErrorsChanged += new EventHandler<DataErrorsChangedEventArgs>(CalculatorViewModel_ErrorsChanged);
_firstValue = "0";
_secondValue = "0";
}
void CalculatorViewModel_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
_calculateCommand.IsEnabled = HasErrors == false;
}
public void Calculate()
{
Result = _calculator.Add(Convert.ToInt32(FirstValue), Convert.ToInt32(SecondValue)).ToString();
}
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
CheckIfNumberIsValid("FirstValue", value);
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
CheckIfNumberIsValid("SecondValue", value);
}
}
public void CheckIfNumberIsValid(string propertyName, string value)
{
ClearErrorFromProperty(propertyName);
var validationResult = _calculatorValidator.Validate(value);
if (validationResult.IsValid == false)
{
AddErrorForProperty(propertyName, validationResult.ErrorMessage);
}
OnPropertyChanged(propertyName);
}
public string Result
{
get { return _result; }
private set
{
_result = value;
OnPropertyChanged("Result");
}
}
public RelayCommand CalculateCommand
{
get { return _calculateCommand; }
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
XAML changes for INotifyDataErrorInfo
The final change required to implement INotifyDataErrorInfo interface is to change the text box bindings and set ValidatesOnNotifyDataErrors=True
In this post we will be looking at how validation can be done by implementing the IDataErrorInfo interface for a calculator we have been building as part of the Silverlight refactoring series.
The IDataErrorInfo interface gives you the ability to do validation without throwing exceptions.
The full solution for this post can be downloaded here.
Pre IDataErrorInfo
As you can see in the code below we are throwing exceptions in the setters for the two values we want to add together.
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("FirstValue");
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
try
{
int.Parse(_secondValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("SecondValue");
}
}
Implementing the IDataErrorInfo interface
The IDataErrorInfo interface consists of two properties.
For the Error property we can just return null because we don’t want to return a single error message for the entire object.
public string Error
{
get { return null; }
}
For the property which returns an error for a text box we could do the validation like this
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstValue":
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
error = "That is not a number";
}
break;
case "SecondValue":
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
error = "That is not a number";
}
break;
}
return error;
}
}
But as we saw in a previous post about using ValidatesOnExceptions to do validation it’s much easier to write unit tests when there are separation of concerns and the ViewModel is not responsible for validation.
So this means we need to create a class for storing the validation error for each textbox
public class ValidationBase
{
public readonly Dictionary<string, string> Errors;
public ValidationBase()
{
Errors = new Dictionary<string, string>();
}
public void AddError(string propertyName, string message)
{
if (!Errors.ContainsKey(propertyName))
{
Errors[propertyName] = message;
}
}
public void RemoveErrors(string propertyName)
{
Errors.Remove(propertyName);
}
public string GetErrorMessageForProperty(string propertyName)
{
string message;
Errors.TryGetValue(propertyName, out message);
return message;
}
public bool HasErrors()
{
return Errors.Count != 0;
}
}
which is inherited by a CalculatorValidator class that returns a boolean value if the property value is not valid
[Export(typeof(ICalculatorValidator))]
public class CalculatorValidator : ValidationBase, ICalculatorValidator
{
[ImportMany]
public IEnumerable<ICalculatorValidationRule> CalculatorValidationRules { get; set; }
public bool IsPropertyValid(string propertyName, string value)
{
RemoveErrors(propertyName);
foreach (var calculatorValidationRule in CalculatorValidationRules)
{
if (!calculatorValidationRule.IsValid(value))
{
AddError(propertyName, calculatorValidationRule.ErrorMessage);
return false;
}
}
return true;
}
}
Now we can inject/import the CalculatorValidator into the ViewModel and use it to validate the users input.
As we only want the user to be able to click the calculate button when the form is valid we call we the CheckIfCalculteButtonShouldBeEnabled method when a text box value has changed.
[Export]
public class CalculatorViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _firstValue;
private string _secondValue;
private string _result;
private readonly ICalculator _calculator;
private readonly RelayCommand _calculateCommand;
public event PropertyChangedEventHandler PropertyChanged;
private readonly ICalculatorValidator _calculatorValidator;
[ImportingConstructor]
public CalculatorViewModel(ICalculator calculator, ICalculatorValidator calculatorValidator)
{
_calculator = calculator;
_calculatorValidator = calculatorValidator;
_calculateCommand = new RelayCommand(Calculate) { IsEnabled = true };
_firstValue = "0";
_secondValue = "0";
}
public void Calculate()
{
Result = _calculator.Add(Convert.ToInt32(FirstValue), Convert.ToInt32(SecondValue)).ToString();
}
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
OnPropertyChanged("FirstValue");
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
OnPropertyChanged("SecondValue");
}
}
public void CheckIfCalculteButtonShouldBeEnabled()
{
_calculateCommand.IsEnabled = _calculatorValidator.HasErrors() == false;
}
public string Result
{
get { return _result; }
private set
{
_result = value;
OnPropertyChanged("Result");
}
}
public RelayCommand CalculateCommand
{
get { return _calculateCommand; }
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstValue":
error = ValidateNumber("FirstValue", _firstValue);
break;
case "SecondValue":
error = ValidateNumber("SecondValue", _secondValue);
break;
}
CheckIfCalculteButtonShouldBeEnabled();
return error;
}
}
public string ValidateNumber(string propertyName, string value)
{
if (!_calculatorValidator.IsPropertyValid(propertyName, value))
{
return _calculatorValidator.GetErrorMessageForProperty(propertyName);
}
return null;
}
public string Error
{
get { return null; }
}
}
The code below shows how we can unit test the ViewModel which implements the IDataErrorInfo interface.
[TestFixture]
public class When_using_the_CalculatorViewModel
{
private Mock<ICalculator> _calculator;
private Mock<ICalculatorValidator> _calculatorValidator;
private CalculatorViewModel _calculatorViewModel;
[SetUp]
public void SetUp()
{
_calculator = new Mock<ICalculator>();
_calculatorValidator = new Mock<ICalculatorValidator>();
_calculatorViewModel = new CalculatorViewModel(_calculator.Object, _calculatorValidator.Object);
}
[Test]
public void Initial_value_of_first_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.FirstValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_second_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.SecondValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_calculate_button_is_enabled()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.CalculateCommand.IsEnabled;
// Assert
result.ShouldBeTrue();
}
[Test]
public void ValidateNumber_returns_null_if_value_is_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.IsPropertyValid("X","X")).Returns(true);
// Act
var result = _calculatorViewModel.ValidateNumber("X","X");
// Assert
result.ShouldBeNull();
}
[Test]
public void ValidateNumber_returns_error_message_if_value_is_not_valid()
{
// Arrange
const string errorMessage = "ErrorMessageText";
_calculatorValidator.Setup(c => c.IsPropertyValid("X", "X")).Returns(false);
_calculatorValidator.Setup(c => c.GetErrorMessageForProperty("X")).Returns(errorMessage);
// Act
var result = _calculatorViewModel.ValidateNumber("X", "X");
// Assert
result.ShouldEqual(errorMessage);
}
[Test]
public void Calculate_command_should_not_be_enabled_if_ViewModel_is_not_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.HasErrors()).Returns(true);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeFalse();
}
[Test]
public void Calculate_command_should_be_enabled_if_ViewModel_is_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.HasErrors()).Returns(false);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeTrue();
}
}
The final change we need to make is to change the text box binding to ValidatesOnDataErrors=True in the XAML file.