65.9K
CodeProject 正在变化。 阅读更多。
Home

自主验证:扩展 IDataErrorInfo 以使验证更简单& 更健壮

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.14/5 (3投票s)

2011 年 7 月 1 日

CPOL

10分钟阅读

viewsIcon

22753

downloadIcon

511

如何仅用每个视图一行 XAML 让所有控件自行连接进行验证

引言

IDataErrorInfo 改进自定义验证类编写技术的一种方式是,它减少了启用验证所需的自定义 XAML 代码量。然而,IDataErrorInfo 在自动关联绑定与索引错误信息方面的理念本可以走得更远。

使用 IDataErrorInfo,在各种验证绑定之间复制一个代码片段 (ValidatesOnDataErrors ="True") 很简单,但您确实需要确保该片段位于每个需要验证的控件的一个绑定上。完成几次后,通常很容易找出每个已验证控件上哪个绑定需要此代码片段,但您仍然必须找出每个控件上正确的绑定。如果您想验证像 Label 这样的通常没有与视图模型上已验证属性进行任何绑定的东西,您仍然需要编写一些 XAML。

在一个理想的世界中,我们希望我们的绑定能够自动与 IDataErrorInfo 中的正确错误信息相关联,而无需任何按控件的 XAML。同样,如果我们正在验证 Labels——它们通常不直接绑定到决定其验证状态的属性——我们希望它们能够从作为其目标的控件中推断出它们的验证状态,也无需 XAML。简而言之,我们希望控件能够自主地进行自我验证,而无需视图作者考虑它们的哪些绑定需要验证。

有了这样的系统,视图作者将基本可以忘记验证的存在,除非他们正在做一些不寻常的事情,比如验证 Border。只要视图模型实现了 IDataErrorInfo,并且控件从视图模型上的正确属性获取数据,验证就会自动发生。

嗯,使用 IDataErrorInfo,我们无法完全做到。但是通过使用一个新的派生接口——IAutonomousErrorInfo——稍微扩展 IDataErrorInfo,我们可以做到。

本文及随附的示例应用程序展示了如何基于 WPF 验证扩展点构建一个具有这些特征的验证系统。

工作原理:概述

IDataErrorInfo 使用的基本方法很好:将验证结果存储在一个对象中,该对象通过视图模型属性名称进行索引,以产生与每个属性相关的错误。我们将通过一个名为 IAutonomousErrorInfo 的派生接口来扩展此接口,但保持其底层原理不变。

我们将改变的是,不再需要视图作者知道哪些控件绑定应该检查此接口,验证系统将为我们找出答案。

实现这一点的关键在于,屏幕上的控件与视图模型中应决定控件验证状态的属性之间的映射,已经通过控件上的绑定与每个控件类“用户输入的内容”的属性知识的组合,**隐式**地定义了。

第一组信息——控件的哪些属性与视图模型的哪些属性相关联——可以通过遍历视图的逻辑树并查看每个控件上的绑定来通过编程方式访问。

第二组信息——每个控件的哪个属性的绑定源应该被检查以确定该控件的有效性——主要由控件自身的类属性提供。如果一个控件定义了 `ContentProperty` 属性,那么 99% 的情况下,内容属性绑定的视图模型上的源属性就是我们要查找 `IAutonomousErrorInfo` 以验证该控件的属性。如果我们自己创建一个控件类并且它不打算有 `ContentProperty` 属性,我们可以使用自定义属性(我将其命名为 `ValidationProperty` 属性)来指定类似的信息。对于 WPF 定义的极少数我们可能想要验证但 WPF 没有给出 `ContentProperty` 的控件类型,我们可以在属性查找代码中为每个类硬编码要使用的属性。例如,如果控件派生自 `Selector`(`ComboBox`、`ListBox` 和 `TabControl`),则相关属性是“`SelectedValue`”。在极少数情况下,我们想为单个控件覆盖通常的属性,以便验证基于其他属性,我们提供一个附加属性,可以在 XAML 中为该控件指定。(我称之为 `AutonomousValidation.ValueToValidate`)。

