Sie sind auf Seite 1von 54

Dynamische Verbindungen

mit WPF Data Binding


Jörg Neumann
Jörg Neumann
 Principal Consultant bei Acando
 Associate bei Thinktecture
 MVP im Bereich „Client App Dev“
 Beratung, Schulung, Coaching
 Buchautor, Speaker
 Mail
– Joerg.Neumann@Acando.de
– Joerg.Neumann@thinktecture.com
 Blog
– www.HeadWriteLine.BlogSpot.com
Datenbindung
 Item Binding
– Eigenschaften binden
 List Binding
– Listen binden
 Möglichkeiten
– Erzeugen von UI-Elementen aus Daten
– Erscheinungsbild und Verhalten können an Daten
oder Eigenschaften gebunden werden
Datenquellen
 Objekte
 Datenbank-Inhalte
 XML-Daten
 Datenquellen, die per URI erreichbar sind
 Asynchrone Bindung
Die Binding-Klasse
 System.Windows.Data.Binding
– Für Item Binding
– Beschreibt die Bindung zur Datenquelle
– Verbindung zwischen zwei Eigenschaften
– Verfügt über eine Markup-Extension
 {x:Binding … }
 Bindung programmatisch herstellen
– ContentControl.SetBinding(property, binding)
Beispiel
XAML:
<StackPanel>
<TextBox Name="textBox1" />
<TextBlock Name="textBlock1"
Text="{Binding ElementName=textBox1, Path=Text}" />

</StackPanel>

Code:
Dim binding As Binding = New Binding
binding.Source = Me.textBox1
binding.Path = New PropertyPath("Text")
Me.textBlock1.SetBinding(TextBlock.TextProperty, binding)
Binding per Code erstellen
 Binding-Klasse
– ElementName
 Elementname
– Source
 Datenquelle
– Path
 Pfad (Propertyname, XPath, …)
– TargetNullValue
 Standardinhalt, wenn Wert NULL ist
Binding per Code herstellen
 FrameworkElement-Klasse
– SetBinding(DependencyProperty, BindingBase)
– GetBindingExpression(DependencyProperty)
BindingOperations-Klasse
 Stellt Hilfsmethoden für Bindungen bereit
 Bindung ermitteln
– GetBinding(DependencyObject, DependencyProperty),
– GetBindingExpression(…), IsDataBound(…), …
 Bindung erstellen
– SetBinding(DependencyObject, DependencyProperty,
BindingBase)
 Bindung entfernen
– ClearBinding(DependencyObject, DependencyProperty),
– ClearAllBindings(DependencyObject)
Aktualisierungen
 Binding.Mode-Eigenschaft
– TwoWay
 Volle Synchronisation zwischen Quelle und Ziel
– OneWay
 Ziel wird aktualisiert, wenn Quelle sich ändert
– OneWayToSource
 Quelle wird aktualisiert, wenn sich Ziel ändert
– OneTime
 Nur beim Start oder DataContext-Änderung
– Default
 Standardwert der Dependency Property des Ziels
Änderungen reflektieren
 INotifyPropertyChanged
– PropertyChanged-Event
 INotifyCollectionChanged
– CollectionChanged-Event
– ObservableCollection<T>
Änderungen übertragen
 Binding.UpdateSourceTrigger-Eigenschaft
– PropertyChanged
 Quelle wird bei Änderungen des Ziels aktualisiert.
– LostFocus
 Quelle wird aktualisiert, wenn das Ziel des Fokus verliert.
– Explicit
 Quelle wird durch Aufruf der
BindingExpression.UpdateSource()-Methode aktualisiert.
– Default
 Es wird der Standardwert der entsprechenden Dependency
Property des Ziels verwendet.
Beispiel
 Datenquelle manuell aktualisieren
XAML:
<TextBox Name="textBox1"
Text="{Binding Path=ItemName, UpdateSourceTrigger=Explicit}" />

Code:
Dim be As BindingExpression =
textBox1.GetBindingExpression(TextBox.TextProperty)
be.UpdateSource()
List Binding
 ItemsControl. ItemsSource
– Nimmt IEnumerable-Objekt entgegen

ListBox binden:
Dim names() As String = New String()
{
"Anton",
"Emil",
"Friedrich"
}
listBox1.ItemsSource = names
Darstellung anpassen
 Auf den Objekten der Liste wird
standardmäßig ToString() aufgerufen
 DisplayMemberPath-Eigenschaft
– Gibt den Namen/Pfad der anzuzeigenden
Eigenschaft an

ListBox binden:
Dim persons As PersonCollection = New PersonCollection
persons.Add(New Person("Anton", "Jäger"))
Me.listBox1.ItemsSource = persons
Me.listBox1.DisplayMemberPath = "FirstName"
Datenbindung zentralisieren
 FrameworkElement.DataContext
