用户控件上的IDataErrorInfo验证问题

时间:2014-01-23 22:26:00

标签: .net wpf binding wpf-controls idataerrorinfo

我有一个实现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
}

0 个答案:

没有答案