WPF MVVM验证DataGrid并禁用CommandButton [英] WPF MVVM Validation DataGrid and disable CommandButton

查看:187
本文介绍了WPF MVVM验证DataGrid并禁用CommandButton的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


  • 我使用datagrid

  • 我有一个绑定到命令的按钮

  • 我有一些自定义验证规则应用于某些单元格和一个文本框。



我想要实现的是:




  • 我喜欢在打字时验证(这已经在使用验证规则和UpdateSourceTrigger = PropertyChanged)。

  • 我想验证单个单元格/行(这也已经在工作)


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


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

  • 如果表单有效,我想启用该命令。



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



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



有没有人做过这样的事情?



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

解决方案

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


  1. 当我们输入需要Int的字符串时,由WPF的Binding引擎生成错误。
    使用UpdateSourceExceptionFilter解决了这个问题。

  2. 自定义UI级别验证。
    使用我们自己的界面和以下通知模式,如INotifyPropertyChanged解决了这个问题。

  3. 自定义后端级验证。
    在ViewModel中处理PropertyChanged事件解决了这个问题。

逐个解决方案 p>


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

     < TextBox VerticalAlignment =StretchVerticalContentAlignment =CenterLoaded =TextBox_Loaded> 
    < TextBox.Text>
    < Binding Path =IDUpdateSourceExceptionFilter =ReturnExceptionHandlerUpdateSourceTrigger =PropertyChangedValidatesOnDataErrors =TrueValidatesOnExceptions =True>
    < Binding.ValidationRules>
    < CustomValidRule ValidationStep =ConvertedProposedValue>< / CustomValidRule>
    < /Binding.ValidationRules>
    < / Binding>
    < /TextBox.Text>
    < / TextBox>


