Model-View-ViewModel (MVVM)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

MVVM

Siehe auch: MSDN – Implementing the MVVM Pattern

MVVM (Model View ViewModel) ist ein Programm-Entwurfsmuster zur Entkoppelung von Markup und Logik der UI. Es sieht die Rollentrennung von UI-Designern (VIEW) und Entwicklern (MODEL und VIEWMODEL) vor.

Vorteile: Durch diese Trennung können die Anwendungsschichten von verschiedenen Arbeitsgruppen entwickelt werden. Designer können einen Fokus auf das Benutzererlebnis und die UI legen (VIEW) und Entwickler unabhängig davon die UI- und Geschäftslogik schreibe (MODEL und VIEWMODEL). In der Regel sind keine aufwendigen UI-Tests nötig. Stattdessen genügen codebasierte Modul-Tests des ViewModel. Auch ist eine leichtere Austauschbarkeit der View z.B. nach WPF oder Windows Phone möglich. Unterstützung in WPF, Silverlight, Windows Phone und Windows Store Apps.

Nachteil: MVVM stellt einen Mehraufwand dar und ist für Anwendungen mit einfacher UI ein Overkill. Für größere Anwendungen kann das Design eines ausreichend allgemeinen ViewModel’s im Voraus schwierig sein. Außerdem führe eine schlecht verwaltete Datenbindung zu einem erheblichen Speicherbedarf der Anwendung.

Das MVVM-Entwurfsmuster besteht aus den folgenden Schichten und Kern-Technologien:

View : XAML, UX-Logik, Bindung zum ViewModel/Model und der Datenkonvertierung über IValueConverter

ViewModel : Stellt Bindbare Propertys und Commands bereit und enthält UI-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ICommand, INotifyPropertyChanged, NotifyCollectionChanged, INotifyCollectionChanged und INotifyDataErrorInfo.

Model : Entität, Geschäftsregeln und Validierungs-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ObservableCollection<T>, INotifyPropertyChanged, INotifyCollectionChanged, und INotifyDataErrorInfo.

[Data|View|Etc.]Services : Diverse Dienste die MVVM zuarbeiten und auf verschiedenen Plattformen unterschiedlich implementiert werden. Zum Beispiel der Zugriff auf unterschiedliche Datenbank-Hersteller oder die Implementierung der MessageBox in Apps, WPF oder auf einem Windows Phone.

DataService

Die DataService-Schicht übernimmt die Aufgabe der Datenbeschaffung (GetAllKunden()) bzw. der Datenaktualisierung (UpdateKunde(kunde)).

Die DataAccess-Schicht wird Ausgeführt im Model oder im ViewModel.

In einfachen Anwendungen schließt das Model diesen Code zur Unterstützung von Datenzugriff und Caching mit ein.

Stehen unterschiedliche Daten-Anbieter (DB, XML, WCF, etc.) zur Verfügung kann die Aufgaben der Datenbereitstellung aus dieser Schicht entkoppelt und in eine eigene ProviderService-Klasse implementiert werden. Diese ProviderService’s sollten ein zuvor erstelltes Interface (z.B. IProviderService) implementieren um Provider-Unabhängig im DataService Daten beschaffen zu können:

/* ZUM BEISPIEL
DataService ds;
ds = new DataService(new WCFProviderService()); // If Online
ds = new DataService(new DBProviderService()); // else Offline
*/

public interface IProviderService {
    object GetKunden();
}

public class DBProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden von der DB";
    }
}

public class WCFProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden vom HTTPS-WCF-Dienst";
    }
}

public class DataService {
    public DataService(IProviderService providerService) {
        ProviderService = providerService;
    }

    IProviderService ProviderService;

    public object GetKunden() {
        var kunden = ProviderService.GetKunden();
        // Aufbereiten der Kunden
        return kunden;
    }
}

Model

Das Model ist für die Verwaltung, Konsistenz und Gültigkeit der enthaltenen Entitäten verantwortlich.

Das Model besteht aus:

Gekapselter Entität und deren Daten

Geschäftsregeln

