Silverlight验证的红色轮廓一直停留在数据通过验证的元素周围,直到聚焦和模糊



我有一个类如下:

public class Guardian : ModelBase, IDataErrorInfo
    {
        internal Guardian()
        {
        }
        [Required]
        [StringLength(50)]
        [Display(Name = "Guardian's First Name")]
        public string FirstName
        {
            get { return GetValue(() => FirstName); }
            set { SetValue(() => FirstName, value); }
        }
        [Required]
        [StringLength(50)]
        [Display(Name = "Guardian's Last Name")]
        public string LastName
        {
            get { return GetValue(() => LastName); }
            set { SetValue(() => LastName, value); }
        }
        [USPhoneNumber]
        [Display(Name = "Home Phone Number")]
        public string HomePhone
        {
            get { return GetValue(() => HomePhone); }
            set { SetValue(() => HomePhone, value.NormalizeNANPPhoneNumber()); }
        }
        [USPhoneNumber]
        [Display(Name = "Personal Cell")]
        public string PersonalCell
        {
            get { return GetValue(() => PersonalCell); }
            set { SetValue(() => PersonalCell, value.NormalizeNANPPhoneNumber()); }
        }
        [Required]
        [StringLength(100)]
        [Display(Name = "Address")]
        public string Address1
        {
            get { return GetValue(() => Address1); }
            set { SetValue(() => Address1, value); }
        }
        [StringLength(100)]
        public string Address2
        {
            get { return GetValue(() => Address2); }
            set { SetValue(() => Address2, value); }
        }
        [Required]
        [StringLength(100)]
        public string City
        {
            get { return GetValue(() => City); }
            set { SetValue(() => City, value); }
        }
        [Required]
        [StringLength(100)]
        public string State
        {
            get { return GetValue(() => State); }
            set { SetValue(() => State, value); }
        }
        [Required]
        [StringLength(20)]
        [USPostalCode]
        [Display(Name = "ZIP Code")]
        public string Zip
        {
            get { return GetValue(() => Zip); }
            set { SetValue(() => Zip, value); }
        }
        [Required]
        [Display(Name = "Relationship to Children")]
        public FamilyRole Relationship
        {
            get { return GetValue(() => Relationship); }
            set { SetValue(() => Relationship, value); }
        }
        internal bool IsEmpty()
        {
            return
                string.IsNullOrWhiteSpace(FirstName)
                && string.IsNullOrWhiteSpace(LastName)
                && string.IsNullOrWhiteSpace(HomePhone)
                && string.IsNullOrWhiteSpace(PersonalCell)
                && string.IsNullOrWhiteSpace(Address1)
                && string.IsNullOrWhiteSpace(Address2)
                && string.IsNullOrWhiteSpace(City)
                && string.IsNullOrWhiteSpace(State)
                && string.IsNullOrWhiteSpace(Zip)
                && Relationship == null
                ;
        }
        /// <summary>
        /// Provides support for cross-cutting concerns without having to write
        /// an attribute in Silverlight.
        /// When time allows, convert to an Attribute. The code produced then
        /// can be reused in other projects.
        /// </summary>
        /// <param name="listToAddTo"></param>
        private void CustomValidation(List<ValidationResult> listToAddTo)
        {
            if (listToAddTo == null)
                throw new ArgumentNullException("listToAddTo");
            if (string.IsNullOrWhiteSpace(HomePhone) && string.IsNullOrWhiteSpace(PersonalCell))
                listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone" }));
        }
        #region IDataErrorInfo Members
        public string Error
        {
            get
            {
                List<ValidationResult> results = new List<ValidationResult>();
                this.IsValidObject(results);
                CustomValidation(results);
                if (results.Count > 0)
                    return results[0].ErrorMessage;
                else
                    return null;
            }
        }
        public string this[string columnName]
        {
            get
            {
                List<ValidationResult> results = new List<ValidationResult>();
                this.IsValidObject(results);
                CustomValidation(results);
                var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
                if (resultByColumn.Count > 0)
                    return resultByColumn[0].ErrorMessage;
                else
                    return null;
            }
        }
        #endregion
    }

我为这个类实现IDataErrorInfo。一切都很好,我的问题确实很烦人,但这个问题已经大到付账单的人说需要解决了。我有一个单独的void来执行额外的验证,它由IDataErrorInfo成员调用。它检查是否至少有一个电话号码被填写。

这个类的一个实例在我的模型上,叫做CurrentGuardian,这个模型是以下弹出窗口的DataContext:

