Reputation: 956
I have a MainWindow and a UserControl. The MainWindow shows the UserControl. The UserControl itself following the MVVM pattern and implements the IDataErrorInfo Interface in the ViewModel. This works fine, but the Validation.ErrorTemplate isn't showing.
My UserControl.xaml
<TextBox x:Name="txtName"
Text="{Binding Path=Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Grid.Row="0" Grid.Column="2"
VerticalAlignment="Bottom" MinWidth="100" FontSize="12">
<TextBox.Resources>
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="1"/>
</AdornedElementPlaceholder>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
The ToolTip is shown correctly, but the red border of the TextBox is only shown when i use Snoop and select the TextBox in the VisualTree.
So i'm missing anything? Any Trigger?
I looked up this in two books an i try the examples, which are working. So is this an error according to the UserControl, and i have to update it in some way manually?
EDIT Now, I'm totally confused, when i only used it this way:
<TextBox x:Name="txtName"
Text="{Binding Path=Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Grid.Row="0" Grid.Column="2"
VerticalAlignment="Bottom" MinWidth="100" FontSize="12">
<TextBox.Resources>
<Style 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="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="3"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
The BorderBrush and BorderThickness is always "Red"/"3"! Why or how can i trigger to set it back? According to this: Only false style displaying when using Validation.HasError trigger property WPF the default values should appear when the Validation.HasError get cleared.
Upvotes: 0
Views: 3241
Reputation: 8631
I realize this is an old post, and you've probably moved on to bigger and better things, but I thought I'd post an answer for those who find this because, like me, they are struggling with similar problems.
I was having similar problems. For me, the answer was to replace
Path=(Validation.Errors)[0].ErrorContent
with
Path=(Validation.Errors).CurrentItem.ErrorContent
This got rid of the
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1').
Errors that you report in your edit to your answer. Also, see this blog (which gave me the idea) for more detail.
Upvotes: 2
Reputation: 956
So i found my mistake, (or at least the part why the Validation.HasError are never reset) i used the IDataErrorInfo Interface in a wrong way. I didn't return null or String.Empty if the validation passed. :(
//Not used in WPF so return value is null
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get
{
//the wrong way
string error = "false way";
//the right way
string error = null;
//or
string error = String.Empty;
switch (propertyName)
{
case ("name"):
if (string.IsNullOrEmpty(name) || name.Trim() == String.Empty)
{
error = "Enter name";
}
break;
case ("age"):
if (string.IsNullOrEmpty(age) || age.Trim() == String.Empty)
{
error = "Enter age";
}
break;
default:
Debug.Fail("Validation: Unexpected property: " + propertyName);
break;
}
return error;
}
}
EDIT sometimes i get this error, without the trim...
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0)[0].ErrorContent; DataItem='TextBox' (Name='txtName'); target element is 'TextBox' (Name='txtName'); target property is 'ToolTip' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Das angegebene Argument liegt außerhalb des gültigen Wertebereichs. Parametername: index'
Upvotes: 2
Reputation: 284
I think you need to move the Border
around the AdornedElementPlaceholder
. Here is the ValidationTemplate I use which has a cut corner which displays the error message when you hover over it.
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel>
<!--TextBox Error template-->
<Canvas Panel.ZIndex="1099">
<DockPanel>
<Border BorderThickness="1" BorderBrush="#FFdc000c" CornerRadius="0.7"
VerticalAlignment="Top" DockPanel.Dock="Bottom" HorizontalAlignment="Left">
<Grid>
<Polygon x:Name="toolTipCorner"
Grid.ZIndex="2"
Margin="-1"
Points="11,11 11,0 0,0"
Fill="#FFdc000c"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsHitTestVisible="True"/>
<Polyline Grid.ZIndex="3"
Points="12,12 0,0" Margin="-1" HorizontalAlignment="Right"
StrokeThickness="1.5"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
Stroke="White"
VerticalAlignment="Top"
IsHitTestVisible="True"/>
<AdornedElementPlaceholder x:Name="ErrorAdorner" />
</Grid>
</Border>
</DockPanel>
<Popup x:Name="ErrorPopup" Opacity="0" IsOpen="False" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=ErrorAdorner}" StaysOpen="True">
<Border Canvas.Bottom="4"
x:Name="errorBorder"
Canvas.Left="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Adorner}}"
CornerRadius="5"
Background="#FFdc000c">
<Border.Effect>
<DropShadowEffect ShadowDepth="2.25"
Color="Black"
Opacity="0.4"
Direction="315"
BlurRadius="4"/>
</Border.Effect>
<TextBlock TextWrapping="Wrap" Margin="4"
MaxWidth="250" Foreground="White"
FontSize="12"
Text="{Binding Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Adorner}}" />
</Border>
</Popup>
</Canvas>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Value="True" Binding="{Binding ElementName=toolTipCorner, Path=IsMouseOver}">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="fadeInStoryboard">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ErrorPopup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame Value="True" KeyTime="00:00:00"/>
</BooleanAnimationUsingKeyFrames>
<DoubleAnimation Duration="00:00:00.15"
Storyboard.TargetName="errorBorder"
Storyboard.TargetProperty="Opacity"
To="1"/>
<ThicknessAnimation Duration="00:00:00.15"
Storyboard.TargetName="errorBorder"
Storyboard.TargetProperty="Margin"
FillBehavior="HoldEnd"
From="1,0,0,0"
To="5,0,0,0">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut" Amplitude="2"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
<BeginStoryboard x:Name="fadeOutStoryBoard">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ErrorPopup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame Value="False" KeyTime="00:00:00"></DiscreteBooleanKeyFrame>
</BooleanAnimationUsingKeyFrames>
<DoubleAnimation Duration="00:00:00"
Storyboard.TargetName="errorBorder"
Storyboard.TargetProperty="Opacity"
To="0"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You can then set this to be the ErrorTemplate
for a item like so:
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ErrorTemplate2}"/>
</Style>
Upvotes: 0