– Definiert Datenquelle für alle untergeordneten
Elemente
Code:
Dim persons As PersonCollection = New PersonCollection
persons.Add(New Person("Anton", "Jäger"))
Me.DataContext = persons

XAML:
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name">
Anzeige mit Vorlagen anpassen
 DataTemplate-Klasse
 Zuweisung über ItemsControl.ItemTemplate
Listenelemente im Format „Nachname, Vorname“:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=FirstName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Template in Resource Dictionary
<Window x:Class="DataBindingDemo.Window1“

<Window.Resources>
<DataTemplate x:Key="ListTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=FirstName}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>

<StackPanel Margin="10" Orientation="Vertical" Width="200">


<ListBox ItemsSource="{Binding}"
ItemTemplate="{StaticResource ListTemplate}" />
</StackPanel>

</Window>
Value Converter
 Konvertiert Werte zwischen Quelle & Ziel
 Klasse implementiert IValueConverter
– Convert() -> Richtung Anzeige
– ConvertBack() -> Richtung Datenquelle

Value Converter

Convert
Quelle Ziel
Convert back
Beispiel
Public Class BirthdayToAgeConverter
Implements IValueConverter
Public Function Convert( _
value As Object, targetType As Type, parameter As Object, _
culture As CultureInfo) As Object Implements IValueConverter.Convert
Dim birthDay As DateTime = DateTime.Parse(value.ToString)
Dim years As Integer = (DateTime.Now.Year - birthDay.Year)
If ((DateTime.Now.Month < birthDay.Month) _
OrElse ((DateTime.Now.Month = birthDay.Month) _
AndAlso (DateTime.Now.Day < birthDay.Day))) Then
years = (years - 1)
End If
Return years
End Function
Public Function ConvertBack( _
value As Object, targetType As Type, parameter As Object,
culture As CultureInfo) As Object Implements IValueConverter.ConvertBack

Throw New NotSupportedException


End Function
End Class
Beispiel
<Window x:Class="LocalDataBinding.Window1"
xmlns:local="clr-namespace:LocalDataBinding"
Title="DataBinding Demo" Height="360" Width="300">

<Window.Resources>
<local:BirthdayToAgeConverter x:Key="AgeConverter"/>
<DataTemplate x:Key="ListTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=Birthday,
Converter={StaticResource AgeConverter}}"/>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
</Window>
Multi Binding
 Mehrere Quelleigenschaften binden
– Aus unterschiedlichen Quellen
 Es wird ein Multi Value Converter benötigt
– Klasse die IMultiValueConverter implementiert
– Convert()
– ConvertBack()
Beispiel

<Window x:Class="BindingDemo.Window1“
xmlns:local="clr-namespace:BindingDemo">

<Window.Resources>
<local:NameConverter x:Key="nameConverter"/>
</Window.Resources>

<TextBlock Name="textBox2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource nameConverter}"
ConverterParameter="FormatLastFirst">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Beispiel
Public Class NameConverter
Implements IMultiValueConverter

Public Function Convert( _


values() As Object, targetType As Type, parameter As Object, _
culture As CultureInfo) As Object Implements IMultiValueConverter.Convert

Dim param As String = String.Empty


For Each value As Object In values
param = (param _
+ (value.ToString + " "))
Next
If (param.Length > 0) Then
Return param.Trim
End If
Return String.Empty
End Function
Public Function ConvertBack( _
value As Object, targetTypes() As Type, parameter As Object, _
culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack

Dim parameters() As Object = value.ToString.Split(" ")


Dim i As Integer = 0
Do While (i < parameters.Length)
parameter = parameters(i).ToString.Replace(" ", "").Trim
i = (i + 1)
Loop
Return parameters
End Function
End Class
Binding.StringFormat
 Es kann ein Formatstring angegeben werden
<TextBlock>
<TextBlock.Text>

<MultiBinding
StringFormat="{}{0} {1}, Geburtstag: {2:dd.MM.yy}">

<Binding Path="FirstName" />


<Binding Path="LastName" />
<Binding Path="Birthday" />

</MultiBinding>

</TextBlock.Text>
</TextBlock>
Data Trigger
 Deklaratives Einfügen von Logik
 Einfache ‚ist gleich‘-Konstrukte
 Werden über Styles.Triggers definiert
 DataTrigger-Element
– Binding: Verweist auf das Filterfeld
– Value: Legt den Filterwert fest
– Setter: Die Zielformatierung
Beispiel: Data Trigger
<Window x:Class="DataTriggerDemo.Window1">
<Window.Resources>
<Style x:Key="imageStyle" TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMan}" Value="true">
<Setter Property="Source" Value="pack://application:,,,/Man.png" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMan}" Value="false">
<Setter Property="Source" Value="pack://application:,,,/Women.png" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="personTemplate">
<Image Style="{StaticResource imageStyle}" Width="40" Height="40" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding}"
ItemTemplate="{StaticResource personTemplate}" />
</Grid>
</Window>
DataTemplateSelector-Klasse
 DataTemplate dynamisch auswählen
 Implementierung
