How to display validation errors on silverlight TextBlock

428 Views Asked by At

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?

2

There are 2 best solutions below

1
On BEST ANSWER

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>
2
On

It is not really supported in Silverlight by default. One way that I have found to work well in the past is to make a control that inherits from Textblock. You can then handle the IDatanotify interface and exceptions and turn the textblock different colors based off of those.