WPF 输入验证(2)
IDataErrorInfo 验证
IDataErrorInfo 接口需要实现者公开一个属性和一个索引器:
public interface IDataErrorInfo { string Error { get; } string this[string propertyName] { get; } }
Error 属性用于指示整个对象的错误,而索引器用于指示单个属性级别的错误。两者的工作原理相同:如果返回非 null 或非空字符串,则表示存在验证错误。此外,返回的字符串可用于向用户显示错误(我将在后面加以说明)。
当使用绑定到数据源对象上的各个属性的各个控件时,该接口的最重要部分就是索引器。只有在将对象显示在 DataGrid 或 BindingGroup 中的这种方案中,才使用 Error 属性。Error 属性用于指示行级别的错误,而索引器用于指示单元格级别的错误。
实现 IDataErrorInfo 具有一个很大的弊端:索引器的实现通常会导致较大的 switch-case 语句(对象中的每个属性名称都对应于一种情况),您必须基于字符串进行切换和匹配,并返回指示错误的字符串。而且,在对象上设置属性值之前,不会调用 IDataErrorInfo 的实现。如果其他对象订阅了该对象上的 INotifyPropertyChanged.PropertyChanged,则已通知这些对象发生了更改,它们可能已基于 IDataErrorInfo 实现要声明为无效的数据开始工作。如果这对于您的应用程序可能是个问题,则需要在对设置的值不满意时从属性 setter 引发异常。
IDataErrorInfo 的优点是,它可用于轻松地处理交叉耦合属性。例如,除了使用 ValidationRule 来验证 Inventory 字段的输入格式之外,还应记住这一要求:当 ActivityType 为 Install 时,必须填写 Inventory 字段。ValidationRule 本身无权访问数据绑定对象上的其他属性。它只是传递为 Binding 挂接的属性所设置的值。若要满足此要求,当设置了 ActivityType 属性时,您需要使验证在 Inventory 属性上发生,并在 ActivityType 设置为 Install 而 Inventory 的值为空时返回无效结果。
若要实现此目的,您需要使用 IDataErrorInfo,以便可在评估 Inventory 时检查 Inventory 和 ActivityType 属性,如下所示:
public string this[string propertyName] { get { return IsValid(propertyName); } } private string IsValid(string propertyName) { switch (propertyName) { ... case "Inventory": if (ActivityType != null && ActivityType.Name == "Install" && string.IsNullOrWhiteSpace(Inventory)) return "Inventory expended must be entered for installs"; break; }
此外,您需要让 Inventory Binding 在 ActivityType 属性发生更改时调用验证。通常,仅当 UI 中的该属性发生更改时,Binding 才查询 IDataErrorInfo 实现或调用 ValidationRules。在本例中,即使 Inventory 属性尚未更改但相关 ActivityType 已更改,我也需要触发 Binding 验证的重新评估。
可通过两种方式让 Inventory Binding 在 ActivityType 属性发生更改时进行刷新。第一种(也是较为简单)的方式是在设置 ActivityType 时为 Inventory 发布 PropertyChanged 事件:
ActivityType _ActivityType; public ActivityType ActivityType { get { return _ActivityType; } set { if (value != _ActivityType) { _ActivityType = value; PropertyChanged(this, new PropertyChangedEventArgs("ActivityType")); PropertyChanged(this, new PropertyChangedEventArgs("Inventory")); } } }
这会使 Binding 刷新并重新评估该 Binding 的验证。
第二种方式是在 ActivityType ComboBox 或其父元素之一的上面挂接 Binding.SourceUpdated 附加事件,并从该事件的代码隐藏处理程序来触发 Binding 刷新:
<ComboBox Name="activityTypeIdComboBox" Binding.SourceUpdated="OnPropertySet"... private void OnPropetySet(object sender, DataTransferEventArgs e) { if (activityTypeIdComboBox == e.TargetObject) { inventoryTextBox.GetBindingExpression( TextBox.TextProperty).UpdateSource(); } }
以编程方式调用 Binding 上的 UpdateSource 会使其将绑定目标元素中的当前值写入源属性,从而如同用户刚刚编辑过控件那样来触发验证链。
将 BindingGroup 用于交叉耦合属性
Microsoft .NET Framework 3.5 SP1 中新增了 BindingGroup 功能。BindingGroup 专门用于一次性评估一组绑定上的验证。例如,它允许用户填写整个窗体,并一直等到其按“提交”或“保存”按钮以评估窗体的验证规则,然后一次性呈现验证错误。在示例应用程序中,我要求必须至少提供一个 Customer、Objective 或 Reason。BindingGroup 也可以用于评估窗体的子集。
若要使用 BindingGroup,需要一组具有普通 Bindings 并共享公共上级元素的控件。在示例应用程序中,Customer ComboBox、Objective ComboBox 和 Reason TextBox 都位于同一个布局 Grid 中。BindingGroup 是 FrameworkElement 上的属性。它具有 ValidationRules 集合属性,可以使用一个或多个 ValidationRule 对象来填充该属性。下面的 XAML 演示示例应用程序的 BindingGroup 挂接:
<Grid>... <Grid.BindingGroup> <BindingGroup> <BindingGroup.ValidationRules> <local:CustomerObjectiveOrReasonValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True"/> </BindingGroup.ValidationRules> </BindingGroup> </Grid.BindingGroup> </Grid>
在此示例中,我向集合中添加了 CustomerObjectiveOrReasonValidationRule 的一个实例。使用 ValidationStep 属性,可以在某种程度上控制传递给规则的值。UpdatedValue 表示要使用写入到数据源对象的值(在写入该值后)。您还可以为 ValidationStep 选择值,从而可以使用用户的原始输入、应用了类型和值转换后的值或是“已提交的”值(这意味着实现 IEditableObject 接口以针对对象的属性做出事务性更改)。
ValidatesOnTargetUpdated 标志使得每次通过 Bindings 设置目标属性时都会对规则进行评估。这包括最初设置属性时的情况,因此在初始数据无效时,以及每当用户在属于 BindingGroup 的控件中更改值时,都将立刻获得验证错误指示。
挂接到 BindingGroup 的 ValidationRule 的工作方式与挂接到单个 Binding 的 ValidationRule 稍有不同。图 7 显示了挂接到上面代码示例中演示的 BindingGroup 的 ValidationRule。
图 7 BindingGroup 的 ValidationRule
public class CustomerObjectiveOrReasonValidationRule : ValidationRule { public override ValidationResult Validate( object value, CultureInfo cultureInfo) { BindingGroup bindingGroup = value as BindingGroup; if (bindingGroup == null) return new ValidationResult(false, "CustomerObjectiveOrReasonValidationRule should only be used with a BindingGroup"); if (bindingGroup.Items.Count == 1) { object item = bindingGroup.Items[0]; ActivityEditorViewModel viewModel = item as ActivityEditorViewModel; if (viewModel != null && viewModel.Activity != null && !viewModel.Activity.CustomerObjectiveOrReasonEntered()) return new ValidationResult(false, "You must enter one of Customer, Objective, or Reason to a valid entry"); } return ValidationResult.ValidResult; } }
在挂接到单个 Binding 的 ValidationRule 中,传入的值是来自一个数据源属性的单个值,该数据源属性设置为该 Binding 的 Path。对于 BindingGroup,传递给 ValidationRule 的值是 BindingGroup 本身。该值包含一个 Items 集合,该集合由包含元素的 DataContext(本例中为 Grid)进行填充。
对于示例应用程序,我使用 MVVM 模式,因此,视图的 DataContext 是 ViewModel 本身。Items 集合仅包含对 ViewModel 的单个引用。我可以从 ViewModel 来访问它上面的 Activity 属性。本例中 Activity 类所具有的验证方法可确定是否输入了至少一个 Customer、Objective 或 Reason,因此,我不必在 ValidationRule 中重复该逻辑。
与前面介绍的其他 ValidationRule 一样,如果您对传入的数据值满意,则可返回一个 ValidationResult.ValidResult。如果不满意,则可使用设置为 false 的有效标志和指示问题的字符串消息来构造新的 ValidationResult,该消息随后可以用于显示。
不过,设置 ValidatesOnTargetUpdated 标志并不足以让 ValidationRules 自动触发。BindingGroup 是围绕为整个控件组显式触发验证(通常是通过在窗体上按“提交”或“保存”按钮这类操作)的概念而设计的。在某些方案中,用户在认为编辑过程完成之前不希望被验证错误指示所打扰,因此 BindingGroup 在设计上考虑了此方法。
在示例应用程序中,我希望每当用户更改窗体中的内容时,将立即向其提供验证错误反馈。若要使用 BindingGroup 实现此目的,必须在属于该组的各个输入控件上挂接适当的更改事件,并使这些事件的事件处理程序触发 BindingGroup 的评估。在示例应用程序中,这意味着在 TextBox 上的两个 ComboBoxes 和 TextBox.TextChanged 事件上挂接 ComboBox.SelectionChanged 事件。所有这些事件都可以指向代码隐藏中的单个处理方法:
private void OnCommitBindingGroup( object sender, EventArgs e) { CrossCoupledPropsGrid.BindingGroup.CommitEdit(); }
请注意,对于验证显示,将在 BindingGroup 所在的 FrameworkElement(如示例应用程序中的 Grid)上显示默认红色边框,如图 4 所示。也可以使用 Validation.ValidationAdornerSite 和 Validation.ValidationAdornerSiteFor 附加属性来更改显示验证指示的位置。默认情况下,各个控件也会为其各自的验证错误显示红色边框。在示例应用程序中,我通过 Style 将 ErrorTemplate 设置为 null 以将这些边框关闭。
对于 .NET Framework 3.5 SP1 中的 BindingGroup,您可能会在初始窗体加载时遇到与正常显示验证错误有关的问题,即使在 ValidationRule 上设置了 ValidatesOnTargetUpdated 属性。我针对此问题找到的一个解决方法是“稍微修改”BindingGroup 中的绑定属性之一。在示例应用程序中,您可以在视图的 Loaded 事件中,在 TextBox 中最初呈现的任何文本结尾处添加和删除一个空格,如下所示:
string originalText = m_ProductTextBox.Text; m_ProductTextBox.Text += " "; m_ProductTextBox.Text = originalText;
这会使 BindingGroup ValidationRule 在所包含的 Binding 属性之一发生更改时触发,从而调用每个 Binding 的验证。此行为在 .NET Framework 4.0 中得到了修复,因此无需该解决方法便可获得验证错误的初始显示 — 只需在验证规则上将 ValidatesOnTargetUpdated 属性设置为 true。
本文来自博客园,作者:风痕天下,转载请注明原文链接:https://www.cnblogs.com/xuxiaorong/articles/3013399.html
浙公网安备 33010602011771号