<controls:ChildWindow xmlns:my="clr-namespace:Microsoft.Windows.Controls"  
                                        x:Class="Tracktion.Controls.CheckInWindows.AddGuardian"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                 xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
                 Width="575" Height="326" 
                 Title="Add Parent/Guardian" HasCloseButton="False" 
                                        xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<controls:ChildWindow.Resources>
    <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="FontFamily" Value="Segoe UI" />
        <Setter Property="FontSize" Value="12" />
    </Style>
</controls:ChildWindow.Resources>
<Grid x:Name="LayoutRoot" Margin="2">
    <Grid.RowDefinitions>
        <RowDefinition Height="44" />
        <RowDefinition Height="215*" />
        <RowDefinition Height="45" />
    </Grid.RowDefinitions>
    <TextBlock Height="23" Name="textBlock1" Text="Please fill out the form below. Fields marked with an asterisk are required." VerticalAlignment="Top" TextAlignment="Center" />
    <TextBlock Height="23" Margin="0,21,0,0" Name="textBlock2" Text="When done, click Add Another Guardian or Continue Adding Children below." VerticalAlignment="Top" TextAlignment="Center" />
    <sdk:Label Grid.Row="1" Height="22" HorizontalAlignment="Left" Margin="0,11,0,0" Name="label1" VerticalAlignment="Top" Width="142" Content="* Guardian's First Name:" />
    <sdk:Label Content="* Guardian's Last Name:" Height="22" HorizontalAlignment="Left" Margin="0,46,0,0" Name="label2" VerticalAlignment="Top" Width="142" Grid.Row="1" />
    <sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="0,81,0,0" Name="label3" VerticalAlignment="Top" Width="142" Content="* Home Phone:" />
    <sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="0,116,0,0" Name="label4" VerticalAlignment="Top" Width="120" Content="* Personal Cell:" />
    <sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="302,11,0,0" Name="label5" VerticalAlignment="Top" Width="76" Content="* Address:" />
    <sdk:Label Content="* What is your relationship to the child or children?" Height="23" HorizontalAlignment="Left" Margin="0,155,0,0" Name="label6" VerticalAlignment="Top" Width="360" Grid.Row="1" />
    <TextBox Text="{Binding Path=CurrentGuardian.FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,5,0,0" Name="textBox1" VerticalAlignment="Top" Width="135" />
    <TextBox Text="{Binding Path=CurrentGuardian.LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,40,0,0" Name="textBox2" VerticalAlignment="Top" Width="135" />
    <TextBox Text="{Binding Path=CurrentGuardian.HomePhone, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,75,0,0" Name="txtHomePhone" VerticalAlignment="Top" Width="135" LostFocus="PhoneNumber_LostFocus" />
    <TextBox Text="{Binding Path=CurrentGuardian.PersonalCell, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,110,0,0" Name="txtCellPhone" VerticalAlignment="Top" Width="135" LostFocus="PhoneNumber_LostFocus" />
    <my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Address1, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,5,0,0" x:Name="textBox5" VerticalAlignment="Top" Width="184" Watermark="Line 1" />
    <my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Address2, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,40,0,0" x:Name="textBox6" VerticalAlignment="Top" Width="184" Watermark="Line 2" />
    <my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.City, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,75,0,0" x:Name="textBox7" VerticalAlignment="Top" Width="184" Watermark="City" />
    <ComboBox ItemsSource="{Binding Path=States}" DisplayMemberPath="Abbreviation" SelectedValuePath="Abbreviation" SelectedValue="{Binding Path=CurrentGuardian.State, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,110,0,0" Name="comboBox1" VerticalAlignment="Top" Width="88" />
    <my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Zip, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="460,110,0,0" x:Name="textBox8" VerticalAlignment="Top" Width="90" Watermark="ZIP" />
    <ComboBox DisplayMemberPath="Name" Height="28" HorizontalAlignment="Left" ItemsSource="{Binding Path=Relationships}" Margin="302,149,0,0" Name="comboBox2" SelectedItem="{Binding Path=CurrentGuardian.Relationship, Mode=TwoWay, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="249" Grid.Row="1" />
    <Button Content="Cancel" Grid.Row="2" Height="37" HorizontalAlignment="Left" Margin="0,8,0,0" Name="btnCancel" VerticalAlignment="Top" Width="93" Style="{StaticResource RedButton}" Click="btnCancel_Click" />
    <StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
        <Button Content="Add Another Guardian" Height="37" Margin="5,8,0,0" Name="btnAddGuardian" VerticalAlignment="Top" Style="{StaticResource OrangeButton}" HorizontalAlignment="Right" Width="159" Click="btnAddGuardian_Click" />
        <Button Content="Continue" Height="37" Margin="5,8,0,0" Name="btnContinue" VerticalAlignment="Top" Style="{StaticResource GreenButton}" HorizontalAlignment="Right" Padding="20,0" Click="btnContinue_Click" />
        <!-- TODO: Visibility set when accessing this screen through check in screen. -->
        <Button Content="Check In" Margin="5,8,0,0" Name="btnCheckIn" Visibility="Collapsed" Style="{StaticResource GreenButton}" Click="btnCheckIn_Click" />
    </StackPanel>
