Подтвердить что ты не робот

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

Ответ 1

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 Layer. Когда вы переключаете вкладки, этот слой отбрасывается.

Ответ 2

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

Разница в том, что мой 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. - но, возможно, он также останавливается в какой-то момент, это не ясно из документов.

Ответ 3

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

Я создал поведение , которое автоматически переносит Content of TabItem в AdornerDecorator, так что его не нужно делать вручную на всех вкладках.

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"