MainWindow.xaml.cs

  object ReturnExceptionHandler(object bindingExpression,Exception exception)
{
vm.CanHello = false;

return这是从UpdateSourceExceptionFilterCallBack。;
}




  1. 自定义UI级别验证要启用按钮正确响应,我们需要将4个东西粘在一起,即可。 ViewModel,Button,ValidationRules和DataGrid的模板列的文本框。否则ViewModel.CanHello属性无法正确设置,因此不会使用RelayCommand。
    现在ValidationRules:CustomValidRule和NegValidRule没有粘贴到ViewModel。为了使它们通知ViewModel关于它们的验证结果,他们需要触发一些事件。
    我们将利用WPF跟随使用InotifyPropertyChanged的通知模式。
    我们将创建一个界面IViewModelUIRule用于UI级验证规则与ViewModel进行交互。



    ViewModelUIRuleEvent.cs

     使用系统; 

    命名空间BusinessLogic
    {
    public interface IViewModelUIRule
    {
    事件ViewModelValidationHandler ValidationDone;
    }

    public delegate void ViewModelValidationHandler(object sender,ViewModelUIValidationEventArgs e);

    public class ViewModelUIValidationEventArgs:EventArgs
    {
    public bool IsValid {get;组; }

    public ViewModelUIValidationEventArgs(bool valid){IsValid = valid; }
    }
    }

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

      public class CustomValidRule:ValidationRule,IViewModelUIRule 
    {

    bool _isValid = true;
    public bool IsValid {get {return _isValid; } set {_isValid = value;

    public override ValidationResult验证(对象值,System.Globalization.CultureInfo cultureInfo)
    {

    int? a =值为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,必须为> 0和< 10);
    }
    }

    OnValidationDone();

    返回结果;
    }

    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验证(对象值,System.Globalization.CultureInfo cultureInfo)
    {
    int? a =值为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();

    返回结果;
    }

    private void OnValidationDone()
    {
    if(ValidationDone!= null)
    ValidationDone(this,new ViewModelUIValidationEventArgs(_isValid));
    }

    public event ViewModelValidationHandler ValidationDone;
    }

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

     命名空间BusinessLogic 
    {
    public class ViewModel :INotifyPropertyChanged
    {
    private ObservableCollection< ValidationRule> _rules;
    public ObservableCollection< ValidationRule>规则{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(e.NewItems中的var v)
    ((Class1)v).PropertyChanged + = ViewModel_PropertyChanged;
    }

    void ViewModel_PropertyChanged(object sender,PropertyChangedEventArgs e)
    {
    //如果所有验证在这里运行良好
    // canHello = true;
    }
    ......

    现在我们添加了规则集,我们需要添加我们的验证规则。为此,我们需要参考我们的验证规则。我们现在正在使用XAML添加这些规则,所以我们将使用TexBox的Loaded事件绑定到ID字段的TextBox来访问这些规则,所以

     code>< TextBox VerticalAlignment =StretchVerticalContentAlignment =CenterLoaded =TextBox_Loaded> 
    < TextBox.Text>
    < Binding Path =IDUpdateSourceExceptionFilter =ReturnExceptionHandlerUpdateSourceTrigger =PropertyChangedValidatesOnDataErrors =TrueValidatesOnExceptions =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规则)
    vm.Rules.Add(rule);
    }




    1. 自定义后端级验证。
      这是通过处理Class1对象的PropertyChanged事件来完成的。请参阅上面的ViewModel.cs列表。

        void ViewModel_PropertyChanged(object sender,PropertyChangedEventArgs e)
      {
      / /如果所有后端最后一级验证运行在这里
      // canHello = true;
      }


    注意:使用反射来避免处理TextBox加载的事件。所以只需要添加验证规则就可以完成工作。


    I have created a sample MVVM application attached.

    • I use a datagrid
    • I have a button bound to a command
    • I have a some custom validation rules applied to certain cells and a textbox.

    What I want to achieve is:

    • I like validating while typing (this is already working with validation rules and UpdateSourceTrigger=PropertyChanged).
    • I'd like to validate single cells/rows (this is already working, too)

    • I'd like to do "form" validation. E.g. Cross-row validation to validate that no duplicate strings are in the first column of the datagrid.

    • I'd like to disable the command if any validation rule has or the viewmodels form validation has an error.
    • I'd like to enable the command if the form is valid.

    How would you do this? I have no clue how to implement the form validation in the view model.

    My first idea was just to call a validation method on the viewmodel from the code behind every time anything changes. But doing so, I still don't know how to inform the viewmodel about an validation error in the view's validation rule (e.g. if someone enters text to the ID column). The viewmodel would simply not know about it and eventually validate successfully, just because the wrong value never reaches it. Ok, I could use strings and do the whole conversion in the viewmodel - but I don't like this idea, because I would like to use the whole power of the converters/validators in WPF.

    Has anybody already done something like that?

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

    解决方案

    We need to handle actually 3 types of errors.

    1. Error generated by Binding engine of WPF when we enter String where Int is needed. Using UpdateSourceExceptionFilter solves this problem.
    2. Custom UI level validation. Using our own Interface and following notification pattern like INotifyPropertyChanged solves this problem.
    3. Custom back-end level validation. Handling PropertyChanged event in our ViewModel solves this problem.

    One by one solutions

    1. Error generated by Binding engine of WPF when we enter String where Int is needed.

          <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>
      

    MainWindow.xaml.cs

    object ReturnExceptionHandler(object bindingExpression, Exception exception)
            {
                vm.CanHello = false;
    
                return "This is from the UpdateSourceExceptionFilterCallBack.";
           }
    

    1. Custom UI level validation

    To enable Button respond properly we need to glue 4 things together viz; ViewModel, Button, ValidationRules, and DataGrid’s template column’s textbox. Otherwise ViewModel.CanHello property can’t be set properly thus making RelayCommand of no use. Right now ValidationRules : CustomValidRule and NegValidRule are not glued to ViewModel. To make them notify ViewModel about their validation result, they need to fire some event. We will make use of notification pattern which WPF follows using InotifyPropertyChanged. We will create an interface IViewModelUIRule for UI level validation rules to interact with 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; }
            }
        }
    

    Our validation rules will now implement this interface.

    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;
    }
    

    Now, we need to update our ViewModel class to maintain validation rules collection. And to handle ValidationDone event fired by our custom validation rules.

    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;
            }
            ……
    

    Now that we have added Rules collection, we need to add our validation rules to it. For this we need to have reference to our validation rules. We are now adding these rules using XAML, so we will use TexBox’s Loaded event for the TextBox binded to ID field to get access to these like so,

    <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. Custom back-end level validation. This is done by handling PropertyChanged event of Class1’s objects. See ViewModel.cs listing above.

      void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
      {            
          // if all back-end last level validations run good here
          // canHello = true;
      }
      

    Note : We can use reflection to avoid handling of TextBox Loaded event. So merely adding validation rules to the will do the work.

    这篇关于WPF MVVM验证DataGrid并禁用CommandButton的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