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>