TextBox с проверкой теряет ErrorTemplate при смене вкладки

У меня есть TextBox с правилом проверки, который находится на вкладке TabControl. ErrorTemplate по умолчанию правильно показывает (красная рамка вокруг TextBox) при сбое правила проверки.
Однако, если есть переход на другую вкладку, а затем обратно на вкладку с TextBox, подсветка ErrorTemplate исчезла. Если в TextBox есть изменение, правило проверки по-прежнему вызывается и возвращает false, но выделение ошибки по-прежнему не отображается.
Только когда текстовое содержимое изменяется на действительный, а затем снова на недействительный, возвращается подсветка.
Мне бы хотелось, чтобы, если текстовое содержимое было недопустимым, при переходе на другую вкладку и обратно сохраняется недопустимая подсветка Любые идеи, чтобы получить такое поведение, приветствуются.
XAML:

<TextBox Height="35" >
  <TextBox.Text>
    <Binding Path="pan_id" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <ps:PanIdValidation />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

3 ответа

Решение

TabItem должен быть определен следующим образом:

<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>

Проблема заключается в том, что ошибки Validation.Error окрашены в слое Adorner. Когда вы переключаете вкладки, этот слой отбрасывается.

Просто дополнение для особых случаев: у меня была похожая проблема, и сейчас я использую решение, подобное коду Дилана.

Разница в том, что мой TabItem содержит GroupBox и TextBox находится внутри него. В этом случае AdornerDecorator должен находиться в самом GroupBox, а не в качестве прямого потомка TabItem.

Так что это не сработало:

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

Но это сделал:

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

Я добавляю это, потому что я не мог найти решение легко и даже документацию AdornerLayer.GetAdornerLayer() (хотя не уверен, если это применимо здесь) говорится This static method traverses up the visual tree starting at the specified Visual and returns the first adorner layer found. - но, возможно, это также останавливается в какой-то момент, это не ясно из документов.

Как объяснил Дилан, это происходит потому, что слой Adorner, в котором отображаются ошибки валидации, отбрасывается при переключении табуляции. Так что вам нужно обернуть содержимое AdornerDecorator,

Я создал поведение, которое оборачивает Content из TabItem автоматически в AdornerDecorator, так что это не должно быть сделано вручную на всех 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;
            }
        }
    }
}

Вы можете установить это поведение на всех TabItems через стиль по умолчанию:

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

b это пространство имен, в котором находится поведение, что-то вроде этого (будет различным для каждого проекта):

xmlns:b="clr-namespace:Styling.Behaviors;assembly=Styling"
Другие вопросы по тегам