错误模板显示在其他控件上方,应隐藏它

时间:2021-07-07 07:12:38

I'm trying to implement validation in my WPF application using the IDataErrorInfo interface, and I've encountered a not-so-desirable situation.

我正在尝试使用IDataErrorInfo接口在我的WPF应用程序中实现验证,并且我遇到了一个不太理想的情况。

I have this template which is used when a control fails to validate

我有这个模板,当控件无法验证时使用

<ControlTemplate x:Key="errorTemplate">
    <DockPanel LastChildFill="true">
        <Border Background="Red" DockPanel.Dock="Right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                                    ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
            <TextBlock Text="!" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Foreground="White" />
        </Border>
        <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
            <Border BorderBrush="red" BorderThickness="1" />
        </AdornedElementPlaceholder>
    </DockPanel>
</ControlTemplate>

Everything is well until I try to display something above the control that failed validation, such as displaying a dock item above it:

一切顺利,直到我尝试在验证失败的控件上方显示某些内容,例如在其上方显示停靠项:

错误模板显示在其他控件上方,应隐藏它错误模板显示在其他控件上方,应隐藏它

How can I avoid this and make my error template displayed below the dock item, as it should?

如何避免这种情况并使我的错误模板显示在停靠项目下面,因为它应该如此?

EDIT

编辑

I found that I could wrap my TextBox with an AdornerDecorator to fix this, but I really don't want to do this for each and every TextBox control in my application. Is there maybe a way to set it with a Style or some other way?

我发现我可以使用AdornerDecorator包装我的TextBox来解决这个问题,但我真的不想为我的应用程序中的每个TextBox控件执行此操作。有没有办法用Style或其他方式设置它?

EDIT 2

编辑2

I could probably change the default TextBox ControlTemplate to include an AdornerDecorator, but I'm not too keen on changing any of WPF's default control templates. Any other suggestions are welcome.

我可以更改默认的TextBox ControlTemplate以包含AdornerDecorator,但我不太热衷于更改任何WPF的默认控件模板。欢迎任何其他建议。

3 个解决方案

#1


12  

OK, I found a relatively simple solution which doesn't force me to change any control templates.

好的,我找到了一个相对简单的解决方案,它不会强迫我更改任何控件模板。

Instead of decorating each TextBox with an AdornerDecorator like this

而不是像这样使用AdornerDecorator装饰每个TextBox

<StackPanel>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
</StackPanel>

I can have the AdornerDecorator wrap my entire view, which achieves the same result.

我可以让AdornerDecorator包装我的整个视图,从而实现相同的结果。

<AdornerDecorator>
    <StackPanel>
        <TextBox Text={Binding ...} />
        <TextBox Text={Binding ...} />
    </StackPanel>
</AdornerDecorator>

This way I can define it at most one time per view.

这样我每次最多可以定义一次。

#2


3  

Based on @AdiLester great answer, if your controls are deriving from a base class and you don't want to put AdornerDecorator in XAML of each control, then go this way:

基于@AdiLester的很好的答案,如果您的控件是从基类派生的,并且您不想将AdornerDecorator放在每个控件的XAML中,那么请采用以下方式:

public class MyBaseUserControl : UserControl
{
    public MyBaseUserControl()
    {

    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        if (!(newContent is AdornerDecorator))
        {
            this.RemoveLogicalChild(newContent);

            var decorator = new AdornerDecorator();
            decorator.Child = newContent as UIElement;

            this.Content = decorator;
        }
    }
}

#3


0  

I would use a style, and here here's an example of one that you can easily adapt.

我会使用一种风格,这里有一个你可以轻松适应的例子。

Note that the ErrorContent is coming from (Validation.Errors).CurrentItem.ErrorContent as opposed to Errors[0]. Although both will work, the latter will litter your output window with swallowed exceptions as outlined here.

请注意,ErrorContent来自(Validation.Errors).CurrentItem.ErrorContent而不是Errors [0]。虽然两者都可以工作,但后者会在输出窗口中丢失吞噬异常,如此处所述。

<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
    <Setter Property="Margin" Value="0,0,16,0" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />

    <!--
    Error handling
    -->
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <TextBlock DockPanel.Dock="Right" Text=" *" 
                               Foreground="Red" FontWeight="Bold" FontSize="16" 
                               ToolTip="{Binding ElementName=placeholder, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Border BorderBrush="Red"  BorderThickness="1">
                        <AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
                    </Border>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="Background" Value="LightYellow"/>
        </Trigger>
    </Style.Triggers>
</Style>

#1


12  

OK, I found a relatively simple solution which doesn't force me to change any control templates.

好的,我找到了一个相对简单的解决方案,它不会强迫我更改任何控件模板。

Instead of decorating each TextBox with an AdornerDecorator like this

而不是像这样使用AdornerDecorator装饰每个TextBox

<StackPanel>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
    <AdornerDecorator>
        <TextBox Text={Binding ...} />
    </AdornerDecorator>
</StackPanel>

I can have the AdornerDecorator wrap my entire view, which achieves the same result.

我可以让AdornerDecorator包装我的整个视图,从而实现相同的结果。

<AdornerDecorator>
    <StackPanel>
        <TextBox Text={Binding ...} />
        <TextBox Text={Binding ...} />
    </StackPanel>
</AdornerDecorator>

This way I can define it at most one time per view.

这样我每次最多可以定义一次。

#2


3  

Based on @AdiLester great answer, if your controls are deriving from a base class and you don't want to put AdornerDecorator in XAML of each control, then go this way:

基于@AdiLester的很好的答案,如果您的控件是从基类派生的,并且您不想将AdornerDecorator放在每个控件的XAML中,那么请采用以下方式:

public class MyBaseUserControl : UserControl
{
    public MyBaseUserControl()
    {

    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        if (!(newContent is AdornerDecorator))
        {
            this.RemoveLogicalChild(newContent);

            var decorator = new AdornerDecorator();
            decorator.Child = newContent as UIElement;

            this.Content = decorator;
        }
    }
}

#3


0  

I would use a style, and here here's an example of one that you can easily adapt.

我会使用一种风格,这里有一个你可以轻松适应的例子。

Note that the ErrorContent is coming from (Validation.Errors).CurrentItem.ErrorContent as opposed to Errors[0]. Although both will work, the latter will litter your output window with swallowed exceptions as outlined here.

请注意,ErrorContent来自(Validation.Errors).CurrentItem.ErrorContent而不是Errors [0]。虽然两者都可以工作,但后者会在输出窗口中丢失吞噬异常,如此处所述。

<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
    <Setter Property="Margin" Value="0,0,16,0" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />

    <!--
    Error handling
    -->
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <TextBlock DockPanel.Dock="Right" Text=" *" 
                               Foreground="Red" FontWeight="Bold" FontSize="16" 
                               ToolTip="{Binding ElementName=placeholder, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Border BorderBrush="Red"  BorderThickness="1">
                        <AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
                    </Border>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="Background" Value="LightYellow"/>
        </Trigger>
    </Style.Triggers>
</Style>