Janez Lukan
Janez Lukan

Reputation: 1449

WPF validation - displaying errors for elements in StackPanel

I'm having problems with how validation errors are displayed for TextBox elements in vertical StackPanel. I'm trying to display error messages below TextBox.

I have this error template:

    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <AdornedElementPlaceholder />
                        <ItemsControl ItemsSource="{Binding}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

If I have enough white space below the TextBox, error is displayed fine, but in StackPanel (for example), it does not add extra margin or padding for error messages when there are some, because of adorner layer.

It is expected to be so, according to this source:

Note that the Validation.ErrorTemplate will be displayed on the adorner layer. Elements in the adorner layer are rendered on top of the rest of the visual elements and they will not be considered when the layout system is measuring and arranging the controls on the adorned element layer.

How can I display validation error messages, so that they won't show over other elements in StackPanel?

Upvotes: 0

Views: 2066

Answers (2)

Il Vic
Il Vic

Reputation: 5666

You can also consider to include your error template in the TextBox's template. Something like that (of course it can be improved):

<Style x:Key="eTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <StackPanel>
                    <Border BorderBrush="Gray" BorderThickness="1" CornerRadius="1" Padding="2">
                        <ScrollViewer Name="PART_ContentHost" Focusable="False" 
                                    ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                                    ScrollViewer.VerticalScrollBarVisibility="Hidden"
                                    Background="#00FFFFFF" />
                    </Border>
                    <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(Validation.Errors)}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

In this way the ItemsControl is considered for the layout computation.

Upvotes: 3

Janez Lukan
Janez Lukan

Reputation: 1449

Ok, I found the solution with converter. I ended up with style similar to this:

    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="10" />
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <AdornedElementPlaceholder />
                        <ItemsControl ItemsSource="{Binding}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="0,5"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Trigger.Setters>
                    <Setter Property="Margin" Value="{Binding (Validation.Errors).Count, RelativeSource={RelativeSource Self}, Converter={StaticResource ErrorsToMarginConverter}}"/>
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>

and converter:

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is int)
        {
            var errors = (int)value;
            return new Thickness(10, 10, 10, (errors * 20));
        }

        return value;
    }

Upvotes: 0

Related Questions