结合这两组信息,我们可以创建一个系统,利用控件和视图模型属性之间的这种隐式关联来自主执行验证。这个系统,我称之为“验证扫描器”,以以下方式运行:

  1. 每当视图模型关联的错误集发生变化时(例如,用户刚刚点击“保存”,并且验证逻辑——无论是在业务层还是在服务器上——刚刚返回结果),它就会被调用。
  2. 它遍历视图的逻辑树,并对每个控件执行以下操作:
    1. 确定控件的哪个属性应决定控件的验证状态。
    2. 检查该属性上的绑定,从而确定视图模型上与该绑定交换数据的属性名称。
    3. 检查新返回的错误集,以确定视图模型上的该属性是否关联了任何错误。
    4. 相应地设置控件的验证状态。

工作原理:详细信息

为了避免需要在视图中添加代码,整个自主验证系统(除了视图模型上的 IAutonomousErrorInfo 接口)都作为附加行为实现。

具体来说,我们在每个视图的顶部设置一个附加属性,我们希望为该视图启用自主验证:

xmlns : validation ="clr- na mespace:Autonomous Validation " 
validation : AutonomousValidation.Errors ="{ Binding }" 

这将我们的附加属性(`AutonomousValidation.Errors`),类型为 `IAutonomousErrorInfo`,绑定到实现此接口的视图模型。如果您愿意,您可以让视图模型的单独属性实现 `IAutonomousErrorInfo`,并绑定到该属性。我在示例中选择直接在视图模型上实现 `IAutonomousErrorInfo`,以便 `IDataErrorInfo` 的用户更熟悉它。

Errors 附加属性的更改处理程序为 IAutonomousErrorInfoErrorInfoChanged 事件添加了一个处理程序。这给了我们一个处理程序,每当视图模型上的错误内容发生变化时都会被调用——这正是我们更新控件验证状态所需要的。此更改处理程序启动了上述验证扫描器的一次运行。

关于这一点,一个很棒的地方是,我们不需要将此附加属性添加到我们设置附加属性的视图的任何子视图中——子视图是父视图逻辑树的一部分,因此它们将自动被扫描和更新。

除了连接附加行为之外,`AutonomousValidation.Errors` 的更改处理程序还执行另一个重要功能——它以编程方式向视图添加一个额外的附加属性,即 `ValidationScanner` 属性。除了在验证错误列表更改时运行验证扫描之外,`ValidationScanner` 还存储了上次扫描无效的属性列表,以便在 `IAutonomousErrorInfo` 中的新错误列表中**不**包含这些属性名称时,可以将这些属性恢复到其有效状态。

当验证扫描器发现某个绑定应被标记为具有无效源数据时(请记住,WPF 验证系统本质上是一个使绑定而非控件失效的系统),它会通过向绑定添加 `ValidationRule` 来使绑定失效。具体来说,它添加了一个 `ValidationFailureRule` 类型的验证规则,顾名思义,该规则总是失败的。如果源数据的问题得到纠正,验证扫描器将删除此验证规则,并且绑定再次变得有效。

验证扫描器做的另一件事是处理 Labels 的特殊情况。使用 IDataErrorInfo,连接 Labels 的验证需要针对每个 Label 使用笨重的 XAML。使用自主验证系统,Label 的验证状态由 Label 目标的验证状态决定,这通常是我们想要的。

