WPF MVVM验证DataGrid并禁用CommandButton

时间:2015-09-14 08:52:32

标签: c# wpf validation mvvm datagrid

我已经创建了一个附加的示例MVVM应用程序。

  • 我使用的是datagrid
  • 我有一个绑定命令的按钮
  • 我有一些自定义验证规则应用于某些单元格和文本框。

我想要实现的目标是:

  • 我喜欢在键入时进行验证(这已经在使用验证规则和UpdateSourceTrigger = PropertyChanged)。
  • 我想验证单个单元格/行(这也已经有效)

  • 我想做“表单”验证。例如。跨行验证,以验证数据网格的第一列中没有重复的字符串。

  • 如果有任何验证规则或者viewmodels表单验证有错误,我想禁用该命令。
  • 如果表单有效,我想启用该命令。

你会怎么做?我不知道如何在视图模型中实现表单验证。

我的第一个想法就是每次有任何变化时都会从后面的代码中调用viewmodel上的验证方法。但是这样做,我仍然不知道如何通知viewmodel有关视图验证规则中的验证错误(例如,如果有人将文本输入到ID列)。 viewmodel根本就不知道它并最终成功验证,只是因为错误的值永远不会达到它。好吧,我可以使用字符串并在viewmodel中进行整个转换 - 但我不喜欢这个想法,因为我想在WPF中使用转换器/验证器的全部功能。

有人已经做过类似的事吗?

https://www.dropbox.com/s/f3a1naewltbl9yp/DataGridValidationTest.zip?dl=0

2 个答案:

答案 0 :(得分:0)

我不相信可以使用DataGrid中的多个列来验证行。但是,正如您所提到的,您可以使用viewmodel来完成它。

您必须将DataGrid的行存储在ViewModel中(但我希望您已经这样做了)。您需要实施INotifyDataErrorInfo。此界面允许您在某些错误发生更改时通知视图。

然后,每次更改name属性时,验证是否存在重复项。

您的保存按钮应使用ICommand来调用保存操作。在CanExecute方法中,您可以检查实现HasErrors的对象的INotifyDataErrorInfo属性,并返回相应的boolean。这会相应地禁用该按钮。

答案 1 :(得分:0)

