文本框在切换选项卡时失去验证错误模板。

38

我有一个带有验证规则的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个回答

70

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>
问题在于,验证错误提示被绘制在装饰器层。当你切换标签页时,该层会被丢弃。

太棒了!当包装多个共享相同样式属性的控件时,它也能正常工作。 - LazyZebra
良好的AdornerDecorator使用! - AnjumSKhan
非常感谢。如果您有一个滚动查看器,它也必须在这个<TabItem Header="Foo">中。 <ScrollViewer VerticalScrollBarVisibility="Auto"> <Border> <AdornerDecorator> - David Wilton

14

对于特殊情况,我想补充一点:我之前也遇到了类似的问题,现在使用的解决方案与Dylan的代码类似。

区别在于我的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()的文档(虽然不确定是否适用于此处)也说明该静态方法从指定的 Visual 开始向上遍历视觉树,并返回找到的第一个装饰层。 - 但也许它在某些时候会停止,这在文档中没有明确说明。


谁会想到这个 bug 呢?在我用验证包装了一个包含多个文本框的 StackPanel 后,即使我更改选项卡,错误仍然存在。谢谢! - Alexandru Dicu

7
如Dylan所解释的那样,这是因为装饰层(Adorner layer)在选项卡切换时被丢弃了,而验证错误就是在这个层中绘制的。因此,您需要使用AdornerDecorator来包装内容。
我创建了一个行为(Behavior),它会自动将TabItemContent包装在AdornerDecorator中,从而无需手动在所有TabItem上执行此操作。
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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接