我有一个类如下:
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
}
}