我们需要处理3种类型的错误。

  1. 当我们输入需要Int的String时,WPF的绑定引擎生成错误。 使用UpdateSourceExceptionFilter解决了这个问题。
  2. 自定义UI级别验证。 使用我们自己的接口和跟随通知模式,如INotifyPropertyChanged解决了这个问题。
  3. 自定义后端级别验证。 在ViewModel中处理PropertyChanged事件解决了这个问题。
  4. 逐个解决方案

    1. 当我们输入需要Int的String时,由WPF的Binding引擎生成错误。

          <TextBox VerticalAlignment="Stretch" VerticalContentAlignment="Center" Loaded="TextBox_Loaded">
            <TextBox.Text>
              <Binding Path="ID" UpdateSourceExceptionFilter="ReturnExceptionHandler" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True" ValidatesOnExceptions="True" >
                    <Binding.ValidationRules>
                       <CustomValidRule ValidationStep="ConvertedProposedValue"></CustomValidRule>
                    </Binding.ValidationRules>
              </Binding>
             </TextBox.Text>
            </TextBox>
      
    2. MainWindow.xaml.cs

      object ReturnExceptionHandler(object bindingExpression, Exception exception)
              {
                  vm.CanHello = false;
      
                  return "This is from the UpdateSourceExceptionFilterCallBack.";
             }
      
      1. 自定义UI级别验证
      2. 要启用Button正确响应,我们需要将4个东西粘在一起即可。 ViewModel,Button,ValidationRules和DataGrid的模板列的文本框。否则无法正确设置ViewModel.CanHello属性,从而使RelayCommand无用。 现在ValidationRules:CustomValidRule和NegValidRule没有粘贴到ViewModel。为了让他们通知ViewModel他们的验证结果,他们需要触发一些事件。 我们将使用使用InotifyPropertyChanged的WPF跟随的通知模式。 我们将为UI级别验证规则创建一个接口IViewModelUIRule,以与ViewModel进行交互。

        ViewModelUIRuleEvent.cs

        using System;
        
            namespace BusinessLogic
            {
                public interface IViewModelUIRule
                {
                    event ViewModelValidationHandler ValidationDone;
                }
        
                public delegate void ViewModelValidationHandler(object sender, ViewModelUIValidationEventArgs e);
        
                public class ViewModelUIValidationEventArgs : EventArgs
                {
                    public bool IsValid { get; set; }
        
                    public ViewModelUIValidationEventArgs(bool valid) { IsValid = valid; }
                }
            }
        

        我们的验证规则现在将实现此界面。

        public class CustomValidRule : ValidationRule, IViewModelUIRule
            {
        
                bool _isValid = true;
                public bool IsValid { get { return _isValid; } set { _isValid = value; } }
        
                public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
                {
        
                    int? a = value as int?;
                    ValidationResult result = null;
        
                    if (a.HasValue)
                    {
                        if (a.Value > 0 && a.Value < 10)
                        {
                            _isValid = true;
                            result = new ValidationResult(true, "");
                        }
                        else
                        {
                            _isValid = false;
                            result = new ValidationResult(false, "must be > 0 and < 10 ");
                        }
                    }
        
                    OnValidationDone();
        
                    return result;
                }
        
                private void OnValidationDone()
                {
                    if (ValidationDone != null)
                        ValidationDone(this, new ViewModelUIValidationEventArgs(_isValid));
                }
        
                public event ViewModelValidationHandler ValidationDone;
            }
        

        ///////////////////////////

            public class NegValidRule : ValidationRule, IViewModelUIRule
        {
            bool _isValid = true;
            public bool IsValid { get { return _isValid; } set { _isValid = value; } }
        
            ValidationResult result = null;
        
            public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
            {
                int? a = value as int?;
                if (a.HasValue)
                {
                    if (a.Value < 0)
                    {
                        _isValid = true;
                        result = new ValidationResult(true, "");
                    }
                    else
                    {
                        _isValid = false;
                        result = new ValidationResult(false, "must be negative ");
                    }
                }
        
                OnValidationDone();
        
                return result;
            }
        
            private void OnValidationDone()
            {
                if (ValidationDone != null)
                    ValidationDone(this, new ViewModelUIValidationEventArgs(_isValid));
            }
        
            public event ViewModelValidationHandler ValidationDone;
        }
        

        现在,我们需要更新ViewModel类以维护验证规则集合。并处理由我们的自定义验证规则触发的ValidationDone事件。

        namespace BusinessLogic
        {
            public class ViewModel : INotifyPropertyChanged
            {
                private ObservableCollection<ValidationRule> _rules;
                public ObservableCollection<ValidationRule> Rules { get { return _rules; } }
        
                public ViewModel()
                {
                    _rules = new ObservableCollection<ValidationRule>();
        
                    Rules.CollectionChanged += Rules_CollectionChanged;
        
                    MyCollection.CollectionChanged += MyCollection_CollectionChanged;            
        
                    MyCollection.Add(new Class1("Eins", 1));
                    MyCollection.Add(new Class1("Zwei", 2));
                    MyCollection.Add(new Class1("Drei", 3));
                }
        
                void Rules_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
                {
                    foreach (var v in e.NewItems)
                        ((IViewModelUIRule)v).ValidationDone += ViewModel_ValidationDone;
                }
        
                void ViewModel_ValidationDone(object sender, ViewModelUIValidationEventArgs e)
                {
                    canHello = e.IsValid;
                }
        
                void MyCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
                {
                    foreach (var v in e.NewItems)
                        ((Class1)v).PropertyChanged += ViewModel_PropertyChanged;
                }
        
                void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
                {            
                    // if all validations runs good here
                    // canHello = true;
                }
                ……
        

        现在我们已经添加了Rules集合,我们需要向它添加验证规则。为此,我们需要参考我们的验证规则。我们现在使用XAML添加这些规则,因此我们将使用TexBox的Loaded事件作为绑定到ID字段的TextBox来访问这些,就像这样,

        <TextBox VerticalAlignment="Stretch" VerticalContentAlignment="Center" Loaded="TextBox_Loaded">
                                    <TextBox.Text>
                                            <Binding Path="ID" UpdateSourceExceptionFilter="ReturnExceptionHandler" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True" ValidatesOnExceptions="True" >
                                                <Binding.ValidationRules>
                                                    <b:CustomValidRule ValidationStep="ConvertedProposedValue"></b:CustomValidRule>
                                                </Binding.ValidationRules>
                                            </Binding>
                                    </TextBox.Text>
                                </TextBox>
        

        //////////////////////

        private void TextBox_Loaded(object sender, RoutedEventArgs e)
                {
                    Collection<ValidationRule> rules= ((TextBox)sender).GetBindingExpression(TextBox.TextProperty).ParentBinding.ValidationRules;
        
                    foreach (ValidationRule rule in rules)
                        vm.Rules.Add(rule);
                }
        
        1. 自定义后端级别验证。 这是通过处理Class1对象的PropertyChanged事件来完成的。请参阅上面的ViewModel.cs。

          void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
          {            
              // if all back-end last level validations run good here
              // canHello = true;
          }
          
        2. 注意:我们可以使用反射来避免处理TextBox Loaded事件。因此,只需在遗嘱中添加验证规则即可。