Validierungs-Logik

Eine Anwendungs-Domäne besteht aus mehreren Model’s der Geschäftsentität, aus Auflistungen von Geschäftsentitäten oder aus Kombinationen beider.

Bei einfacher DataService-Schicht kann diese Aufgabe direkt im Model implementiert werden.

Das Model ist eine nicht-visuelle Klasse die weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das Model besitzt keine Referenz oder Abhängigkeit zum ViewModel oder zur View.

Wenn Ihre Anwendungs-Domäne aus mehreren Model’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ModelBase zu implementieren und von dieser zu erben.

Das Model könnte auch in einem WCF-Dienst implementiert sein was das ViewModel anschließend konsumiert.

Für Benachrichtigung über Eigenschaftsänderungen implementiert das Model INotifyPropertyChanged bzw. INotifyCollectionChanged. Diese Benachrichtigungen benötigt ViewModel/View um ihre Daten/Anzeige zu aktualisieren. Diese Benachrichtigungsdienst kann auch vom ViewModel übernommen werden.

Auflistungen von Objekten die später an die View gebunden werden sollen, werden i.d.R. von ObservableCollection<T> erstellt da diese Klasse u.a. INotifyCollectionChanged implementiert.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im Model implementiert oder diese Aufgabe übernimmt auch das ViewModel.

ViewModel

Durch die Implementierung der Präsentations-Logik im ViewModel koordiniert diese die Interaktion zwischen den Model‘s und der View.

Das ViewModel besteht aus:

Durch-gereichte / Gekapselte Model‘s

Bindbare Eigenschaften für die View

Bindbare Kommandos für die View

Benachrichtigungen für die View

Gekapselte Präsentations-Logik

Ein ViewModel implementiert Model’s und wird für die Aufgabenstellung einer View entwickelt.

Während das Model System-unabhängig ist, tendiert das ViewModel zu einem konkreten System wie WPF, Silverlight, Windows Store App, etc.

Das ViewModel ist eine nicht-visuelle Klasse das weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das ViewModel besitzt keine Kenntnisse oder Referenz über die View, d.h. eine Abhängigkeit von der View darf nicht entstehen. Soweit nicht gekapselt könnte das ViewModel jedoch über das Model Kenntnisse über den DataService haben.

Das ViewModel arbeitet mit bereits initiierten Model’s oder stößt das Befüllen der Model’s mittels der DataAccess-Schicht an.

Wenn Ihre Anwendungs-Domäne aus mehreren ViewModel’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ViewModelBase zu implementieren und von diesen abzuleiten.

Test : Das ViewModel ist unabhängig von der View und dem Model testbar.

Die Daten die in der View präsentiert werden, können im ViewModel manipulieren und aufbereitet werden. Separate Konverter-Klassen (IValueConverter) werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Das ViewModel implementiert Befehle die als Eigenschaft vom Typ ICommand bereitgestellt werden. Diese wiederum können später in der View gebunden werden:

<Button Content="{Binding Email}"
        Command="{Binding SendenCommand}" />

Bindung : Das ViewModel kann die Eigenschaften des Model‘s direkt der View zugänglich machen. Alternativ kapselt das ViewModel die Eigenschaften des Models, bereitet die Daten auf und stellte diese der View dann zur Verfügung. Entscheidend ist die nicht redundante Änderungsbenachrichtigung der View die entweder vom Model oder vom ViewModel kommen. Hierzu implementieren Sie im ViewModel bzw. Model INotifyPropertyChanged, INotifyCollectionChanged oder ObservableCollection<T>.

Das ViewModel definiert logische Zustände und informiert über INotifyPropertyChanged und INotifyCollectionChanged die View über Änderungen die dem Benutzer visuell dargestellt werden können.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im ViewModel implementiert oder diese Aufgabe übernimmt auch das Model.

View

Die Datenbindungs-Infrastruktur ermöglicht eine lose Kopplung, bei der die View und die verknüpften Daten synchronisiert bleiben bzw. alle Benutzereingaben zu den entsprechenden Befehlen geleitet werden.

