简体   繁体   English

WPF MVVM验证DataGrid并禁用CommandButton

[英]WPF MVVM Validation DataGrid and disable CommandButton

I have created a sample MVVM application attached. 我创建了一个示例MVVM应用程序。

  • 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). 我喜欢在输入时进行验证(这已经与验证规则和UpdateSourceTrigger = PropertyChanged一起使用)。
  • I'd like to validate single cells/rows (this is already working, too) 我想验证单个单元格/行(这已经在起作用)

  • I'd like to do "form" validation. 我想进行“表格”验证。 Eg Cross-row validation to validate that no duplicate strings are in the first column of the datagrid. 例如,进行跨行验证,以验证datagrid的第一列中是否没有重复的字符串。

  • I'd like to disable the command if any validation rule has or the viewmodels form validation has an error. 如果有任何验证规则或viewmodels表单验证有错误,我想禁用该命令。
  • 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 (eg if someone enters text to the ID column). 但是这样做,我仍然不知道如何将视图验证规则中的验证错误通知给ViewModel(例如,如果有人在ID列中输入文本)。 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. 好的,我可以使用字符串并在viewmodel中进行整个转换-但是我不喜欢这个想法,因为我想在WPF中使用转换器/验证器的全部功能。

Has anybody already done something like that? 有人做过类似的事情吗?

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

We need to handle actually 3 types of errors. 我们实际上需要处理3种类型的错误。

  1. Error generated by Binding engine of WPF when we enter String where Int is needed. 当我们在需要Int的地方输入String时,WPF的绑定引擎生成的错误。 Using UpdateSourceExceptionFilter solves this problem. 使用UpdateSourceExceptionFilter解决了此问题。
  2. Custom UI level validation. 自定义UI级别验证。 Using our own Interface and following notification pattern like INotifyPropertyChanged solves this problem. 使用我们自己的接口和以下通知模式(例如INotifyPropertyChanged)可以解决此问题。
  3. Custom back-end level validation. 自定义后端级别验证。 Handling PropertyChanged event in our ViewModel solves this problem. 在ViewModel中处理PropertyChanged事件可以解决此问题。

One by one solutions 一对一的解决方案

  1. Error generated by Binding engine of WPF when we enter String where Int is needed. 当我们在需要Int的地方输入String时,WPF的绑定引擎生成的错误。

      <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 MainWindow.xaml.cs

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

            return "This is from the UpdateSourceExceptionFilterCallBack.";
       }
  1. Custom UI level validation 自定义UI级别验证

To enable Button respond properly we need to glue 4 things together viz; 为了使Button正确响应,我们需要将4件事粘合在一起: ViewModel, Button, ValidationRules, and DataGrid's template column's textbox. ViewModel,Button,ValidationRules和DataGrid的模板列的文本框。 Otherwise ViewModel.CanHello property can't be set properly thus making RelayCommand of no use. 否则,将无法正确设置ViewModel.CanHello属性,从而使RelayCommand无效。 Right now ValidationRules : CustomValidRule and NegValidRule are not glued to ViewModel. 现在,ValidationRules:CustomValidRule和NegValidRule尚未粘贴到ViewModel。 To make them notify ViewModel about their validation result, they need to fire some event. 为了使他们将验证结果通知给ViewModel,他们需要触发一些事件。 We will make use of notification pattern which WPF follows using InotifyPropertyChanged. 我们将使用WPF通过InotifyPropertyChanged遵循的通知模式。 We will create an interface IViewModelUIRule for UI level validation rules to interact with ViewModel. 我们将为UI级别验证规则创建一个接口IViewModelUIRule,以与ViewModel进行交互。

ViewModelUIRuleEvent.cs 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. 现在,我们需要更新ViewModel类以维护验证规则集合。 And to handle ValidationDone event fired by our custom validation rules. 并处理我们的自定义验证规则触发的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;
        }
        ……

Now that we have added Rules collection, we need to add our validation rules to it. 现在,我们已经添加了Rules集合,我们需要向其中添加验证规则。 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, 现在,我们使用XAML添加这些规则,因此我们将对绑定到ID字段的TextBox使用TexBox的Loaded事件,以便像这样访问它们,

<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. 这是通过处理Class1对象的PropertyChanged事件来完成的。 See ViewModel.cs listing above. 请参阅上面列出的ViewModel.cs。

     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. 注意:我们可以使用反射来避免处理TextBox Loaded事件。 So merely adding validation rules to the will do the work. 因此,仅将验证规则添加到即可完成工作。

I dont believe it is possible to validate a row using multiple columns in a DataGrid . 我不相信有可能使用DataGrid多个列来验证行。 But, as you mentioned, you can do it using the viewmodel. 但是,正如您提到的,您可以使用viewmodel做到这一点。

You would have to store the rows of the DataGrid in the ViewModel (but I expect you are doing that already). 您必须将DataGrid的行存储在ViewModel (但我希望您已经在这样做了)。 The you need to implement INotifyDataErrorInfo . 您需要实现INotifyDataErrorInfo This interface allows you to notify the view if some errors changed. 如果某些错误发生更改,则可以使用此界面通知视图。

Then, every time the name property is changed, validate if there are any duplicates. 然后,每次更改name属性时,请验证是否存在重复项。

Your save button should use an ICommand to invoke the save action. 您的保存按钮应使用ICommand调用保存操作。 In the CanExecute method you can check the HasErrors property of the object that implements INotifyDataErrorInfo and return the appropriate boolean . CanExecute方法中,您可以检查实现INotifyDataErrorInfo的对象的HasErrors属性,并返回适当的boolean This disables the button accordingly. 这将相应地禁用按钮。

Kinda brute force approach. 金达蛮力进取。 I kinda did the following design in my project. 我在我的项目中做了以下设计。 It's kinda hard to explain in text so hope you'll understand what I typed here 文字很难解释,希望您能理解我在这里输入的内容

I would have the following design 我将有以下设计

  1. FormLevelViewModel - Which contains a collection of InnerViewModels (DataRowViewModel - ie Each row is an viewmodel) and the buttom command FormLevelViewModel-包含InnerViewModels的集合(DataRowViewModel-即每一行都是一个viewmodel)和buttom命令
  2. DataRowLevelViewModel - contains a collection of InnerViewModels (ie CellViewModel) DataRowLevelViewModel-包含InnerViewModels(即CellViewModel)的集合
  3. CellLevelViewModel CellLevelViewModel

    • For CellViewModel, can perform property level validation over there and populate the error to control accordingly 对于CellViewModel,可以在该位置执行属性级别验证,并填充错误以进行相应控制
    • For DataRowViewModel, can perform object level validation and perform validation from all the InnerViewModels 对于DataRowViewModel,可以执行对象级别验证,并可以从所有InnerViewModels执行验证
    • Similarly FormViewModel, can perform validation in recurrsive method to trigger validation from all InnerViewModels and obtained the aggregated results. 同样,FormViewModel可以以递归方法执行验证,以触发所有InnerViewModels的验证并获得汇总结果。

With the above design setup, all you need is having a EventHandler at your ViewModelBase that fired after an ViewModel performed Validation. 通过上述设计设置,您所需要做的就是在ViewModelBase上拥有一个EventHandler,该事件处理程序将在ViewModel执行验证后触发。 Use this event to Trigger parent ViewModel to perform it's own level of validation and keep populate the error result back to the root view model. 使用此事件触发父ViewModel来执行其自己的验证级别,并将错误结果填充回根视图模型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM