Daniel Möller
Daniel Möller

Reputation: 86600

WPF validation stops working when changing tabs

I have a WPF application where there is a TabControl, and within a certain tab there is a TextBox with a validation rule.

The validation rule works fine, the converter works fine, the binding is ok too.
But there is a very annoying bug (probably with WPF itself) that happens when I do this:

The validation completely stops working until I type a valid text, then it starts working again. Changing the text is not enough, it will only start working again if I enter a valid text.

How can I force a revalidation of the text when I enter the tab?


I already tried two solutions listed here, but they seem not to bring the red border:

This is a brief description of the XAML, if needed:

<TabControl ...>
    <TabItem ... />
    <TabItem ...>
        ...
        <TextBox Name="txtName" ...>
            <TextBox.Text>
                <Binding Path="..." 
                         UpdateSourceTrigger="PropertyChanged" 
                         Mode="OneWayToSource"
                         FallbackValue="5"
                         Converter="MyCustomConverterWorkingOk">

                     <Binding.ValidationRules>
                         <local:MyCustomValidationWorkingOk/>
                     </Binding.ValidationRules>
                </Binding> 
            </TextBox.Text>
        </TextBox>
    </TabItem>
</TabControl>

Upvotes: 2

Views: 1066

Answers (3)

BionicCode
BionicCode

Reputation: 28968

The reason for this behavior is the way the TabControl displays its content: all tabs share the same ContentPresenter. When navigating between tabs, the old content is completely removed. This is also true for the AdornerLayer, on which the error template is rendered.

When the tab content is removed then the Adorner that renders the validation error template is also removed. Since the AdornerLayer is stateless and the TabControl doesn't monitor all the possible adorners, the old rendered error templates of the previous tab content can't be restored.

You have three good options:

  1. don't allow the user to leave a tab unless the tab's data is in a valid state or
  2. discard invalid data (which would also reset the related errors) when leaving the tab or
  3. force the AdornerLayer to re-render when switching back to a TabItem which holds invalid data input.

The following example shows how you can force the AdornerLayer to render the adorner of all TextBox elements again as soon as the adorned element is visible:

<!-- Custom error template -->
<ControlTemplate x:Key="ValidationErrorTemplate">
  <StackPanel>

    <!-- Placeholder for the TextBox itself -->
    <AdornedElementPlaceholder />

    <TextBlock Text="{Binding ErrorContent}"
               Foreground="Red" />
  </StackPanel>
</ControlTemplate>

<!-- Style to trigger the rendering of the AdornerLayer by setting the error template -->
<Style TargetType="TextBox">
  <Setter Property="Validation.ErrorTemplate"
          Value="{x:Null}" />
  <Style.Triggers>
    <MultiTrigger>
      <MultiTrigger.Conditions>
        <Condition Property="Validation.HasError"
                   Value="True" />
        <Condition Property="IsVisible"
                   Value="True" />
      </MultiTrigger.Conditions>
      <Setter Property="Validation.ErrorTemplate"
              Value="{StaticResource ValidationErrorTemplate}" />
    </MultiTrigger>
  </Style.Triggers>
</Style>

Upvotes: 3

Scott Howard
Scott Howard

Reputation: 266

Give each TabItem its own AdornerLayer (via the AdornerDecorator element) as shown below. The elements in the adorner layer will be maintained across tab switches.

<TabItem Header="Tab 1">
    <AdornerDecorator>
        <Grid Background="WhiteSmoke">
            <TextBlock Text="Hello from #1" />
        </Grid>
    </AdornerDecorator>
</TabItem>

<TabItem Header="Tab 2">
    <AdornerDecorator>
        <Grid Background="WhiteSmoke">
            <TextBlock Text="Hello from #2" />
        </Grid>
    </AdornerDecorator>
</TabItem>

Upvotes: 1

Akuma Walker
Akuma Walker

Reputation: 28

Not sure but try this:

string text = txtName.Text;
txtName.Text = null;
txtName.Text = text;

Upvotes: -1

Related Questions