Databinding in WPF ist ein sehr weites Feld. Nicht zuletzt aufgrund der Vielseitigkeit und Mächtigkeit spielt Databinding in WPF eine sehr wichtige Rolle. Einige wichtige Merkmale sind:
- Bindings können sowohl in XAML als auch im Code erzeugt werden.
- Konvertierung von Daten. Über sog. Value Converter ist es möglich, die Daten die von der Quelle kommen anzupassen bzw. zu verändern.
- Data Templates – mit Data Templates kann man die Visualisierung der Daten festlegen.
- Data Validation – wie unterstützt mich WPF bei der Validierung der vom Benutzer eingegebenen Daten?
Um Data Validation soll es in den nächsten Posts gehen. Ich kann leider keine Weisheiten zu dem Thema bieten, sondern möchte vielmehr meinen Lernprozess (mit)teilen. In mehreren Postings nähere ich mich Schritt für Schritt dem Thema. Danach wissen wir hoffentlich alle ein bisschen mehr über Databinding und Data Validation mit WPF.
Um erst mal die Grundlagen zu verstehen, fange ich ganz klein an. So klein, dass es mir schon fast zu klein vorkommt… aber nur fast. So sieht das Fenster der Testanwendung aus:
Das zugehörige XAML ist auch ganz einfach:
<Window x:Class="DataValidationWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataValidationWPF"
Title="Data validation with WPF"
Height="130" Width="300">
<Window.Resources>
<local:CompactDisc x:Key="cd" />
</Window.Resources>
<StackPanel>
<TextBlock Text="Please enter a value between 1980 and 2009"
Margin="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Year:" Margin="10" />
<TextBox ToolTip="Please enter year of release"
Width="60"
Margin="10"
Text="{Binding Source={StaticResource cd},
Path=Year,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</StackPanel>
</Window>
Als Datenklasse muss wieder die CompactDisc herhalten. Diesmal total abgespeckt in ihrer Minimalausführung:
public class CompactDisc
{
private int _year;
public int Year
{
get
{
Debug.WriteLine("Get year: " + _year);
return _year;
}
set
{
_year = value;
Debug.WriteLine("Set year to: " + _year);
}
}
}
Es wird also die “Year” Eigenschaft einer Instanz der CompactDisc Klasse an die “Text” Eigenschaft einer TextBox im UI gebunden. Als Mode für die Bindung habe ich “TwoWay” angegeben. D.h. der Datenaustauch erfolgt in beide Richtungen. Nach dem Starten der Applikation wird direkt der Wert ‘0’ in der TextBox angezeigt. 0 ist der Default Wert für den Typ int. Das TwoWay Binding funktioniert also. Selbst bei diesem einfachen Beispiel passiert schon einiges unter der Databinding Haube. Damit überhaupt ein Integer Wert in einer TextBox angezeigt werden kann, durchläuft dieser eine implizite Konvertierung zu einem String.
Zu Diagnosezwecken wurden im Setter und Getter der Eigenschaft jeweils eine Debugausgabe eingebaut. Damit kann man dann im Output Fenster des Visual Studios sehen, ob überhaupt etwas geändert wurde. Um auch wirklich alle Änderungen, die im UI in der TextBox gemacht werden mitzubekommen, habe ich die Eigenschaft “UpdateSourceTrigger” auf den Wert “PropertyChanged” gesetzt. Das bewirkt, dass die Quelle des Bindings (die Year Eigenschaft) aktualisiert wird, sobald in der TextBox editiert wird. Los geht’s. Applikation im Debug Modus aus dem Visual Studio starten und die Debug Ausgaben im Auge behalten.
Sobald die ‘0’ aus der TextBox gelöscht wird, taucht auch schon die erste Fehlermeldung im Output Fenster auf:
A first chance exception of type ‘System.FormatException’ occurred in mscorlib.dll
A first chance exception of type ‘System.Reflection.TargetInvocationException’ occurred in mscorlib.dll
A first chance exception of type ‘System.FormatException’ occurred in mscorlib.dll
System.Windows.Data Error: 7 : ConvertBack cannot convert value ” (type ‘String’). BindingExpression:Path=Year; DataItem=’CompactDisc’ (HashCode=59835590); target element is ‘TextBox’ (Name=”); target property is ‘Text’ (type ‘String’) FormatException:’System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at MS.Internal.Data.SystemConvertConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)’
The thread 0xf44 has exited with code 0 (0×0).
Bei der impliziten Konvertierung gibt es eine FormatException. Die Anwendung läuft trotzdem ohne Probleme weiter. Offensichtlich wird die Exception vom Databinding System geschluckt. Man sieht auch, dass der Setter der Year Eigenschaft nicht aufgerufen wird. Bisher bekommen wir also in unserem Code nichts mit und haben dadurch auch nicht die Möglichkeit dem Benutzer einen entsprechenden Hinweis zu geben (z.B. eine Meldung “Year must not be empty”). Aber hier soll uns ja Data Validation weiterhelfen. Wird eine gültige Zahl in die TextBox eingegeben läuft alles nach Plan, der Setter wird aufgerufen und das Datenobjekt ist auf Stand.
So, das war’s für den ersten Teil. Man sieht, dass selbst bei so einfachen Anwendungen mit Databinding eine Menge passiert. Ich bin gespannt, was WPF bzgl. Validierung für uns Entwickler bereithält.