</Grid>

一个电话号码。您可以同时输入,但至少需要输入一个。当表单绑定到空CurrentGuardian时,第一个电话号码字段Home phone将以红色突出显示。当两个数字都没有数据时,对焦和模糊后它保持红色。如果我输入一个电话号码,这个字段就会变成黑色。删除该号码将变为红色。到目前为止,一切都很好——这是预期的行为。现在,如果我没有为家庭电话输入号码,但随后为个人手机输入电话号码,当我关闭个人手机时,家庭电话号码将保持红色突出显示,直到我选择它。一旦我点击它,红色的轮廓就消失了。我如何使字段验证?我目前在两个字段的模糊事件中有以下代码:

    private void PhoneNumber_LostFocus(object sender, RoutedEventArgs e)
    {
        KioskCheckIn2 model = (KioskCheckIn2)this.DataContext;
        model.CurrentGuardian.IsValidObject(); // revalidate
        txtHomePhone.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        txtCellPhone.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }

我有ValidationResult用于检查两个数字,仅返回HomePhone字段作为违规控件,因为我无法弄清楚如何强制重新验证控件,因此两个红色轮廓将消失。

提前感谢!

尝试在ChildWindow中使用DataForm 而不是。这是已知的bug。您也可以尝试应用这些修复程序。也许Silverlight 5的情况有所改善,我还没有检查过。

一般来说,DataForm控制在工具包的"预览"质量波段,ChildWindow也不是完美的,所以在某些情况下你可以预期bug。您也有源代码来进行进一步的修复。;)

实现INotifyDataErrorInfo,而不是IDataErrorInfo,似乎做到了这一点。IDataErrorInfo很好,但缺乏让UI知道当前属性之外的其他属性何时发生变化的能力。我试着允许两个实现,但它有点奇怪,所以我改变了它,所以它只实现了INotifyDataErrorInfo。

    public class Guardian : ModelBase, /*IDataErrorInfo,*/ INotifyDataErrorInfo
    {
        internal Guardian()
        {
        }
        [Required]
        [StringLength(50)]
        [Display(Name = "Guardian's First Name")]
        public string FirstName
        {
            get { return GetValue(() => FirstName); }
            set { SetValue(() => FirstName, value); }
        }
        [Required]
        [StringLength(50)]
        [Display(Name = "Guardian's Last Name")]
        public string LastName
        {
            get { return GetValue(() => LastName); }
            set { SetValue(() => LastName, value); }
        }
        [USPhoneNumber]
        [Display(Name = "Home Phone Number")]
        public string HomePhone
        {
            get { return GetValue(() => HomePhone); }
            set { SetValue(() => HomePhone, value.NormalizeNANPPhoneNumber()); }
        }
        [USPhoneNumber]
        [Display(Name = "Personal Cell")]
        public string PersonalCell
        {
            get { return GetValue(() => PersonalCell); }
            set { SetValue(() => PersonalCell, value.NormalizeNANPPhoneNumber()); }
        }
        [Required]
        [StringLength(100)]
        [Display(Name = "Address")]
        public string Address1
        {
            get { return GetValue(() => Address1); }
            set { SetValue(() => Address1, value); }
        }
        [StringLength(100)]
        public string Address2
        {
            get { return GetValue(() => Address2); }
            set { SetValue(() => Address2, value); }
        }
        [Required]
        [StringLength(100)]
        public string City
        {
            get { return GetValue(() => City); }
            set { SetValue(() => City, value); }
        }
        [Required]
        [StringLength(100)]
        public string State
        {
            get { return GetValue(() => State); }
            set { SetValue(() => State, value); }
        }
        [Required]
        [StringLength(20)]
        [USPostalCode]
        [Display(Name = "ZIP Code")]
        public string Zip
        {
            get { return GetValue(() => Zip); }
            set { SetValue(() => Zip, value); }
        }
        [Required]
        [Display(Name = "Relationship to Children")]
        public FamilyRole Relationship
        {
            get { return GetValue(() => Relationship); }
            set { SetValue(() => Relationship, value); }
        }
        internal bool IsEmpty()
        {
            return
                string.IsNullOrWhiteSpace(FirstName)
                && string.IsNullOrWhiteSpace(LastName)
                && string.IsNullOrWhiteSpace(HomePhone)
                && string.IsNullOrWhiteSpace(PersonalCell)
                && string.IsNullOrWhiteSpace(Address1)
                && string.IsNullOrWhiteSpace(Address2)
                && string.IsNullOrWhiteSpace(City)
                && string.IsNullOrWhiteSpace(State)
                && string.IsNullOrWhiteSpace(Zip)
                && Relationship == null
                ;
        }
        protected override void PropertyHasChanged(string propertyName)
        {
            base.PropertyHasChanged(propertyName);
            if (ErrorsChanged != null)
                this.GetType().GetProperties().ToList().ForEach(p => ErrorsChanged(this, new DataErrorsChangedEventArgs(p.Name)));
        }
        /// <summary>
        /// Provides support for cross-cutting concerns without having to write
        /// an attribute in Silverlight.
        /// When time allows, convert to an Attribute. The code produced then
        /// can be reused in other projects.
        /// </summary>
        /// <param name="listToAddTo"></param>
        private void CustomValidation(List<ValidationResult> listToAddTo)
        {
            if (listToAddTo == null)
                throw new ArgumentNullException("listToAddTo");
            if (string.IsNullOrWhiteSpace(HomePhone) && string.IsNullOrWhiteSpace(PersonalCell))
                listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone", "PersonalCell" }));
        }
        List<ValidationResult> getErrorList()
        {
            List<ValidationResult> results = new List<ValidationResult>();
            this.IsValidObject(results);
            CustomValidation(results);
            return results;
        }
        /*
        #region IDataErrorInfo Members
        public string Error
        {
            get
            {
                List<ValidationResult> results = getErrorList();
                if (results.Count > 0)
                    return results[0].ErrorMessage;
                else
                    return null;
            }
        }
        public string this[string columnName]
        {
            get
            {
                List<ValidationResult> results = getErrorList();
                var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
                if (resultByColumn.Count > 0)
                    return resultByColumn[0].ErrorMessage;
                else
                    return null;
            }
        }
        #endregion
         */
        #region INotifyDataErrorInfo Members
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            List<ValidationResult> results = getErrorList();
            return results.Where(e => e.MemberNames.Contains(propertyName));
        }
        public bool HasErrors
        {
            get 
            {
                List<ValidationResult> results = getErrorList();
                return results.Count > 0;
            }
        }
        #endregion
    }

