WPF – MVVM, IDataErrorInfo, Validation.ErrorTemplate


Das ist das Ergebnis meiner Anforderungen bzgl. der Darstellung von Validierungsfehlern/Pflichtfeldern. Pflichtfelder sollten einfach durch ein * gekennzeichnet sein und fehlerhafte Eingaben sollten sowohl als Tooltip als auch durch ein Adorner (i) angezeigt werden. Die übliche rote Umrandung der Textboxen darf natürlich jeder gerne selber einfügen 😉

Damit das ganze funktioniert, habe ich erst einmal 2 Control Templates angelegt. Ein ControlTemplate für die „normale“ Validierung und ein zweites für die Anzeige von Pflichtfeldern.

<SolidColorBrush x:Key="BrushError">RED</SolidColorBrush>
<ControlTemplate x:Key="ValidationTemplate" >
 <DockPanel>
  <AdornedElementPlaceholder Name="MyAdornedElement" />
  <ToggleButton x:Name="MyInfo"
                Focusable="False"
                Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}">
  <ToggleButton.Template>
   <ControlTemplate>
    <Border Margin="2,2,2,0" Height="20" Width="20" CornerRadius="10" BorderBrush="{StaticResource BrushError}" BorderThickness="1" Background="Transparent"
 VerticalAlignment="Top" HorizontalAlignment="Center"
 RenderTransformOrigin=".5,.5"
 ToolTip="Klick für weitere Informationen">
    <TextBlock Text="i" Foreground="{StaticResource BrushError}" FontWeight="Bold" HorizontalAlignment="Center"
 ToolTip="Klick für weitere Informationen"/>
   <Border.LayoutTransform>
    <ScaleTransform ScaleX=".8" ScaleY=".8"/>
   </Border.LayoutTransform>
  </Border>
 </ControlTemplate>
 </ToggleButton.Template>
 </ToggleButton>
 <Popup IsOpen="{Binding ElementName=MyInfo, Path=IsChecked}" PlacementTarget="{Binding ElementName=MyInfo}" Placement="Right" AllowsTransparency="True">
 <Grid Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}">
 <Border Background="{StaticResource BrushError}" Margin="3,0,0,0" x:Name="ErrorControl" BorderBrush="White" BorderThickness="1">
 <TextBlock Margin="10,3,5,2" TextTrimming="WordEllipsis" TextWrapping="Wrap"
 Text="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
 Foreground="White" FontWeight="Bold">
 </TextBlock>
 </Border>
 <Path x:Name="path" Margin="3,0,0,0" Data="M 0,10 L 10,0 " Fill="{StaticResource BrushError}"
 StrokeThickness="2" Stroke="White" />
 <Rectangle IsHitTestVisible="True" Fill="Transparent" ToolTip="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
 </Grid>
 </Popup>
 </DockPanel>
 </ControlTemplate>

<ControlTemplate x:Key="ValidationTemplateEmpty">
 <DockPanel>
 <TextBlock Text="*" Margin="0,0,3,0" Foreground="Red" Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}"
 ToolTip="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
 <AdornedElementPlaceholder Name="MyAdornedElement" />
 </DockPanel>
 </ControlTemplate>

Vielleicht noch eine kurze Anmerkung. Ich hatte in meiner ersten Version einen Adorner benutzt und kein Popup. Das Problem mit Adornern ist, dass diese nur in AdornerDecorator Grenzen angezeigt werden können. Bestimmte Controls wie ein ScrollViewer haben diese AdornerDecorator im ControlTemplate und somit konnte mein ValidationAdorner nicht über diese Grenzen hinaus angezeigt werden. Das Popup hat dieses Problem zwar nicht, aber dafür andere Nebeneffekte. Damit kann ich aber leben 😉
Mit den 2 ControlTemplates sind die Grundvorraussetzungen also geschaffen. Nun zu den TextBoxen. Das Ziel habe ich ja Eingangs schon formuliert. Normalerweise sollten ja nun folgende Bedingungen reichen, damit alles so läuft wie gewollt. Zum einen muss die entsprechende TextBox ein Validierungsfehler haben, damit etwas angezeigt wird. Und zum anderen sollte dann unterschieden werden, ob es nur die Anzeige als Pflichtfeld ist oder ein „richtiger“ Validierungsfehler vorliegt.

Folgendes Problem hatte ich dabei nicht bedacht: Bei der Bindingeinstellung LostFocus, will der Nutzer ja erst seine Eingaben machen und dann beim Verlassen des Feldes, soll die Validierung durchgeführt werden. Soweit so gut, aber das hatte in der ersten Version meiner Validierung eine unschöne Auswirkung. sobald Text in der TextBox eingetragen war, hat mein Validation.ErrorTemplate umgeschaltet auf das normale Validierungstemplate und mein roter * für die Pflichtfeldanzeige war nicht mehr da.

Einen wirklich schönen Weg das ganze zu Umgehen habe ich nicht gefunden. Ich benutze jetzt noch eine zusätzliche Regel, um meine Pflichfeld Validierungen anzuzeigen. Und zwar prüfe ich die Fehlermeldung, ob in dieser das Wort „pflichtfeld“ vorkommt. Damit sieht der Style für meine TextBoxen wie folgt aus.

<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
 <MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource ValidationPflichtfeldMultiConverter}">
<Binding Path="(Validation.HasError)" RelativeSource="{RelativeSource self}" />
<Binding Path="Text" RelativeSource="{RelativeSource self}" />
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{RelativeSource self}" />
</MultiBinding>
</Condition.Binding>
</Condition>
 </MultiDataTrigger.Conditions>
 <MultiDataTrigger.Setters>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplateEmpty}"/>
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</MultiDataTrigger.Setters>
 </MultiDataTrigger>
 </Style.Triggers>
 </Style>

Der Vollständigkeit halber noch der MultiConverter für das Auswerten der Regeln. Wenn der Converter „true“ zurück gibt, soll das ValidationTemplateEmpty für Pflichtfelder angezeigt werden.

public class ValidationErrorPflichtfeldMultiConverter : IMultiValueConverter
{
 #region Implementation of IMultiValueConverter

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
 {

if (values.Length != 3)
 return false;

//values[0] = Validation.HasError
 if (!(values[0] is bool))
 return false;

if (!(bool)values[0])
 return false;

//values[1] = Text vom TextBoxControl
 var s = (string)values[1];

if (string.IsNullOrWhiteSpace(s))
 return true;

//value[2] = (Validation.Errors).CurrentItem.ErrorContent
 var err = (string)values[2];

if (err.ToLower().Contains("pflichtfeld"))
 return true;

return false;
 }

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
 {
 throw new NotImplementedException();
 }

#endregion
 }

Für Hinweise und Verbesserungsvorschläge oder gänzlich andere Lösungsmöglichkeiten, bin ich natürlich dankbar.

Advertisements
Dieser Beitrag wurde unter MVVM abgelegt und mit , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s