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

Windows Presentation Foundation 的错误提供程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.14/5 (11投票s)

2006 年 6 月 19 日

6分钟阅读

viewsIcon

86100

downloadIcon

1581

一个用于 Windows Presentation Foundation 的 ErrorProvider 实现。

Sample Image - wpfErrorProvider.png

引言

在过去的几个月里,我一直在开发一个名为 Trial Balance 的共享源会计应用程序。Trial Balance 是我的一个个人项目,旨在演示我认为开发人员应该如何使用 Windows Presentation Foundation 创建富客户端应用程序。

在开发 Trial Balance 的新 UI 时,我最近遇到的一个障碍是缺少一个类似于 Windows Forms 中存在的 ErrorProvider 控件。

在 Windows Forms 中,如果您有一组控件(例如,文本框)绑定到给定数据源,您可以将一个 ErrorProvider 组件拖放到窗体上,并将其 DataSource 设置为文本框使用的相同数据源。然后,ErrorProvider 将自动显示对象上的任何错误,而无需在 UI 上编写验证代码。

在本文中,我将演示我的 ErrorProvider 版本,它是专门为 Windows Presentation Foundation 编写的。我之所以发布这篇文章,是因为我预计很多人都会想知道如何模拟这种行为。我还实现了 策略模式 来显示错误,以使提供程序尽可能可重用。

我现在想指出,这远未完成,应被视为“概念验证”。请将任何问题或建议作为本文的评论报告。

不过,在开始之前,我要感谢 Mike Brown,他来自 MSDN WPF 论坛,是他向我展示了 WPF LogicalTreeHelper 类的用法。这个类使得递归遍历窗口上 WPF 元素的层次结构变得容易。谢谢 Mike!

使用 Paul 的 WPF ErrorProvider

这是一个使用 ErrorProvider 的非常基本的 XAML 示例

<Window x:Class="PaulStovell.Samples.WpfValidation.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation" 
    Title="WpfErrorProvider" Height="300" Width="300"
    >
    <StackPanel>
        <validation:ErrorProvider x:Name="_errorProvider" />


        <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock>Name:</TextBlock>
            <TextBox  Text="{Binding Path=Name}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock>Description:</TextBlock>
            <TextBox  Text="{Binding Path=Description}" />
        </StackPanel>
        </StackPanel>
    </StackPanel>
</Window>

ErrorProvider 本身是一个 FrameworkElement,因此它可以在您的 XAML 中使用。到目前为止,唯一的限制是 ErrorProvider 元素必须在框架控件堆栈中“靠下”。我将解释为什么靠下。

ErrorProvider 从其 DataContext 获取错误消息。这需要是一个实现 System.ComponentModelIDataErrorInfo 接口以及 INotifyPropertyChanged 的类。您可以查看随附的 Account.cs 类以查看这些的粗略实现,或者阅读我(更长的)委托和业务对象 文章以获取更深入的主题讨论。

默认情况下,ErrorProviderDataContext 将设置为其父级的数据上下文(或其父级的父级,或其父级的父级...),因此要使用它,您实际上只需要将其放置在窗口上的正确位置。但是,您也可以根据需要显式分配 DataContext 属性。

扩展错误提供程序

我上传的演示代码包含三个类。第一个是 ErrorProvider 类本身。第二个是名为 IErrorDisplayStrategy 的接口。这个类定义了您需要实现以创建自己的错误显示方式的几个方法

  • bool CanDisplayForElement(FrameworkElement element)
  • void DisplayError(FrameworkElement element, string errorMessage)
  • void ClearError(FrameworkElement element)

当您将 ErrorProvider 添加到窗体时,它会维护一个 IErrorDisplayStrategy 对象列表。当需要为给定的 WPF 控件显示错误时,它将查阅此列表,寻找一个适用于给定元素的策略。

我包含的第三个类是 TextBoxErrorDisplayStrategy。这是 IErrorDisplayStrategy 的一个实现,旨在处理 TextBox

public class TextBoxErrorDisplayStrategy : IErrorDisplayStrategy {
    private Dictionary<FrameworkElement, Brush> _savedBrushes;
    private Dictionary<FrameworkElement, string> _savedToolTips;
    private Color _errorBorderColor;
    private static readonly Color _errorBorderColorDefault = 
                   Color.FromRgb(0xFF, 0x42, 0x2F);
    
    public TextBoxErrorDisplayStrategy() {
        _savedBrushes = new Dictionary<FrameworkElement, Brush>();
        _savedToolTips = new Dictionary<FrameworkElement, string>();
        _errorBorderColor = _errorBorderColorDefault;
    }
    
    public Color ErrorBorderColor {
        get { return _errorBorderColor; }
        set { _errorBorderColor = value; }
    }
    
