Search code examples
c#silverlightsilverlight-5.0

How to display validation errors on silverlight TextBlock


I have a tree which represents XML document. And I show XML validations errors using a bound class which implements INotifyDataErrorInfo.

It works all fine on editable controls like text box or combo.

But some nodes are just text blocks. And they have errors too.

How do I go about it?


Solution

  • I recommend you wrap your TextBlock in a ValidationErrorBorder:

    <ValidationErrorBorder DataContextValidationTargetPath="MyTextProperty">
        <TextBlock Text="{Binding Path=MyTextProperty}"/>
    </ValidationErrorBorder>
    

    ValidationErrorBorder control code:

    /*
     * Use in conjuction with other Controls that do not have the appropriate
     * visualStates to indicate ValidationErrors.
     * Either set DataContextValidationTargetPath to have this
     * ValidationErrorBorder show validation errors that occur at the targeted
     * property of the DataContext
     * or bind ValidationTarget to any target you wish to have this
     * ValidationErrorBorder show validation errors occuring for the bound target.
     */
    [TemplateVisualState(GroupName="ValidationStates", Name = "Valid")]
    [TemplateVisualState(GroupName="ValidationStates", Name = "InvalidUnfocused")]
    [TemplateVisualState(GroupName="ValidationStates", Name = "InvalidFocused")]
    public class ValidationErrorBorder : ContentControl
    {
        public object ValidationTarget
        {
            get { return GetValue( ValidationTargetProperty ); }
            set { SetValue( ValidationTargetProperty, value ); }
        }
    
        public static readonly DependencyProperty ValidationTargetProperty =
            DependencyProperty.Register( "ValidationTarget", typeof( object ),
            typeof( ValidationErrorBorder ), new PropertyMetadata( null ) );
    
        public string DataContextValidationTargetPath
        {
            get
            {
              return (string) GetValue( DataContextValidationTargetPathProperty );
            }
            set { SetValue( DataContextValidationTargetPathProperty, value ); }
        }
    
        public static readonly DependencyProperty
            DataContextValidationTargetPathProperty =
            DependencyProperty.Register( "DataContextValidationTargetPath",
            typeof( string ), typeof( ValidationErrorBorder ),
            new PropertyMetadata( null, HandlePathChanged ) );
    
        private static void HandlePathChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            ((ValidationErrorBorder) d).HandlePathChanged();
        }
    
        private void HandlePathChanged()
        {
            if (DataContextValidationTargetPath != null)
                SetBinding(ValidationTargetProperty,
                    new Binding(DataContextValidationTargetPath));
            else
                ClearValue( ValidationTargetProperty );
        }
    
        public ValidationErrorBorder()
        {
            DefaultStyleKey = typeof( ValidationErrorBorder );
        }
    }
    

    and the control template:

    <Style TargetType="controls:ValidationErrorBorder">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:ValidationErrorBorder">
                    <Grid x:Name="RootElement" Background="{x:Null}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ValidationStates">
                                <VisualState x:Name="Valid"/>
                                <VisualState x:Name="InvalidUnfocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="InvalidFocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <system:Boolean>True</system:Boolean>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid>
                            <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
                            <Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
                                <ToolTipService.ToolTip>
                                    <ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}">
                                        <ToolTip.Triggers>
                                            <EventTrigger RoutedEvent="Canvas.Loaded">
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
                                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                                <DiscreteObjectKeyFrame.Value>
                                                                    <system:Boolean>True</system:Boolean>
                                                                </DiscreteObjectKeyFrame.Value>
                                                            </DiscreteObjectKeyFrame>
                                                        </ObjectAnimationUsingKeyFrames>
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </EventTrigger>
                                        </ToolTip.Triggers>
                                    </ToolTip>
                                </ToolTipService.ToolTip>
                                <Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
                                    <Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
                                    <Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
                                </Grid>
                            </Border>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    and the validationTooltipTemplate:

    <ControlTemplate x:Key="ValidationToolTipTemplate">
        <Grid x:Name="Root" Margin="5,0" Opacity="0" RenderTransformOrigin="0,0">
            <Grid.RenderTransform>
                <TranslateTransform x:Name="xform" X="-25"/>
            </Grid.RenderTransform>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="OpenStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition GeneratedDuration="0"/>
                        <VisualTransition GeneratedDuration="0:0:0.2" To="Open">
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform">
                                    <DoubleAnimation.EasingFunction>
                                        <BackEase Amplitude=".3" EasingMode="EaseOut"/>
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
                            </Storyboard>
                        </VisualTransition>
                    </VisualStateGroup.Transitions>
                    <VisualState x:Name="Closed">
                        <Storyboard>
                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Open">
                        <Storyboard>
                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform"/>
                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Border Background="#052A2E31" CornerRadius="5" Margin="4,4,-4,-4"/>
            <Border Background="#152A2E31" CornerRadius="4" Margin="3,3,-3,-3"/>
            <Border Background="#252A2E31" CornerRadius="3" Margin="2,2,-2,-2"/>
            <Border Background="#352A2E31" CornerRadius="2" Margin="1,1,-1,-1"/>
            <Border Background="#FFDC000C" CornerRadius="2"/>
            <Border CornerRadius="2">
                <TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" UseLayoutRounding="false"/>
            </Border>
        </Grid>
    </ControlTemplate>