TextBox with validation loses ErrorTemplate on tab change TextBox with validation loses ErrorTemplate on tab change wpf wpf

TextBox with validation loses ErrorTemplate on tab change


TabItem should be defined as follows:

<TabItem Header="Foo">    <Border>        <AdornerDecorator>            <Grid>                <TextBox Height="35" >                    <TextBox.Text>                         <Binding Path="pan_id" UpdateSourceTrigger="PropertyChanged">                             <Binding.ValidationRules>                                 <ps:PanIdValidation />                             </Binding.ValidationRules>                          </Binding>                      </TextBox.Text>                  </TextBox>              </Grid>          </AdornerDecorator>      </Border>  </TabItem>

The issue is, the Validation.Error cues are painted in the Adorner Layer. When you switch tabs, that layer is discarded.


Just an addition for special cases: I was having similar problem and I am now using a solution similar to Dylan's code.

The difference is that my TabItem contains GroupBox and the TextBox is inside it. In this case the AdornerDecorator has to be in the GroupBox itself, not a direct descendand of the TabItem.

So this didn't work:

<TabItem>    <AdornerDecorator>        <Grid>            <GroupBox>                <Grid>                    <TextBox>...<TextBox/>                </Grid>            </GroupBox>        </Grid>    </AdornerDecorator></TabItem>

But this did:

<TabItem>    <Grid>        <GroupBox>            <AdornerDecorator>                <Grid>                    <TextBox>...<TextBox/>                </Grid>            </AdornerDecorator>        </GroupBox>    </Grid></TabItem>

I am adding it because I couldn't find the solution easily and even the documentation of AdornerLayer.GetAdornerLayer() (though not sure if it is applicable here) states This static method traverses up the visual tree starting at the specified Visual and returns the first adorner layer found. - but perhaps it also stops at some point, this is not clear from the docs.


As Dylan explained, this is because the Adorner layer, in which the validation errors are drawn, is discarded on tab switch. So you need to wrap the content with AdornerDecorator.

I have created a behavior that wraps the Content of TabItem automatically in an AdornerDecorator, so that it doesn't have to be done manually on all TabItems.

public static class AdornerBehavior{    public static bool GetWrapWithAdornerDecorator(TabItem tabItem)    {        return (bool)tabItem.GetValue(WrapWithAdornerDecoratorProperty);    }    public static void SetWrapWithAdornerDecorator(TabItem tabItem, bool value)    {        tabItem.SetValue(WrapWithAdornerDecoratorProperty, value);    }    // Using a DependencyProperty as the backing store for WrapWithAdornerDecorator.  This enables animation, styling, binding, etc...    public static readonly DependencyProperty WrapWithAdornerDecoratorProperty =        DependencyProperty.RegisterAttached("WrapWithAdornerDecorator", typeof(bool), typeof(AdornerBehavior), new UIPropertyMetadata(false, OnWrapWithAdornerDecoratorChanged));    public static void OnWrapWithAdornerDecoratorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)    {        var tabItem = o as TabItem;        if (tabItem == null) return;        if(e.NewValue as bool? == true)        {            if (tabItem.Content is AdornerDecorator) return;            var content = tabItem.Content as UIElement;            tabItem.Content = null;            tabItem.Content = new AdornerDecorator { Child = content };        }        if(e.NewValue as bool? == false)        {            if (tabItem.Content is AdornerDecorator)            {                var decorator= tabItem.Content as AdornerDecorator;                var content = decorator.Child;                decorator.Child = null;                tabItem.Content = content;            }        }    }}

You can set this behavior on all TabItems via a default style:

<Style TargetType="TabItem">    <Setter Property="b:AdornerBehavior.WrapWithAdornerDecorator" Value="True"></Setter></Style>

b is the namespace where the behavior is located, something like this (will be different for every project):

xmlns:b="clr-namespace:Styling.Behaviors;assembly=Styling"