【问题标题】:WPF MVVM Validation DataGrid and disable CommandButtonWPF MVVM Validation DataGrid 并禁用 CommandButton
【发布时间】:2015-12-10 05:05:42
【问题描述】:

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

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

我想要实现的是:

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

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

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

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

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

有没有人做过类似的事情?

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

【问题讨论】:

  • 您在 ViewModel 上使用IDataErrorInfo 吗?我会很容易使用它和 ICommand 的 CanExecute 参数。尽管根据您拥有的 ICommand 类型,您可能需要在 PropertyChanged 事件中手动重新查询 CanExecuteChanged。

标签: c# wpf validation mvvm datagrid


【解决方案1】:

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

  1. 当我们在需要 Int 的地方输入 String 时,WPF 的绑定引擎产生错误。 使用 UpdateSourceExceptionFilter 解决了这个问题。
  2. 自定义 UI 级别验证。 使用我们自己的接口并遵循 INotifyPropertyChanged 之类的通知模式可以解决这个问题。
  3. 自定义后端级别验证。 在我们的 ViewModel 中处理 PropertyChanged 事件可以解决这个问题。

一一解决方案

  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>
    

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

现在我们已经添加了规则集合,我们需要向它添加我们的验证规则。为此,我们需要参考我们的验证规则。我们现在使用 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;
    }
    

注意:我们可以使用反射来避免处理 TextBox Loaded 事件。因此,只需在遗嘱中添加验证规则即可。

【讨论】:

  • 好吧,我也想过这个。但是我仍然不知道如何处理如果有人在绑定到 int 的文本框中输入文本的问题。 viewmodel 不会注意到验证错误,因为 viewmodel 支持的属性中的值不会改变。
  • 发布了广泛的答案,因此删除了旧答案。您需要的所有验证现在都按照您的预期工作。
【解决方案2】:

我认为不可能使用DataGrid 中的多个列来验证一行。但是,正如您所提到的,您可以使用视图模型来做到这一点。

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

然后,每次更改 name 属性时,验证是否有任何重复项。

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

【讨论】:

    【解决方案3】:

    有点暴力的方法。我在我的项目中做了以下设计。 这有点难以用文字解释,所以希望你能理解我在这里输入的内容

    我会有以下设计

    1. FormLevelViewModel - 包含 InnerViewModel 的集合(DataRowViewModel - 即每一行都是一个视图模型)和按钮命令
    2. DataRowLevelViewModel - 包含 InnerViewModel 的集合(即 CellViewModel)
    3. CellLevelViewModel

      • 对于 CellViewModel,可以在那里执行属性级别验证并填充错误以进行相应控制
      • 对于 DataRowViewModel,可以执行对象级验证并从所有 InnerViewModel 执行验证
      • 类似FormViewModel,可以通过递归的方式进行验证,从所有的InnerViewModels中触发验证,得到聚合结果。

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

    【讨论】:

    • 嗯好吧,这意味着您在 ViewModel 中进行了整个验证并且您没有使用验证规则,我理解正确吗?这是否意味着您绑定到视图模型中的字符串属性并在那里进行转换,而不是使用自定义转换器或验证规则?
    • 我建议你应该把你想在这里做的事情分开。 IValueConverter 用于视图和视图模型之间的类型转换。应该与验证无关。在 ViewModel 集属性上触发验证,如果验证失败,您应该在 ViewModelBase 中实现 INotifyDataErrorInfo 以引发 ErrorsChanged 事件。
    猜你喜欢
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 2013-08-03
    • 1970-01-01
    • 2012-10-04
    • 2011-04-22
    • 2011-03-05
    相关资源
    最近更新 更多