TextBox with validation loses ErrorTemplate on tab change
Asked Answered
D

3

38

I have a TextBox with a validation rule that is on a tab of a TabControl. The default ErrorTemplate correctly shows (red border around TextBox) when the validation rule fails.
However if there is a switch to another tab and then back to the tab with the TextBox the ErrorTemplate hightlight is gone. If there is a change in the TextBox the validation rule is still called and returns false but the error highlight still doesn't show.
Only when the text content is changed to be valid and then again to be invalid does the highligh comeback.
I would like that if the text content is invalid that switching to another tab and back keeps the invalid highlight. Any ideas to get this behaviour most welcome.
The xaml:

<TextBox Height="35" >
  <TextBox.Text>
    <Binding Path="pan_id" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <ps:PanIdValidation />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>
Declination answered 28/3, 2012 at 12:2 Comment(0)
C
70

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.

Clop answered 28/3, 2012 at 17:17 Comment(3)
awesome! it also works when wrapping several controls that share the same style attributes.Nels
Good use of AdornerDecorator !Contortion
Many thanks. IF you have a scroll viewer it must also be inside that <TabItem Header="Foo"> <ScrollViewer VerticalScrollBarVisibility="Auto"> <Border> <AdornerDecorator>Phio
O
14

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.

Overfly answered 25/11, 2015 at 8:28 Comment(1)
Who would have thought about this bug? After I wrapped my StackPanel which contained several TextBoxes with validations, the errors stay there even if I change the tab items. Thank you!Gallegos
E
8

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"
Eberhardt answered 24/1, 2016 at 18:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.