In this post we are going to see a sample ViewModel Base for WPF, which follows the MVVM pattern, and also along with we are checking the DataError.
public class ViewModelBase : INotifyPropertyChanged
{
protected virtual void Set<T>(ref T member, T val,
[CallerMemberName] string propertyName = null)
{
if
(object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class ValidatableViewModelBase : ViewModelBase, INotifyDataErrorInfo
{
private Dictionary<string, List<string>> _errorslist =
new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string prop)
{
if
(_errorslist.ContainsKey(prop))
return _errorslist[prop];
else
return null;
}
public bool HasErrors
{
get
{ return _errorslist.Count > 0; }
}
protected override void Set<T>(ref T member, T val,
[CallerMemberName] string propertyName = null)
{
base.Set<T>(ref member, val, propertyName);
ValidateProperty(propertyName,
val);
}
private void ValidateProperty<T>(string propname, T value)
{
var
res = new List<ValidationResult>();
ValidationContext ctx = new ValidationContext(this);
ctx.MemberName = propname;
Validator.TryValidateProperty(value, ctx, res);
if (res.Any())
{
_errorslist[propname] =
res.Select(c => c.ErrorMessage).ToList();
}
else
{
_errorslist.Remove(propname);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propname));
}
}
Above code have two implementations which is normal model base another one is validate model base that is used for error notification in model.Then specify the Data Annotation above model
class EmployeeEdit:ValidatableViewModelBase
{
private Guid _id;
private string _firstname;
private string _lastname;
private string _phone;
private string _email;
public Guid Id
{
get
{ return _id; }
set
{ Set<Guid>(ref _id, value); }
}
[Required]
public string FirstName
{
get
{ return _firstname; }
set
{ Set<string>(ref _firstname, value); }
}
[Required]
public string LastName
{
get
{ return _lastname; }
set
{ Set<string>(ref _lastname, value); }
}
[Phone]
public string Phone
{
get
{ return _phone; }
set
{ Set<string>(ref _phone, value); }
}
[EmailAddress]
public string Email
{
get
{ return _email; }
set
{ Set<string>(ref _email, value); }
}
}
Then Bind the Employee Edit or add view model with the save command where we can check whether there is a error in model , based on that we are disabling the command button.
class AddEditEmployeeViewModel:ViewModelBase
{
private bool editMode;
public bool EditMode
{
get
{ return editMode; }
set
{
Set<bool>(ref editMode, value);
}
}
private EmployeeEdit _employee;
public EmployeeEdit Employee
{
get
{ return _employee; }
set
{
Set<EmployeeEdit>(ref _employee, value);
}
}
public RelayCommand SaveCommand;
public AddEditEmployeeViewModel()
{
SaveCommand = new RelayCommand(OnSave, CanSave);
}
private Employee _editEmployee;
public void BindEmployee(Employee employee)
{
_editEmployee = employee;
Employee = new EmployeeEdit();
Employee.ErrorsChanged += Employee_ErrorsChanged;
Employee.Id = employee.Id;
if
(EditMode)
{
Employee.FirstName = employee.FirstName;
Employee.LastName = employee.LastName;
Employee.Email = employee.Email;
Employee.Phone = employee.Phone;
}
}
private void Employee_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
private async void OnSave()
{
}
private bool CanSave()
{
return !Employee.HasErrors;
}
}
Create a Converter to make the visibility for Save Command button, Validates on NotifyDataErrors is the Property makes the notification on errors
class EdittoVisiblityConvertor : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool
val;
bool.TryParse(value.ToString(),
out val);
if
(val)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw
new NotImplementedException();
}
}
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sample.Employee"
xmlns:Data="clr-namespace:Zza.Data;assembly=Zza.Data"
x:Class="Sample.Employee.AddEditEmployeeView"
xmlns:conv="clr-namespace:Sample.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<conv:EdittoVisiblityConvertor x:Key="EditConvertor"
/>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding
RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid x:Name="grid1" DataContext="{Binding Employee}"
HorizontalAlignment="Left" Margin="30,30,0,0" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="17"/>
<ColumnDefinition
Width="93"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"
/>
</Grid.RowDefinitions>
<Label Content="First Name:" Grid.Column="0" HorizontalAlignment="Left" Margin="3,3,0,3" Grid.Row="0" VerticalAlignment="Center"/>
<TextBox x:Name="firstNameTextBox" Grid.Column="1"
HorizontalAlignment="Left" Height="24" Margin="3,4,0,4"
Grid.Row="0"
Text="{Binding FirstName, ValidatesOnNotifyDataErrors=True}"
VerticalAlignment="Center" Width="120" Grid.ColumnSpan="3"/>
<Label Content="Last Name:" Grid.Column="0" HorizontalAlignment="Left"
Margin="3,3,0,3" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox x:Name="lastNameTextBox" Grid.Column="1" HorizontalAlignment="Left"
Height="24" Margin="3,4,0,4" Grid.Row="1"
Text="{Binding LastName, ValidatesOnNotifyDataErrors=True}"
VerticalAlignment="Center" Width="120" Grid.ColumnSpan="3"/>
<Label Content="Phone:" Grid.Column="0" HorizontalAlignment="Left"
Margin="3,3,0,3" Grid.Row="2" VerticalAlignment="Center"/>
<TextBox x:Name="phoneTextBox" Grid.Column="1"
HorizontalAlignment="Left" Height="24" Margin="3,4,0,4" Grid.Row="2"
Text="{Binding Phone, ValidatesOnNotifyDataErrors=True}"
VerticalAlignment="Center" Width="120" Grid.ColumnSpan="3"/>
<Button Content="Save" Command="{Binding SaveCommand}"
Visibility="{Binding EditMode ,
Converter={StaticResource
EditConvertor}}"
Width="75"></Button>
</Grid>
</UserControl>
From this post you can learn how to create a ViewModelBase with Validation for Model Errors in WPF.