Manchmal ist die Wahl wirklich die Qual. In Silverlight 4 gibt es mindestens vier mir bekannte Möglichkeiten in Dialogen Eingaben des Benutzers zu prüfen.
- per Exception (von Anbegin)
- per Eigenschaftsattribut (mit RIA Services eingeführt)
- IDataErrorInfo
- INotifyDataErrorInfo (neu in SL4)
Mit dem letzteren werden wir uns hier nun beschäftigen. Wenn eine Datenklasse das Interface INotifyDataerrorInfo implementiert kann von andere Stelle im Code ein Fehler Event aufgerufen werden. Im Unterschied zu IDataErrorInfo kann dann ein Property auch mehrere Fehler besitzen (ob es sich darüber freuen wird?) was den Code nicht einfacher macht.
Darüber hinaus kann man direkt das ErrorsChanged Event aufrufen und so auch asynchron validieren.
Mein folgendes Silverlight Beispiel ist Teil einer kleinen APP die die UST ID auf Gültigkeit beim zuständigen Finanzamt prüft. Das heißt es werden Daten an einen Webservice geschickt und dieser antwortet für jedes Feld ob dies gültig oder ungültig ist. Der Benutzer muss dann die Eingabe korrigieren oder seinem Geschäftspartner mitteilen das eine UST freie Lieferung nicht möglich ist.
Dazu erstellte ich mir einen Datenklasse die später dann auch an das UI per Binding gebunden wird. Die Attribute dienen ebenfalls der Eingabe validierung bzw dem Layout des Dialoges. Dort kommen Label Steuerelemente zum Einsatz die dann z.B. Display Name verwenden.
Public Class ustidFirma
Implements INotifyDataErrorInfo
<Required()>
<Display(Name:="Ustid")>
Public Property ustid As String
<Required()>
<Display(Name:="Firma")>
Public Property firma As String
Wesentlicher ist aber die Logik. Eine exemplarische Implementierung nachdem das Interface InotifyDataErrorInfo implementiert wird. Es werden zwei Methoden benötigt. Die Eigenschaft HasErrors definiert ob grundlegend Fehler vorhanden sind. Die Funktion GetErrors gibt eine Liste der Fehler zurück die für einen bestimmte Eigenschaft, z.B. ustid, vorliegen. Das ErrorsChanged Event dient dazu die UI über Änderungen zu informieren.
Imports System.ComponentModel
Public Class test1
Implements INotifyDataErrorInfo
Public Event ErrorsChanged(ByVal sender As Object,
ByVal e As System.ComponentModel.DataErrorsChangedEventArgs)
Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged
Public Function GetErrors(ByVal propertyName As String)
As System.Collections.IEnumerable
Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
End Function
Public ReadOnly Property HasErrors As Boolean
Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
Get
End Get
End Property
End Class
Zurück zu meinem Beispiel UST Prüfung. Um die Fehler zu verwalten erstelle ich mir eine Liste für die Attribute und deren Fehler.
Private errors As New Dictionary(Of String, List(Of String))
HasErrors liefert dann zurück ob in der Liste was drin steht.
Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
Get
Return errors.Count > 0
End Get
End Property
Die Funktion GetErrors, liefert die Fehlerliste für das ausgewählte Property.
Public Function GetErrors(ByVal propertyName As String)
As System.Collections.IEnumerable
Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
If errors.ContainsKey(propertyName) Then
Return errors(propertyName)
Else
Return Nothing
End If
End Function
Eine manuell von mir erstellte Hilfsfunktion RaisErrorsChanged füllt die Fehlerliste und wirft dann das Event um im Userinterface die Bindung zu aktualisieren.
Public Sub RaiseErrorsChanged(ByVal propertyName As String, ByVal Fehler As String)
If Not errors.ContainsKey(propertyName) Then errors(propertyName) = New List(Of String)()
errors(propertyName).Add(Fehler)
RaiseEvent ErrorsChanged(Me,
New DataErrorsChangedEventArgs(propertyName))
End Sub
Diese Sub wird von mir sozusagen per Hand aufgerufen. Die dazu gehörige Logik blende ich hier aus. Stellen Sie sich einfach einen Button vor der folgenden Code enthält.
Dim ui As ustidFirma = LayoutRoot.DataContext
ui.RaiseErrorsChanged("ustid", "USTID falsch")
Da es sich um eine gebundenes Objekt handelt kann ich jederzeit dieses Objekt zurückholen und darauf dann die Methode RaiseErrorsChanged aufrufen. Im XAML Code sind dafür mehrere Dinge nötig. Der Namensraum (hier willkürlich local genannt) um auf die Projektklassen zugreifen zu können. Die deklarative Instanz mit dem frei gewählten Namen uf1. Das Grid mit dem Namen Layoutroot ( Silverligth Default) bekommt dann dieses Objekt uf1 per Binding in seinem DataContext zugewiesen. Beachten Sie das dies notwendig ist, um im vorigen Codeblock damit agieren zu können und das Label und die Textbox zu Binden. Dabei ist noch wesentlich das in der Bindung der Text Eigenschaft der Textbox das Binding Attribut ValidatesOnNotifyDataErrors gesetzt wird. Der folgende XAML Code ist stark gekürzt.
xmlns:local="clr-namespace:KoelnSL"
>
<UserControl.Resources>
<local:ustidFirma x:Key="uf1"/>
.....
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource uf1}}">
..... <sdk:Label Target="{Binding ElementName=txtmyID}"/>
<sdk:Label Target="{Binding ElementName=txtID}"/>
..
<TextBox x:Name="txtmyID" />
<TextBox x:Name="txtID"
Text="{Binding ustid,Mode=TwoWay,ValidatesOnExceptions=true,
NotifyOnValidationError=true,ValidatesOnNotifyDataErrors=True}" />
.. </Grid>
Der Vollständigkeit halber noch der fertige Dialog.