– Klasse leitet von DataTemplateSelector ab
– SelectTemplate()-Methode überschreiben
– Passendes Template zurückgeben
 Verwendung
– Selector in Resource Dictionary einbinden
– In ItemsControl über ItemTemplateSelector
zuweisen
Beispiel: Template Selector
<Window x:Class="TemplateSelectorDemo.Window1"
xmlns:local="clr-namespace:TemplateSelectorDemo">

<Window.Resources>
<local:PersonDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>

<ListBox ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}" />
</Window>

Public Class PersonDataTemplateSelector


Inherits DataTemplateSelector

Public Overrides Function SelectTemplate(item As Object, _


container As DependencyObject) As DataTemplate
Dim person As Person = CType(item, Person)
Dim presenter As ContentPresenter = CType(container, ContentPresenter)
If person.IsMen = True Then
Return CType(presenter.FindResource("manTemplate"), DataTemplate)
Else
Return CType(presenter.FindResource("womenTemplate"), DataTemplate)
End If
Return Nothing
End Function
End Class
Erweiterte Darstellung
 Navigation in einer Liste
 Synchronisation mehrerer Listen
 Sortierte Darstellung der Einträge
 Filterung nach bestimmten Kriterien
 Gruppierung von Einträgen
ICollectionView
 Steht zwischen Datenquelle und Anzeige
 Wie BindingSource in Windows Forms
CollectionView ermitteln:
Dim viewSource As New CollectionViewSource()
viewSource.Source = persons
Dim view As ICollectionView = viewSource.View
Me.DataContext = view
Navigation
 CurrentItem, CurrentPosition
– Aktueller Datensatz bzw. Position
 CurrentChanging, CurrentChanged-Event
– Informiert über Änderungen
 MoveCurrentToFirst(), MoveCurrentToLast()
– Springt zum ersten bzw. letzten Satz.
 MoveCurrentToNext(), MoveCurrentToPrevious()
– Springt zum nächsten bzw. vorherigen Datensatz.
 MoveCurrentTo()
– Springt zum angegebenen Datensatz.
 MoveCurrentToPosition()
– Springt zum Datensatz des angegebenen Index.
Sortierung
 ICollectionView. SortDescriptions-Eigenschaft
 Kann ein oder mehrere Sortierkriterien
aufnehmen
Liste aufsteigend nach Nachname sortieren:
view.SortDescriptions.Add( _
New SortDescription("LastName", ListSortDirection.Ascending))
Filtern
 ICollectionView.Filter-Eigenschaft
 Nimmt ein Predicate<T> entgegen
 Callback-Mechanismus mit Delegate
 Callback-Methode gibt true oder false zurück
Personen die vor 1970 geboren wurden:
view.Filter = AddressOf OnFilter

Private Function OnFilter(ByVal p As Person) As Boolean


Return p.BirthDay.Year <= 1980
End Function
Gruppieren
 ICollectionView.GroupDescriptions-
Eigenschaft
 Nimmt eine oder mehrere
Gruppierungskriterien entgegen
Liste nach Namen gruppieren:
view.GroupDescriptions.Add( _
New PropertyGroupDescription("LastName"))
Gruppenkopf darstellen
 ItemsControl.GroupStyle
– Nimmt ein GroupStyle-Objekt entgegen
 Formatierung
– GroupStyle.HeaderTemplate
Beispiel – Liste gruppieren
<ListBox x:Name="listBox1"
ItemsSource="{Binding}" MinWidth="200">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1“
Background="Silver">
<TextBlock Text="{Binding Path=Name}"
FontWeight="Bold"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate> ... </ListBox.ItemTemplate>
</ListBox>
Was gibt‘s noch?
 Hierarchisches Binding
– Hierarchische Daten binden
– TreeView, …
 XML Binding
– XML Data Provider
– XML-Daten können in XAML eingebettet werden
– Bindung wird über XPath hergestellt
 Object Binding
– Objekte können per XAML gebunden werden
– Object Data Provider
{ In-depth support and consulting for
software architects and developers }
http://www.thinktecture.com/

joerg.neumann@thinktecture.com
www.HeadWriteLine.BlogSpot.com

39
Validierung
Der Validierungsprozess

Binding Target Binding Source


DependencyObject Object
Binding Object

Dependency
Validation Converter Property
Property
Validierung
 Binding wird um ValidationRules erweitert