现在,当我第一次进入屏幕时,两个电话字段都突出显示。在一个字段中输入有效的电话号码使两个电话号码字段都通过验证,并且每个字段周围的红线消失。注意,我把第二个字段放回CustomValidation

下面
listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone", "PersonalCell" }));

…这样,如果输入格式不正确,两个属性都可以在它们周围显示红线。

下面是我为IDataErrorInfo和INotifyDataErrorInfo处理验证的一些代码。你将不得不重新工作它一点,以适应你的基类(如果你有一个),但我希望这有助于一些人:

namespace CLARIA.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    /// <summary>
    /// Builds upon ModelBase with built-in validation.
    /// </summary>
    public abstract class ValidatableModelBase : ModelBase,
    #if SILVERLIGHT
        INotifyDataErrorInfo
    #else
        IDataErrorInfo
    #endif
    {
        private List<ValidationResult> GetErrorList()
        {
            List<ValidationResult> results = new List<ValidationResult>();
            this.IsValidObject(results);
            CustomValidation(results);
            return results;
        }
        /// <summary>
        /// Allows the derived class to override and add custom validation.
        /// The validation results generated from this method should be added
        /// to the collection <see cref="addResultsToThisList"/>.
        /// </summary>
        /// <param name="addResultsToThisList"></param>
        protected virtual void CustomValidation(List<ValidationResult> addResultsToThisList) {}
    #if SILVERLIGHT
        #region INotifyDataErrorInfo Members
        protected override void PropertyHasChanged(string propertyName)
        {
            base.PropertyHasChanged(propertyName);
            // Force re-validation of every property.
            if (ErrorsChanged != null)
                this.GetType().GetProperties().ToList().ForEach(p => ErrorsChanged(this, new DataErrorsChangedEventArgs(p.Name)));
        }
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            List<ValidationResult> results = GetErrorList();
            return results.Where(e => e.MemberNames.Contains(propertyName));
        }
        public bool HasErrors
        {
            get
            {
                List<ValidationResult> results = GetErrorList();
                return results.Count > 0;
            }
        }
        #endregion
    #else
        #region IDataErrorInfo Members
        public string Error
        {
            get
            {
                List<ValidationResult> results = GetErrorList();
                if (results.Count > 0)
                    return results[0].ErrorMessage;
                else
                    return null;
            }
        }
        public string this[string columnName]
        {
            get
            {
                List<ValidationResult> results = GetErrorList();
                var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
                if (resultByColumn.Count > 0)
                    return resultByColumn[0].ErrorMessage;
                else
                    return null;
            }
        }
        #endregion
    #endif

    }
    }

最新更新