我有一个实现IDataErrorInfo的实体类。实体类有两个属性,生效日期和终止日期。我已经使用DataAnnotations来生成所需的生效日期,以及一些自定义DataAnnotations以确保生效日期在终止日期之前,反之亦然。
在大多数情况下,验证工作正常。如果我在生效日期没有日期,我会得到一个红色边框和一个工具提示,要求生效日期。如果我在生效日期之前设置终止日期,则终止日期控件会按预期显示ErrorTemplate。
这是问题所在。如果我的生效日期设置为在终止日期之后,生效日期将按预期显示ErrorTemplate。但是,如果我将终止日期更改为生效日期之后,我可以在调试器中看到生效日期的BindingExpression Validation附加属性不再有错误,但ErrorTemplate不会消失并显示。
实际上,如果我更新终止日期或生效日期,我希望它重新验证这两个字段。它似乎是这样做的,但是没有相应地更新显示模板。
我在控件上启用了WPF绑定诊断,当我将终止日期更新为应该使两个字段都有效的日期时,这是我看到的有效日期绑定信息。
System.Windows.Data Warning: 95 : BindingExpression (hash=3778436): Got PropertyChanged event from MyEntity (hash=-46241493)
System.Windows.Data Warning: 101 : BindingExpression (hash=3778436): GetValue at level 1 from MyEntity (hash=-46241493) using RuntimePropertyInfo(EffectiveDate): DateTime (hash=-1904007305)
System.Windows.Data Warning: 80 : BindingExpression (hash=3778436): TransferValue - got raw value DateTime (hash=-1904007305)
System.Windows.Data Warning: 89 : BindingExpression (hash=3778436): TransferValue - using final value DateTime (hash=-1904007305)
System.Windows.Data Warning: 91 : BindingExpression (hash=3778436): Update - DataErrorValidationRule (hash=29574219) failed
System.Windows.Data Warning: 95 : BindingExpression (hash=3778436): Got PropertyChanged event from MyEntity (hash=-46241493)
System.Windows.Data Warning: 101 : BindingExpression (hash=3778436): GetValue at level 1 from MyEntity (hash=-46241493) using RuntimePropertyInfo(EffectiveDate): DateTime (hash=-1904007305)
System.Windows.Data Warning: 80 : BindingExpression (hash=3778436): TransferValue - got raw value DateTime (hash=-1904007305)
System.Windows.Data Warning: 89 : BindingExpression (hash=3778436): TransferValue - using final value DateTime (hash=-1904007305)
System.Windows.Data Warning: 91 : BindingExpression (hash=3778436): Update - DataErrorValidationRule (hash=29574219) failed
我已经阅读了很多关于此的帖子和许多文章,但我尝试过的任何内容都没有效果。我很抱歉,如果之前有人问过这个问题,那么有很多关于类似问题的问题,但我不知道还有什么要搜索的,所以我想发布我的具体例子。最后说明,我已经看到了一些INotifyDataErrorInfo的提及,但我现在无法升级到.NET 4.5。
我的表单背后的代码
public partial class MyForm : Form
{
private static readonly DependencyProperty MyEntityTestProperty = DependencyProperty.Register("MyEntityTest", typeof(MyEntity), typeof(MyForm));
private MyEntity MyEntityTest { get { return GetValue(MyEntityTestProperty) as MyEntity; } set { SetValue(MyEntityTestProperty, value); } }
public MyForm()
{
InitializeComponent();
}
protected void OnLoaded(object sender, RoutedEventArgs e)
{
MyEntityTest = new MyEntity();
}
}
我的表单的XAML
<Form
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:z="clr-namespace:MyTest;assembly=MyTest"
mc:Ignorable="d"
x:Class="MyForm"
Loaded="OnLoaded"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Effective"/>
<z:DatePickerBox Name="EffectiveDatePickerBox" Grid.Row="0" Grid.Column="1" Date="{Binding MyEntityTest.EffectiveDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, diagnostics:PresentationTraceSources.TraceLevel=High}"/>
<Label Grid.Row="1" Grid.Column="0" Content="Termination"/>
<z:DatePickerBox Name="TerminationDatePickerBox" Grid.Row="1" Grid.Column="1" Date="{Binding MyEntityTest.TerminationDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
</Grid>
</Form>
后面的UserControl代码
public partial class DatePickerBox : IDataErrorInfo, INotifyPropertyChanged
{
public DatePickerBox()
{
InitializeComponent();
}
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date", typeof(DateTime?), typeof(DatePickerBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DateChangedCallBack));
public DateTime? Date
{
get
{
return GetValue(DateProperty) as DateTime?;
}
set
{
if (value.Equals(Date)) return;
SetValue(DateProperty, value);
FirePropertyChanged("Date");
}
}
private static void DateChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePickerBox dpb = (DatePickerBox)d;
dpb.FirePropertyChanged("Date");
}
private void MainBorder_MouseEnter(object sender, MouseEventArgs e)
{
VisualStateManager.GoToElementState(MainBorder, "MouseEnter", true);
}
private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
CalendarPopup.IsOpen = true;
}
private void MainBorder_MouseLeave(object sender, MouseEventArgs e)
{
VisualStateManager.GoToElementState(MainBorder, "MouseLeave", true);
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
if (columnName == "Date")
{
BindingExpression be = BindingOperations.GetBindingExpression(this, DateProperty);
if (be == null || be.ValidationError == null) return null;
return (string)be.ValidationError.ErrorContent;
}
return Validation.GetHasError(this) ? "DatePickerBox has Error" : null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl XAML
<UserControl x:Class="DatePickerBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:z="clr-namespace:MyTest">
<UserControl.Resources>
<z:DateTimeToStringConverter x:Key="DateTimeToStringConverter"/>
</UserControl.Resources>
<Validation.ErrorTemplate>
<!-- We want the Validation.ErrorTemplate to show up on the TextBox, not the entire DataBoxPicker UserControl -->
<ControlTemplate/>
</Validation.ErrorTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBox Name="DateTextBox" Margin="4,4,0,0" Date="{Binding Path=Date, RelativeSource={RelativeSource FindAncestor, AncestorType=z:DatePickerBox, AncestorLevel=1}, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
<Border Name="MainBorder" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="4,4,0,0" Background="Transparent" BorderBrush="Transparent" BorderThickness="1" MouseEnter="MainBorder_MouseEnter" MouseLeave="MainBorder_MouseLeave" MouseLeftButtonDown="MainBorder_MouseLeftButtonDown">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="MouseEnter">
<Storyboard>
<ColorAnimation To="DarkGray" Duration="0:0:00.1" Storyboard.TargetName="MainBorder" Storyboard.TargetProperty="BorderBrush.Color"/>
</Storyboard>
</VisualState>
<VisualState Name="MouseLeave"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ToggleButton Name="PopupToggleButton">
<Image Width="16" Height="16" HorizontalAlignment="Left" VerticalAlignment="Top" Source="..\Images\calendar_16x16.png"/>
</ToggleButton>
</Border>
<Popup x:Name="CalendarPopup" Margin="0,-1,0,0" IsOpen="{Binding Path=IsChecked, ElementName=PopupToggleButton, Mode=TwoWay}" PopupAnimation="Fade" StaysOpen="False">
<Calendar Margin="0,-1,0,0"
SelectedDate="{Binding Path=Date, RelativeSource={RelativeSource FindAncestor, AncestorType=z:DatePickerBox, AncestorLevel=1}, Mode=TwoWay, ValidatesOnDataErrors=True}"
Focusable="False"
DisplayDate="{Binding Path=Date, RelativeSource={RelativeSource FindAncestor, AncestorType=z:DatePickerBox, AncestorLevel=1}, Mode=OneWay, FallbackValue={x:Static sys:DateTime.Today}, TargetNullValue={x:Static sys:DateTime.Today}, ValidatesOnDataErrors=True}">
<Control.Triggers>
<EventTrigger RoutedEvent="Calendar.SelectedDatesChanged">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PopupToggleButton" Storyboard.TargetProperty="IsChecked">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"></DiscreteBooleanKeyFrame>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Control.Triggers>
</Calendar>
</Popup>
</StackPanel>
</UserControl>
我的App.xaml中的样式
因此定义了TextBox的默认样式,并且我在Validation.HasError上定义了一个触发器
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color A="255" R="255" G="220" B="220"/>
</SolidColorBrush.Color>
</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
我的实体类
public class MyEntity : : INotifyPropertyChanged, IDataErrorInfo
{
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[Required(ErrorMessage = "Effective Date is required")]
[DateCompare(CompareType.LessThenOrEqualTo, "TerminationDate", ErrorMessage = "Effective Date must be the same as or come before Termination Date")]
public virtual DateTime? EffectiveDate { get { return _effectiveDate; } set { _effectiveDate = value; FirePropertyChanged("EffectiveDate"); FirePropertyChanged("TerminationDate"); } }
protected DateTime? _effectiveDate;
[DateCompare(CompareType.GreatherThenOrEqualTo, "EffectiveDate", ErrorMessage = "Termination Date must be the same as or come after Effective Date")]
public virtual DateTime? TerminationDate { get { return _terminationDate; } set { _terminationDate = value; FirePropertyChanged("TerminationDate"); FirePropertyChanged("EffectiveDate"); } }
protected DateTime? _terminationDate;
#region // IDataErrorInfo Members
public string this[string propertyName]
{
get
{
var results = new List<ValidationResult>();
Validator.TryValidateProperty(GetType().GetProperty(propertyName).GetValue(this, null),
new ValidationContext(this, null, null) { MemberName = propertyName },
results);
return results.Count == 0 ? null : results.First().ErrorMessage;
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
#endregion // IDataErrorInfo Members
}