MVVM - Validierungen
In diesem Kapitel lernen wir Validierungen kennen. Wir werden uns auch eine saubere Möglichkeit ansehen, die Validierung mit den bereits unterstützten WPF-Bindungen durchzuführen, diese jedoch in MVVM-Komponenten einzubinden.
Validierung in MVVM
Wenn Ihre Anwendung Dateneingaben von Endbenutzern akzeptiert, müssen Sie diese Eingabe überprüfen.
Stellen Sie sicher, dass es Ihren allgemeinen Anforderungen entspricht.
WPF verfügt über einige großartige Builds und Funktionen im Bindungssystem zur Validierung von Eingaben, und Sie können all diese Funktionen weiterhin nutzen, wenn Sie MVVM ausführen.
Beachten Sie, dass die Logik, die Ihre Validierung unterstützt und definiert, welche Regeln für welche Eigenschaften vorhanden sind, Teil des Modells oder des ViewModel sein sollte, nicht der Ansicht selbst.
Sie können weiterhin alle Arten der Validierung verwenden, die von der WPF-Datenbindung unterstützt werden, einschließlich -
- Das Auslösen von Ausnahmen für eine Eigenschaft ist festgelegt.
- Implementierung der IDataErrorInfo-Schnittstelle.
- Implementierung von INotifyDataErrorInfo.
- Verwenden Sie WPF-Validierungsregeln.
Im Allgemeinen wird INotifyDataErrorInfo empfohlen und wurde in WPF .net 4.5 eingeführt. Es unterstützt die Abfrage des Objekts nach Fehlern im Zusammenhang mit Eigenschaften und behebt einige Mängel bei allen anderen Optionen. Insbesondere ermöglicht es eine asynchrone Validierung. Damit können Eigenschaften mehr als einem Fehler zugeordnet werden.
Validierung hinzufügen
Schauen wir uns ein Beispiel an, in dem wir unserer Eingabeansicht Validierungsunterstützung hinzufügen. In großen Anwendungen benötigen Sie dies wahrscheinlich an mehreren Stellen in Ihrer Anwendung. Manchmal gibt es in Ansichten, manchmal in ViewModels und manchmal in diesen Hilfsobjekten Wrapper um Modellobjekte.
Es empfiehlt sich, die Validierungsunterstützung in eine gemeinsame Basisklasse zu stellen, die Sie dann aus verschiedenen Szenarien erben können.
Die Basisklasse unterstützt INotifyDataErrorInfo, sodass diese Validierung ausgelöst wird, wenn sich die Eigenschaften ändern.
Erstellen Fügen Sie eine neue Klasse mit dem Namen ValidatableBindableBase hinzu. Da wir bereits eine Basisklasse für die Behandlung von Eigenschaftsänderungen haben, leiten wir die Basisklasse daraus ab und implementieren auch die INotifyDataErrorInfo-Schnittstelle.
Es folgt die Implementierung der ValidatableBindableBase-Klasse.
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace MVVMHierarchiesDemo {
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string propertyName) {
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
public bool HasErrors {
get { return _errors.Count > 0; }
}
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
base.SetProperty<T>(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
private void ValidateProperty<T>(string propertyName, T value) {
var results = new List<ValidationResult>();
//ValidationContext context = new ValidationContext(this);
//context.MemberName = propertyName;
//Validator.TryValidateProperty(value, context, results);
if (results.Any()) {
//_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
} else {
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
Fügen Sie nun AddEditCustomerView und AddEditCustomerViewModel in den entsprechenden Ordnern hinzu. Es folgt der Code von AddEditCustomerView.xaml.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
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:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Grid x:Name = "grid1"
HorizontalAlignment = "Left"
DataContext = "{Binding Customer}"
Margin = "10,10,0,0"
VerticalAlignment = "Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "Auto" />
<ColumnDefinition Width = "Auto" />
</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"
Grid.Row = "0"
VerticalAlignment = "Center" />
<TextBox x:Name = "firstNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "0"
Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Last Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "1"
VerticalAlignment = "Center" />
<TextBox x:Name = "lastNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "1"
Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Email:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "2"
VerticalAlignment = "Center" />
<TextBox x:Name = "emailTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "2"
Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Phone:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "3"
VerticalAlignment = "Center" />
<TextBox x:Name = "phoneTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "3"
Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
</Grid>
<Grid Grid.Row = "1">
<Button Content = "Save"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Add"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Cancel"
Command = "{Binding CancelCommand}"
HorizontalAlignment = "Left"
Margin = "150,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
</Grid>
</Grid>
</UserControl>
Es folgt die Implementierung von AddEditCustomerViewModel.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
public AddEditCustomerViewModel() {
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value);}
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value);}
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
Done();
}
private bool CanSave() {
return !Customer.HasErrors;
}
}
}
Es folgt die Implementierung der SimpleEditableCustomer-Klasse.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Model {
public class SimpleEditableCustomer : ValidatableBindableBase {
private Guid _id;
public Guid Id {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _firstName;
[Required]
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
[Required]
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private string _email;
[EmailAddress]
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _phone;
[Phone]
public string Phone {
get { return _phone; }
set { SetProperty(ref _phone, value); }
}
}
}
Wenn der obige Code kompiliert und ausgeführt wird, wird das folgende Fenster angezeigt.
Wenn Sie auf die Schaltfläche Kunde hinzufügen klicken, wird die folgende Ansicht angezeigt. Wenn der Benutzer ein Feld leer lässt, wird es hervorgehoben und die Schaltfläche Speichern deaktiviert.