<TextBox Name="numberTextBox">
<TextBox.Text>
<Binding Path="Number"
UpdateSourceTrigger="PropertyChanged">

<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>

</Binding>
</TextBox.Text>
</TextBox>
Validierung
 ExceptionValidationRule
– Wird ausgelöst, wenn die Datenquelle eine
Exception auslöst
– ShortCut
<Binding ValidatesOnExceptions = "True">
 Eigene Validierungsregeln
– Ableitung von ValidationRule
– Validate()-Methode überschreiben
Beispiel
Class FutureDateTimeRule
Inherits ValidationRule

Public Overrides Function Validate(


_value As Object, cultureInfo As CultureInfo) As ValidationResult

Dim theDate As DateTime


Try
theDate = DateTime.Parse(value.ToString)
Catch ex As FormatException
Return New ValidationResult(False, "Der Wert ist kein valides Datum!")
End Try
If (DateTime.Now.Date >= theDate) Then
Return New ValidationResult(False, "Datum muss in der Zukunft liegen!")
Else
Return ValidationResult.ValidResult
End If
End Function
End Class
Beispiel
Eigene Validierungsregel deklarieren:
<Window x:Class="ValidationDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationDemo">
<TextBox Name="startDateTextBox">
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:FutureDateTimeRule />
</Binding.ValidationRules>

</Binding>
</TextBox.Text>
</TextBox>
</Window>
Validierung über IDataErrorInfo
 System.ComponentModel.IDataErrorInfo
– public string Error
– public string this[string propertyName]
 Wird von der Datenquelle implementiert
 Vorteil
– Die Datenquelle kennt die Daten/Validierung
– Wiederverwendung
Beispiel
IDataErrorInfo-Interface implementieren:
Class Person
Implements IDataErrorInfo

Public ReadOnly Property [Error]() As String Implements IDataErrorInfo.Error


Get
Return Me(String.Empty)
End Get
End Property

Default Public ReadOnly Property Item( _


propertyName As String) As String Implements IDataErrorInfo.Item
Get
If (String.IsNullOrEmpty(propertyName) _
OrElse (propertyName = "BirthDay")) Then
If ((Me.BirthDay.Year >= DateTime.Now.Year)) Then
Return "Das Datum muss in der Vergangenheit liegen!"
End If
End If
Return String.Empty
End Get
End Property

End Class
Beispiel
Validierung über IDataErrorInfo:
<Window x:Class="ValidationDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Name="startDateTextBox">
<TextBox.Text>
<Binding
Path="StartDate"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True" />
</TextBox.Text>
</TextBox>
</Window>
Die Validation-Klasse
 Steuert die Validierung
 Eine Instanz pro Bindung
 Ergebnisse abfragen:
– Validation.GetHasError(element)
– Validation.GetErrors(element)
– Validation.AddErrorHandler(element, handler)
Visuelles Feedback
 Zuweisen eines Styles
<Window>
<Window.Resources>
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>

<TextBox Style="{StaticResource textStyleTextBox}" >


...
</TextBox>
</Window>
Visuelles Feedback
 Zuweisen eines Error Templates
<Window>
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<AdornedElementPlaceholder Name="fieldAdorner" />
<Image
ToolTip="{Binding ElementName=fieldAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
Source="Error.ico" Width="16" Height="16" Margin="5,0,0,0" />
</DockPanel>
</ControlTemplate>
</Window.Resources>

<TextBox Validation.ErrorTemplate="{StaticResource validationTemplate}" >


...
</TextBox>
</Window>
BindingGroups
 Ermöglichen das gemeinsame Validieren und
Speichern mehrerer Bindings
 Eine ValidationRule für mehrere Bindings
zuweisen
 Transaktionales Speichern
– BeginEdit(), CancelEdit(), CommitEdit()
 Validieren
– ValidateWithoutEdit()
Beispiel: BindingGroups
<Window x:Class="BindingGroupDemo.Window1"
xmlns:local="clr-namespace:BindingGroupDemo"
Validation.Error="Window_Error">

<Window.BindingGroup>
<BindingGroup Name="myGroup" NotifyOnValidationError="True">
<BindingGroup.ValidationRules>
<local:DateTimeRule />
</BindingGroup.ValidationRules>
</BindingGroup>
</Window.BindingGroup>

<Grid>
<TextBox>
<TextBox.Text="{Binding Path=StartDate, BindingGroupName=myGroup}">
</TextBox>
<TextBox>
<TextBox.Text="{Binding Path=EndDate, BindingGroupName=myGroup}">
</TextBox>
</Grid>
</Window>
{ In-depth support and consulting for
software architects and developers }
http://www.thinktecture.com/

joerg.neumann@thinktecture.com
www.HeadWriteLine.BlogSpot.com

54