Datenbindung - Schema

Die View besteht aus:

UI-Elemente (XAML, DataTemplate’s, UserControl’s, Style‘s, Resource’s, etc.

Property-Bindungen auf Eigenschaften der ViewModel

Command-Bindungen auf Eigenschaften der ViewModel

UX (User Experience)-Logik

Die View geht eine 1:1 Beziehung mit dem entsprechenden ViewModel ein. Eine View kann sich aus mehreren Views (z.B. UserControl’s) zusammensetzen, die wiederum ihr eigenes ViewModel besitzen.

Die UI-Elemente binden ihre Eigenschaften mit einem Binding-Objekt an das dazugehörige ViewModel:

<... Text="{Binding Email}"
     Command="{Binding SpeichernCommand}" ... />

UX : In der dazugehörigen Code-Behind-Klasse liegt kein bis wenig Code der sich gekapselt nur auf die UX (User Experience)-Logik auswirkt.

Separate Konverter-Klassen die IValueConverter implementieren werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Bindung : Für umgehende Aktualisierung der UI-Elemente ist es wichtig dass die gebunden Quelleigenschaft des Model’s bzw. des ViewModel‘s die View korrekt benachrichtigt. Dies tun sie, wenn Sie die folgenden Interfaces implementiert haben: INotifyPropertyChanged, INotifyCollectionChanged, ObservableCollection<T>, oder INotifyDataErrorInfo.

Die konkrete Umsetzung der Bindung können Sie im Kapitel Datenbindung nachvollziehen.

WPF Eingabe-Validierung

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

 

Validierung basierend auf komplexer Regeln für Geschäftsdaten

WPF weist ein umfassendes Datenbindungssystem auf. Abgesehen davon, dass die Datenbindung wesentlich zur lockeren Kopplung der Benutzeroberfläche mit der Logik (MVVM) beiträgt, bietet es auch eine leistungsstarke und flexible Unterstützung für die Überprüfung der Daten. Dies gilt es zu beherrschen.

  • Wie arbeitet diese Überprüfung?
  • Welche Arten der Überprüfungen gibt es?
  • Wo liegen die Stärken und Schwächen?
  • Wie Können Überprüfungsfehler dem Benutzer angezeigt werden?

Vorbereitung

Kenntnisse in der WPF-Datenbindung, eine Einführung finden Sie z.B. hier Datenbindung in WPF.

Um eine Daten-Pipeline zwischen einer einzelnen Eigenschaft in einem Ziel Steuerelement und einer Datenquellen-Objekteigenschaft zu schaffen verwendet man ein Binding-Objekt.

Das eine Überprüfung eingeleitet werden kann, muss die Mode-Eigenschaft des Binding-Objektes auf TwoWay stehen.

Arten der Überprüft

Es gibt 3 miteinander kombinierbare Überprüfungs-Mechanismen, um zu ermitteln, ob über ein datengebundenes Steuerelement Benutzereingaben gültig sind.

1. Exception

Wenn beim Beschreiben der Quellobjekt-Eigenschaft eine Exception ausgelöst wird, so kann ein Überprüfungsfehler für das Binding-Objekt bestehen.

2. ValidationRule, BindingGroup

Dem Binding-Objekt können eine Sammlung von ValidationRule’s übergeben werden, die Entscheiden ob Überprüfungsfehler durch Benutzereingaben vorliegen. Die BindingGroup unterstützt diesen Xaml-Ansatz um die Möglichkeit Eigenschaftsübergreifend Überprüfungen vornehmen zu können.

3. IDataErrorInfo, INotifyDataErrorInfo

Durch Implementieren von IDataErrorInfo in das gebundene Datenquellen-Objekt Entscheidet dieses ob Überprüfungsfehler vorliegen. Das INotifyDataErrorInfo-Interface stand bis .NET 4.5 nur Silverlight zur Verfügung. Die Implementierungs-Logik unterscheidet sich zu IDataErrorInfo wird jedoch doch von Windows Store App, WPF und Silverlight unterstützt.

Wann wird überprüft

Die Überprüfung findet dann statt, wenn die Bindung die Daten in die Quellobjekt-Eigenschaft schreibt. Dieses Update-Verhalten wird im Binding-Objekt gesetzt:
Text=“{Binding Path=Aufgabe.Beschreibung, UpdateSourceTrigger=PropertyChanged}
Folgende Einstelllungen sind denkbar:

  • PropertyChanged – Bei jeder Änderung im Steuerelement. (Datum in TextBox, Sinnvoll?)
  • FocusChange – Das Update wird mit Wechsel des Steuerelement- Fokusses eingeleitet.
  • Explicit – Der Aufruf erfolgt manuell und wird z.B. in Kombination mit BindingGroup genutzt.

Arbeitsschritte der Überprüft

  1. Benutzereingabe führt zur Änderung einer UIElement-Eigenschaft
  2. Diese Daten werden ggf. in den Quellobjekt-Eigenschaftentyp umgewandelt
  3. Der Wert für die Quellobjekt-Eigenschaft wird gesetzt
  4. Binding.SourceUpdated-Ereignis wird ausgelöst
  5. Exception werden von der Bindung abgefangen, wenn diese vom Setter der Quellobjekt-Eigenschaft ausgelöst wurden
  6. OPTIONAL: IDataErrorInfo- / INotifyDataErrorInfo-Eigenschaften werden vom Quellobjekt abgerufen
  7. Der Benutzer erhält Überprüfungsfehlerangaben
  8. Validation.Error wird ausgelöst

X. OPTIONAL: ValidationRule’s auslösen, je nachdem wie ValidationStep-Eigenschaft gesetzt wurde, z.B. vor- / nach der Typkonvertierung, nach dem aktualisieren Quellobjekt-Eigenschaft oder wenn begonnen wird (s. IEditableObject) die Quellobjekt-Eigenschaft zu ändern.

Prüfung per Exception

Durch Festlegen der ValidatesOnExceptions-Eigenschaft im Binding-Objekt wird ein Überprüfungsfehler festgelegt, wenn im Setter der Quellobjekt-Eigenschaft eine Exception ausgelöst wird.

Text="{Binding Path=MitarbeiterNr, ValidatesOnExceptions=True}"

Auch würde ein Überprüfungsfehler vorliegen, wenn die Exception im Typkonvertierungs-Prozess ausgelöst wurde.

Prüfung per ValidationRule

Dem Binding-Objekt können mehrere ValidationRule-Objekte über die Eigenschaft ValidationRules hinzugefügt weden.

Um eigene ValidationRule-Objekte zu nutzen, leitet man diese Klassen von ValidationRule ab und überschreiben die Validate-Methode. Das Bindung-Objekt ruft diese Methode auf sobald sich die Daten im gebundenen Steuerelement ändern.

Wenn die Validate-Methode ein ungültiges ValidationResult-Objekt zurückgibt, wird für diese Bindung ein Überprüfungsfehler festgelegt.

public class RegexValidationRule : ValidationRule
{
    public string RegexMuster { get; set; }

    public override ValidationResult Validate(object value,
       CultureInfo cultureInfo)
    {
        if (RegexMuster == null)
            return ValidationResult.ValidResult;

        if (!(value is string))
            return new ValidationResult(
                isValid: false,
                errorContent: "Die Eingabe muss aus Text bestehen.");

        if (!Regex.IsMatch((string)value, RegexMuster))
            return new ValidationResult(
                isValid: false, errorContent: "Falsches Eingabeformat.");

        return ValidationResult.ValidResult;
    }
}

Eigenschaften, die in einer ValidationRule öffentlich verfügbar gemacht werden (s. z.B. RegexMuster), können von der XAML am Punkt der Verwendung festgelegt werden.
Das zurückgebende ValidationResult kann die Fehlermeldung die in der Benutzeroberfläche angezeigt werden soll.


  
    
      
        
      
    
  

ValidationRule-Objkete können jweils unterschiedliche Werte für die ValidationStep-Eigenschaft aufweisen.

Wenn eine ValidationRule einen Fehler zurückgibt werden die darauffolgenden Regeln nicht ausgewertet.

PRO

  • Implementierung kompliziertere Überprüfungs-Logik möglich

CONTRA

  •  Das ValidationRule-Objekt hat keinen Zugriff auf andere Eigenschaften im Quellobjekt und kann daher seinen Wert nicht mit diesen, für die Validierung in Beziehung setzen.
  • Die ValidationRule-Objkete werden im XAML, in einem erweiterten Binding-Element angeben (= Aufwand).

Prüfung per IDataErrorInfo

public interface IDataErrorInfo
{
    string Error { get; }
    string this[string columnName] { get; }
}

Durch Implementieren von  IDataErrorInfo in das Datenquell-Objekt und durch Festlegen der ValidatesOnDataErrors-Eigenschaft im Binding-Objekt, führt die Bindung Aufrufe der IDataErrorInfo-API aus.

Text="{Binding Path=Aufgabe.Beschreibung, ValidatesOnDataErrors=True}

Wenn von diesen IDataErrorInfo-Eigenschaften Zeichenfolgen zurückgegeben werden, die nicht Null oder nicht leer sind, wird ein Überprüfungsfehler festgelegt.

public class Kunde : IDataErrorInfo
{
    public string this[string propertyName]
    {
        get { return IsValid(propertyName); }
    }

    public string Email { get; set; }
    public bool IstInternetKunde { get; set; }

    private string IsValid(string propertyName)
    {
        switch (propertyName)
        {
            case "Email":
                if (!string.IsNullOrWhiteSpace(Email)
                    && IstInternetKunde)
                    return "Als Internet-Kunde wird eine"
                           + " Email-Adresse benötigt.";
                break;
        }
        return String.Empty;
    }
}

Der Indexer wird verwendet, um Fehler auf den einzelnen Eigenschaftenebenen anzugeben.

Die Error-Eigenschaft wird verwendet, um einen Fehler für das Objekt als Ganzes anzugeben, z. B. auf Datensatz-Ebene wie im DataGrid oder in einer BindingGroup.

PRO

  • Der Vorteil von IDataErrorInfo besteht darin, dass untereinander verbundene Eigenschaften einfacher verarbeitet werden können.

CONTRA

  •  Die Implementierung des Indexers führt in der Regel zu einer umfangreichen switch-case-Block.
  • Die Implementierung von IDataErrorInfo erst aufgerufen, nachdem der Eigenschaftswert bereits in dem Objekt festgelegt wurde. D.h. die anderen Objekte wurden bereits über Änderungen informiert (siehe INotifyPropertyChanged) obwohl die Daten im Anschluss als ungültig erklärt werden.
  • Nur ein Fehler pro Eigenschaft möglich.
  • Benutzen des Indexer der evtl. für eigen Logik benötigt wird.

Prüfung per INotifyDataErrorInfo

Das Konzept wurde in Silverlight eingeführt und mit der .NET 4.5 Version auch auf die anderen Bereiche wie WPF, Windows Store Apps ausgeweitet. Vom Grundgedanken her ist es dem IDataErrorInfo-Konzept ähnlich, das Interface schreibt jedoch folgende Member vor:

// Definiert Member, die von Datenentitätsklassen
// implementiert werden können, um benutzerdefinierten,
// synchronen und asynchronen Validierungssupport bereitzustellen.
public interface INotifyDataErrorInfo
{
    // Ruft einen Wert ab, der angibt, ob die Entität
    // über Validierungsfehler verfügt.
    bool HasErrors { get; }

    // Tritt ein, wenn sich die Validierungsfehler
    // für eine Eigenschaft oder für die ganze Entität geändert haben.
    event EventHandler ErrorsChanged;

    // Ruft die Validierungsfehler für eine
    // angegebene Eigenschaft oder für die ganze Entität ab.
    IEnumerable GetErrors(string propertyName);
}

Dieses Konzept arbeitet idealerweise mit der ValidationContext-Klasse sowie den Mebmern des Namensraumes System.ComponentModel.DataAnnotations zusammen. Hierbei werden die Validierungsregeln als Attribute der Eigenschaften im Quell-Objekt beschrieben.

Eine Implementierung würde Konzeptionell wie folge Dargestellt:

public class Kunde : INotifyDataErrorInfo
{
    #region Bindables

    public string Email { get; set; }

    public bool IstInternetKunde { get; set; }

    #endregion

    #region INotifyPropertyChanged Members

    public event EventHandler ErrorsChanged;

    public bool HasErrors { get {
      return _FehlerListe == null ? false : _FehlerListe.Count > 0; } }

    public IEnumerable GetErrors(string propertyName)
    {
        IList list;
        return _FehlerListe.TryGetValue(propertyName, out list)
          ? list : Enumerable.Empty();
    }

    #endregion

    #region Helpers for INotifyDataErrorInfo

    // Im Setter der jeweiligen Eigenschaft aufrufen
    // Ideale Kombiniert mit Techniken aus dem
    // Namespace System.ComponentModel.DataAnnotations
    private void Validate([CallerMemberName] string propertyName = null)
    {
        switch (propertyName)
        {
            case "Email":
                if (!string.IsNullOrWhiteSpace(Email)
                     && IstInternetKunde)
                    SetErrors(propertyName, new[]{"Als Internet-Kunde"
                     + " wird eine Email-Adresse benötigt."});
                else
                    ClearErrors(propertyName);
                break;
        }
    }

    private void SetErrors(string propertyName,
                  IEnumerable validationResults)
    {
        _FehlerListe[propertyName] = validationResults.ToList();
        OnErrorsChanged(propertyName);
    }

    private void ClearErrors(string propertyName)
    {
        _FehlerListe.Remove(propertyName);
        OnErrorsChanged(propertyName);
    }

    private void OnErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(
                         propertyName));
    }

    private readonly IDictionary<string, IList> _FehlerListe
             = new Dictionary<string, IList>();

    #endregion
}

