MVVM中的UI绑定验证

时间:2012-11-28 02:02:24

标签: wpf mvvm validation

我正在使用DataTemplate将一些代码转换为更合适的MVVM实现,并且遇到某些类型的UI验证问题。

我在View模型中验证没有问题 - IDataErrorInfo已经实现,一切都很好。我遇到的问题是UI绑定错误,它们可能会将TextBox中的字母绑定到int。

以前,我用过:

System.Windows.Controls.Validation.AddErrorHandler(userControl, handler) 

...并保留添加和删除的错误计数,以了解所有表单的数据是否正常。

但是现在我正在做MVVM,我没有访问userControl来设置这个处理程序。所以我真的没有一个钩子来开始这个。

是否有某种全局DataTemplateApplied事件处理程序,我可以执行以下操作:

void OnDataTemplateApplied(object data, Control template)
{
  if (data is MyViewModelBase)
  {
    Validation.AddErrorHandler(template, handler);
  }
}

或者,也许我可以在引导程序中为外部Shell窗口调用AddErrorHandler一次,然后每次触发事件时都会弄清楚哪个ViewModel正在为该特定控件供电?

我知道有些人喜欢在VM中创建所有VM字段字符串并进行大量的类型转换 - 由于各种原因,这对我们的系统来说并不现实。

1 个答案:

答案 0 :(得分:3)

您可能对此答案感兴趣:https://stackoverflow.com/a/13335971/1094526 主要思想正是你所说的(订阅错误处理程序)。据我了解,问题是你无法从ViewModel访问控件,但是解决起来并不困难

在我正在工作的项目中,我从ViewModel中暴露了两种方法:AddUIError和RemoveUIError。我在我的View中创建了一个事件处理程序,然后我将DataContext转换为我的ViewModel类型,并根据发生的情况调用AddUIError或RemoveUIError。 我正在使用DataTemplates将View与ViewModel相关联,因此在应用模板时,DataContext会自动设置为ViewModel。如果需要,可以将ViewModel存储在私有字段中(在视图中),并在每次DataContext更改时更新引用(存在DataContextChanged事件)

如果这将在多个ViewModel中完成,您可以将两个方法(AddUIError和RemoveUIError)放在类似ViewModelBase的类中,并将ValidationError事件处理移动到一个Behavior并在每个视图中使用它。


有关行为部分的更多信息: Behavior类是Expression Blend SDK的一部分,因此如果你想这样做,你将需要它。

例如,行为对于将一些常用功能附加到许多组件而不创建派生类很有用。

首先,我们需要在名为ViewModelBase的类中定义AddUIError和RemoveUIError(当然,这是所有其他ViewModel的基类):

class ViewModelBase {
   public void AddUIError(...) {/* Details ommitted */ }
   public void RemoveUIError(...) {/* Details ommitted */ }
}

然后,通过子类化Behavior创建一个Behavior。我们使用FrameworkElement作为模板参数,因此可以将此行为附加到任何FrameworkElement(或派生类)实例:

class NotifyDataErrorsBehavior : Behavior<FrameworkElement>
{
    // Called when the the Behavior is attached
    protected override void OnAttached()
    {
        base.OnAttached();
        // Initialize the handler for the Validation Error Event
        _handler = new RoutedEventHandler(OnValidationRaised);
        // Add the handler to the event from the element which is attaching this behavior
        AssociatedObject.AddHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        // Remove the event handler from the associated object
        AssociatedObject.RemoveHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
    }

    private RoutedEventHandler _handler = null;


    private void OnValidationRaised(object sender, RoutedEventArgs e)
    {
        var args = (System.Windows.Controls.ValidationErrorEventArgs)e;

        ViewModelBase viewModel = AssociatedObject.DataContext as ViewModelBase;
        if (viewModel != null)
        {
            // You can add only Exception validation errors if you want..

            if (args.Action == ValidationErrorEventAction.Added)
                viewModel.AddUIValidationError(...);
            else if (args.Action == ValidationErrorEventAction.Removed)
                viewModel.RemoveUIValidationError(...);
            else
                throw new NotSupportedException("ValidationErrorEventAction has changed");
        }
    }
}

最后只需在XAML中使用它: 1.添加对NotifyDataErrorsBehavior所在的命名空间的引用,以及对System.Windows.Interactivity命名空间的引用(来自Expression Blend SDK):

<UserControl 
        ...
        xmlns:behavior="clr-namespace:MyApp.Behaviors"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        ...
>

2。添加行为(与UserControl的内容处于同一级别:

<i:Interaction.Behaviors>
    <behavior:NotifyDataErrorsBehavior/>
</i:Interaction.Behaviors>

前:

<UserControl 
        ...
        xmlns:behavior="clr-namespace:MyApp.Behaviors"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        ...
>
    <i:Interaction.Behaviors>
       <behavior:NotifyDataErrorsBehavior/>
    </i:Interaction.Behaviors>
    <Grid>
       ...
    </Grid>
</UserControl>