您可能已经注意到,本文档中“通常”或“几乎总是”这些词语出现的频率相当高。这是因为,从根本上说,自主验证系统通过让验证扫描器使用类级别的知识来判断哪些属性**可能**决定哪些控件的验证状态,从而减少了在每个视图中实现验证所需的工作量。这意味着,**通常**,您不需要编写任何按控件的 XAML 来启用验证。但是任何此类系统都需要一种方法来处理意外情况。有三种方法可以处理特殊情况:

  1. 提供了一个名为 `AutonomousValidation.ValueToValidate` 的附加属性,以便您在必要时可以在 XAML 中指定控件应基于某个属性进行验证,而不是通常为该控件类假定的属性。(对于像 `Border` 这样没有常规验证属性的类,这就是您指定视图模型上要检查的属性的方式。通过将此值设置为 `null`,您还可以告诉系统完全不验证该控件。)
  2. 如果您想使用标准的 WPF 机制来验证特定的绑定——ValidatesOnDataErrorsValidatesOnExceptionsValidationRule 对象——只需继续使用标准的 WPF 技术。验证扫描器将自动绕过任何使用这些标准技术的控件。
  3. 在极少数情况下,控件的有效性由视图模型的某些复杂特性决定,而不是由单个视图模型属性的有效性决定(例如,选中了过多的复选框),您需要在视图模型上创建一个附加属性,该属性可以绑定到 `AutonomousValidation.ValueToValidate`。这个附加属性(我建议名称以“`Validity`”结尾)不需要设置其值,也不需要支持更改通知——它只需要存在,以便 `IAutonomousErrorInfo` 可以声明它有错误,然后这些错误可以传递给关心它们的控件。

如您所见,有几个不同的级别可以指定应该检查哪个属性来确定特定控件的有效性。以下是优先级顺序:

  1. 在控件的任何绑定上使用旧式 WPF 验证:ValidatesOnDataErrorsValidatesOnExceptionsValidationRule 对象。
  2. 在控件的 XAML 中设置 AutonomousValidation.ValueToValidate 附加属性。
  3. 在控件类上指定 ValidationPropertyAttribute
  4. 在控件类上指定 ContentPropertyAttribute
  5. 对不具备 Content 属性的框架提供控件类进行硬编码异常处理。

IAutonomousErrorInfo 接口

以下是您的视图模型要实现才能利用自主验证的接口:

public interface IAutonomousErrorInfo : IDataErrorInfo
    {
        /// <summary>
        /// Are there any errors?     
        /// </summary>
        bool HasErrors { get; }

        /// <summary>
        /// Returns an IEnumerable of all of the view model property names that 
        /// currently have errors associated with them.
        /// </summary>                 
        IEnumerable<string> PropertyNames { get; }

        /// <summary>
        /// An event that is raised whenever an error message is added or 
        /// removed, and whenever the entire collection of errors is replaced.
        /// </summary>
        event EventHandler ErrorInfoChanged;
    }

就是这样——两个新属性和一个事件。有关示例实现,请参阅示例程序中的 BaseViewModelWithAutonomousErrorInfo.cs

**变体**:实际上,您的视图模型不**需要**直接实现此接口——如果您愿意,您可以在一个单独的类(例如“`ErrorInfo`”)上实现此接口,修改您的视图模型以使用 `ErrorInfo` 类型的属性来存储其验证结果,并让视图中的 `AutonomousValidation.Errors` 属性绑定到 `ErrorInfo` 对象,而不是绑定到整个视图模型。

示例程序

与本文相关的示例程序提供了自主验证体系结构的完整实现,以及一个使用该体系结构提供验证的简单视图。提供了按钮,允许您在有效和无效状态之间切换各种控件。

示例视图定义在 SubView.xamlSubView.cs 中。

IAutonomousErrorInfo 的一个示例实现包含在 BaseViewModelWithAutonomousErrorInfo.cs 中。您自己的应用程序的适当实现可能会大不相同,但我建议您先查看示例。

MainWindowModel.cs 主要包含命令连接,允许用户在示例视图中验证和取消验证控件。

自主验证体系结构几乎所有的核心内容都在 AutonomousValidation.csValidationScanner.cs 中。

历史

  • 2011年6月30日:初始版本
© . All rights reserved.