Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error template is displayed above other controls, when it should be hidden

I'm trying to implement validation in my WPF application using the IDataErrorInfo interface, and I've encountered a not-so-desirable situation.

I have this template which is used when a control fails to validate

<ControlTemplate x:Key="errorTemplate">
    <DockPanel LastChildFill="true">
        <Border Background="Red" DockPanel.Dock="Right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                                    ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
            <TextBlock Text="!" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Foreground="White" />
        </Border>
        <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
            <Border BorderBrush="red" BorderThickness="1" />
        </AdornedElementPlaceholder>
    </DockPanel>
</ControlTemplate>

Everything is well until I try to display something above the control that failed validation, such as displaying a dock item above it:

Normal displayDisplay when part of the control is hidden

How can I avoid this and make my error template displayed below the dock item, as it should?

EDIT

I found that I could wrap my TextBox with an AdornerDecorator to fix this, but I really don't want to do this for each and every TextBox control in my application. Is there maybe a way to set it with a Style or some other way?

EDIT 2

I could probably change the default TextBox ControlTemplate to include an AdornerDecorator, but I'm not too keen on changing any of WPF's default control templates. Any other suggestions are welcome.

like image 281
Adi Lester Avatar asked Apr 24 '12 08:04

Adi Lester


2 Answers

OK, I found a relatively simple solution which doesn't force me to change any control templates.

Instead of decorating each TextBox with an AdornerDecorator like this

<StackPanel>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
</StackPanel>

I can have the AdornerDecorator wrap my entire view, which achieves the same result.

<AdornerDecorator>
    <StackPanel>
        <TextBox Text={Binding ...} />
        <TextBox Text={Binding ...} />
    </StackPanel>
</AdornerDecorator>

This way I can define it at most one time per view.

like image 92
Adi Lester Avatar answered Oct 19 '22 20:10

Adi Lester


Based on @AdiLester great answer, if your controls are deriving from a base class and you don't want to put AdornerDecorator in XAML of each control, then go this way:

public class MyBaseUserControl : UserControl
{
    public MyBaseUserControl()
    {

    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        if (!(newContent is AdornerDecorator))
        {
            this.RemoveLogicalChild(newContent);

            var decorator = new AdornerDecorator();
            decorator.Child = newContent as UIElement;

            this.Content = decorator;
        }
    }
}
like image 20
Mohsen Afshin Avatar answered Oct 19 '22 21:10

Mohsen Afshin