teekkari
teekkari

Reputation: 956

Validation.ErrorTemplate not shown

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

Answers (3)

Dave
Dave

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

teekkari
teekkari

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

Martin Grundy
Martin Grundy

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

Related Questions