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