繁体   English   中英

WPF MVVM验证DataGrid并禁用CommandButton

[英]WPF MVVM Validation DataGrid and disable CommandButton

我创建了一个示例MVVM应用程序。

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

我想要实现的是:

  • 我喜欢在输入时进行验证(这已经与验证规则和UpdateSourceTrigger = PropertyChanged一起使用)。
  • 我想验证单个单元格/行(这已经在起作用)

  • 我想进行“表格”验证。 例如,进行跨行验证,以验证datagrid的第一列中是否没有重复的字符串。

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

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

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

有人做过类似的事情吗?

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

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

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

一对一的解决方案

  1. 当我们在需要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

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

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

为了使Button正确响应,我们需要将4件事粘合在一起: ViewModel,Button,ValidationRules和DataGrid的模板列的文本框。 否则,将无法正确设置ViewModel.CanHello属性,从而使RelayCommand无效。 现在,ValidationRules:CustomValidRule和NegValidRule尚未粘贴到ViewModel。 为了使他们将验证结果通知给ViewModel,他们需要触发一些事件。 我们将使用WPF通过InotifyPropertyChanged遵循的通知模式。 我们将为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添加这些规则,因此我们将对绑定到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. 自定义后端级别验证。 这是通过处理Class1对象的PropertyChanged事件来完成的。 请参阅上面列出的ViewModel.cs。

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

注意:我们可以使用反射来避免处理TextBox Loaded事件。 因此,仅将验证规则添加到即可完成工作。

我不相信有可能使用DataGrid多个列来验证行。 但是,正如您提到的,您可以使用viewmodel做到这一点。

您必须将DataGrid的行存储在ViewModel (但我希望您已经在这样做了)。 您需要实现INotifyDataErrorInfo 如果某些错误发生更改,则可以使用此界面通知视图。

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

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

金达蛮力进取。 我在我的项目中做了以下设计。 文字很难解释,希望您能理解我在这里输入的内容

我将有以下设计

  1. FormLevelViewModel-包含InnerViewModels的集合(DataRowViewModel-即每一行都是一个viewmodel)和buttom命令
  2. DataRowLevelViewModel-包含InnerViewModels(即CellViewModel)的集合
  3. CellLevelViewModel

    • 对于CellViewModel,可以在该位置执行属性级别验证,并填充错误以进行相应控制
    • 对于DataRowViewModel,可以执行对象级别验证,并可以从所有InnerViewModels执行验证
    • 同样,FormViewModel可以以递归方法执行验证,以触发所有InnerViewModels的验证并获得汇总结果。

通过上述设计设置,您所需要做的就是在ViewModelBase上拥有一个EventHandler,该事件处理程序将在ViewModel执行验证后触发。 使用此事件触发父ViewModel来执行其自己的验证级别,并将错误结果填充回根视图模型。

暂无
暂无

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

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