    public bool CanDisplayForElement(FrameworkElement element) {
        return element is TextBox;
    }
    
    public void DisplayError(FrameworkElement element, string errorMessage) {
        TextBox textBox = (TextBox)element;
    
        if (!_savedBrushes.ContainsKey(element)) {
            _savedBrushes.Add(element, 
               (Brush)textBox.GetValue(TextBox.BorderBrushProperty));
        }
        if (!_savedToolTips.ContainsKey(element)) {
            _savedToolTips.Add(element, 
              (string)textBox.GetValue(TextBox.ToolTipProperty));
        }
    
        textBox.SetValue(TextBox.BorderBrushProperty, 
                         new SolidColorBrush(_errorBorderColor));
        textBox.SetValue(TextBox.ToolTipProperty, errorMessage);
    }

    public void ClearError(FrameworkElement element) {
        TextBox textBox = (TextBox)element;
    
        if (_savedBrushes.ContainsKey(element)) {
            textBox.SetValue(TextBox.BorderBrushProperty, 
                             _savedBrushes[element]);
            _savedBrushes.Remove(element);
        }
        if (_savedToolTips.ContainsKey(element)) {
            textBox.SetValue(TextBox.ToolTipProperty, 
                             _savedToolTips[element]);
            _savedToolTips.Remove(element);
        }
    }
}

当被告知为 TextBox 显示错误时,它只是更改边框颜色并设置一个工具提示。这有点复杂,因为它需要维护一个更改列表,以便在清除错误时可以回滚这些更改。

使用策略模式意味着您可以以任何您喜欢的方式操作元素,并以任何您喜欢的方式恢复它。这是相对于标准 Windows Forms ErrorProvider 的一个巨大优势,后者只为您提供一个图标和工具提示。

ErrorProvider 类

在外部,ErrorProvider 暴露了这些方法

  • AddDisplayStrategy() - 调用此方法以使错误提供程序知道其他 IErrorDisplayStrategy 类。或者,您可以子类化 ErrorProvider 并覆盖 CreateDefaultDisplayStrategies 方法。
  • RemoveDisplayStrategy()
  • Validate() - 此方法检查窗体上的所有绑定控件。我将在下面详细讨论。
  • Clear() - 删除所有显示的错误。
  • GetFirstInvalidElement() - 这是 Windows Forms ErrorProvider 无法做到的。调用此方法只是获取页面上第一个显示错误的 FrameworkElement。此方法很有用,因为您可以简单地调用它,然后调用元素上的 Focus() 方法,以将用户的注意力引导到他们需要修复的下一个错误。

调用 Validate() 的工作方式如下

  1. ErrorProvider 递归地遍历其父控件上的每个 FrameworkElement,对其进行反射并查找 DependancyProperties
  2. 当它找到一个依赖属性时,它会查找它可能具有的任何数据绑定。如果它有一个,并且元素的数据上下文与 ErrorProvider 的数据上下文相同,它然后对所有已知的 IErrorDisplayStrategies 调用 ClearError() 方法。这意味着,它首先清除所有错误。
  3. 它会记住在清除错误时遇到的绑定列表。由于每个绑定都有一个指向属性的 Path,它将该 Path 用作在绑定对象(在本例中为我们的数据上下文)上实现的 IDataErrorInfo 索引器的参数。这会返回一个错误字符串。
  4. 它知道绑定所属的框架元素,因此它会循环遍历已知的 IErrorDisplayStrategies,寻找一个与它需要的框架元素类型匹配的策略。当它找到一个时,它会告诉它显示错误。

已知问题、错误和抱怨

再次强调,这段代码尚未完成。它几乎没有经过测试,并且确实存在问题。以下是我已经知道的一些问题

  1. 速度慢。如果一个属性更改,并且您有 10 个绑定属性,则所有 10 个都将重新检查。我可以使用一些奇怪的缓存来尝试减少这种情况,但我会在实际感觉到性能下降时再担心这个问题。我相信 Windows Forms ErrorProvider 的工作方式相同。
  2. 速度慢。对每个框架元素的递归遍历不好。同样,我可以在第一次时缓存它们,但如果您动态地向窗口添加控件,您可能会得到奇怪的行为。
  3. 未检查线程安全。
  4. 未优化。
  5. 它期望您使用数据绑定,并且没有办法显式设置错误。我可能会在需要时添加它。
  6. 速度慢。

如果有人想出更好的方法来实现这些功能,我希望看到它。我可能更希望您能自己编写解决方案并发布博客,或者写一篇 CodeProject 文章,而不是给我发送许多小的代码片段来集成。如果您创建替代方案,或改进了方法,我将乐意在这篇文章中添加链接。

感谢阅读,希望您觉得这个演示有用!

© . All rights reserved.