Prüfen per BindingGroup

Eine BindingGroup ist speziell so konzipiert, dass die Überprüfung in einer Gruppe von Bindungen gleichzeitig ausgewertet werden können. Dies ist z.B. nützlich, wenn ein Benutzer-View erst auf OK-Klick validiert werden soll.
Um eine BindingGroup zu verwenden, benötigen Sie eine Gruppe von Steuerelementen mit normalen Bindungen, die ein Vorgängerelement gemeinsam verwenden.


    
        
            
                
            
        
    

Eine ValidationRule, die mit einer BindingGroup verknüpft ist, funktioniert etwas anders als eine ValidationRule, die mit einer einzigen Bindung verknüpft ist.

public class KundeValidationRule : ValidationRule
{
    public override ValidationResult Validate(
         object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = value as BindingGroup;
        if (bindingGroup == null)
            return new ValidationResult(false, "KundeValidationRule"
                             + " arbeitet nur mit einer BindingGroup");

        object item = bindingGroup.Items[0];
        if ('Prüfung für item' == false)
            return new ValidationResult(false, "Fehlermeldung ....");

        return ValidationResult.ValidResult;
    }
}

Anzeige von Überprüfungsfehlern

Die standardmäßige Kennzeichnung für einen Überprüfungsfehler ist ein roter Rahmen um das Steuerelement ohne einer Anzeige der verknüpfte Fehlermeldung.
Das Hinzufügen einer ToolTip-Anzeige, die den Fehlertext anzeigt, ist ganz einfach:


    
        
            
                
                    
                
            
        
    

Um die standardmäßige Überprüfungsfehleranzeige 100% den eigenen Vorstellung anzupassen, müssen Sie die angefügte Validation.ErrorTemplate-Eigenschaft festlegen:


    
        
            
                
                    
                    
                
            
        
        
    

Die Verknüpfung dieser Vorlage mit einem Steuerelement würde so aussehen:


    

Gut zu wissen

Wenn Sie in der View eine spezielle Aufgabe bei ungültige Daten ausführen müssen, z.B. Controls deaktivieren nutzen Sie das Validation.Error-Event.