2015-05-04 71 views
2

我正在使用INotifyDataErrorInfo接口來實現一般的MVVM驗證機制。我通過調用的OnValidate而不是OnPropertyChanged實現接口:INotifyDataErrorInfo和綁定異常

public void OnValidate(dynamic value, [CallerMemberName] string propertyName = null) 
{ 
     if (PropertyChanged != null) 
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     Validate(propertyName, value); 
} 

在validate方法我生成驗證錯誤,將它們添加到字典,提高ErrorsChanged事件,如果發現驗證錯誤或清除:

if (entry.Validate(strValue, out errorNumber, out errorString) == false) 
{ 
     _validationErrors[propertyName] = new List<string> {errorString}; 
     RaiseErrorsChanged(propertyName); 
} 
else if (_validationErrors.ContainsKey(propertyName)) 
{ 
     _validationErrors.Remove(propertyName); 
     RaiseErrorsChanged(propertyName); 
} 

的HasErrors屬性通過查看錯誤執行詞典:

public bool HasErrors 
    { 
     get { return _validationErrors.Any(kv => kv.Value != null 
        && kv.Value.Count > 0); } 
    } 

爲了防止b中的保存按鈕eing啓用時,有一個驗證錯誤 - save命令canExecuteMethod着眼於HasErrors屬性:

private bool IsSaveEnabled() 
{ 
    return HasErrors == false; 
} 

一切正常,只是我有綁定錯誤地方的情況 - 如果綁定的值(例如)一輸入非整數的整數 - 文本框的ErrorContent更新錯誤字符串:「值'東西'無法轉換」。 但是INotifyDataErrorInfo機制沒有關於此更新。雖然視圖中存在錯誤,但HasErrors仍然是false並啓用了Save。 我想找到一種方法來傳播結合例外INotifyDataErrorInfo機制,所以我將能夠:

  1. 禁用Save按鈕(必須)。
  2. 將驗證錯誤消息更改爲更有意義的錯誤字符串(很好)。

我想找到一個通用的MVVM解決方案,而不需要在視圖中添加代碼。

感謝您的幫助

回答

0

這是我找到的解決方案。它使得INotifyDataErrorInfo在ViewModel中正確運行(當存在任何驗證錯誤時 - HasError爲真),並允許從viewModel添加驗證錯誤。除此之外,它不需要更改視圖,更改綁定或轉換器。

該解決方案包括:

  • 添加自定義的驗證規則。
  • 添加一個基本用戶控件(所有視圖都必須從中導出)。
  • 在ViewModel庫中添加一些代碼。

添加自定義的驗證規則 - 校驗實體來完成實際的驗證和提升,當確認修改的事件:

class ValidationEntity : ValidationRule 
{ 
    public string Key { get; set; } 

    public string BaseName = "Base"; 

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
    { 
     var fullPropertyName = BaseName + "." + Key; 
     ValidationEntry entry; 

     var validationResult = new ValidationResult(true, null); 

     if ((entry = ValidationManager.Instance.FindValidation(fullPropertyName)) != null) 
     { 
      int errorNumber; 
      string errorString; 

      var strValue = (value != null) ? value.ToString() : string.Empty; 

      if (entry.Validate(strValue, out errorNumber, out errorString) == false) 
      { 
       validationResult = new ValidationResult(false, errorString); 
      } 
     } 

     if (OnValidationChanged != null) 
     { 
      OnValidationChanged(Key, validationResult); 
     } 
     return validationResult; 
    } 

    public event Action<string, ValidationResult> OnValidationChanged; 
} 

添加其保持活躍textboxs列表的基本用戶控件,添加驗證規則到每個文本框的結合: 這是在用戶控制基站的代碼:

private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) 
{ 
    _textBoxes = FindAllTextBoxs(this); 

    var vm = DataContext as ViewModelBase; 
    if (vm != null) vm.UpdateAllValidationsEvent += OnUpdateAllValidationsEvent; 

    foreach (var textbox in _textBoxes) 
    { 
     var binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty); 

     if (binding != null) 
     { 
      var property = binding.Path.Path; 
      var validationEntity = new ValidationEntity {Key = property}; 
      binding.ValidationRules.Add(validationEntity); 
      validationEntity.ValidationChanged += OnValidationChanged; 
     } 
    } 
} 
private List<TextBox> FindAllTextBoxs(DependencyObject fe) 
{ 
    return FindChildren<TextBox>(fe); 
} 

private List<T> FindChildren<T>(DependencyObject dependencyObject) 
      where T : DependencyObject 
{ 
    var items = new List<T>(); 

    if (dependencyObject is T) 
    { 
     items.Add(dependencyObject as T); 
     return items; 
    } 

    var count = VisualTreeHelper.GetChildrenCount(dependencyObject); 
    for (var i = 0; i < count; i++) 
    { 
     var child = VisualTreeHelper.GetChild(dependencyObject, i); 
     var children = FindChildren<T>(child); 
     items.AddRange(children); 
    } 
    return items; 
} 

當ValidationChange事件發生 - 該視圖被調用以被通知關於驗證錯誤:

private void OnValidationChanged(string propertyName, ValidationResult validationResult) 
{ 
    var vm = DataContext as ViewModelBase; 

    if (vm != null) 
    { 
     if (validationResult.IsValid) 
     { 
      vm.ClearValidationErrorFromView(propertyName); 
     } 
     else 
     { 
      vm.AddValidationErrorFromView(propertyName, validationResult.ErrorContent as string); 
     } 
    } 
} 

視圖模型基部保持兩個列表:其中使用由INotifyDataErrorInfo接口來顯示驗證錯誤

  • _notifyvalidationErrors。
  • _privateValidationErrors用於將驗證規則生成的錯誤顯示給用戶。

當從視圖添加驗證錯誤 - _notifyvalidationErrors更新爲空值(僅表示存在驗證錯誤)時,錯誤字符串不會添加到_notifyvalidationErrors。如果我們將它添加到那裏,我們將在文本框ErrorContent中兩次獲取驗證錯誤字符串。 驗證錯誤字符串也被添加到_privateValidationErrors(因爲我們希望能夠保持它在視圖模型) 這是在視圖模型的基礎代碼:

視圖中的INotifyDataErrorInfo實現:

public bool HasErrors 
{ 
    get { return _notifyvalidationErrors.Any(kv => kv.Value != null); } 
} 
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

public void RaiseErrorsChanged(string propertyName) 
{ 
     var handler = ErrorsChanged; 
     if (handler != null) 
      handler(this, new DataErrorsChangedEventArgs(propertyName)); 
} 
public IEnumerable GetErrors(string propertyName) 
{ 
    List<string> errorsForProperty; 
    _notifyvalidationErrors.TryGetValue(propertyName, out errorsForProperty); 

    return errorsForProperty; 
} 

用戶可以通過調用ViewModelBase AddValidationError和ClearValidationError方法從視圖中添加驗證錯誤。

public void AddValidationError(string errorString, [CallerMemberName] string propertyName = null) 
{ 
    _notifyvalidationErrors[propertyName] = new List<string>{ errorString }; 
    RaiseErrorsChanged(propertyName); 
} 

public void ClearValidationError([CallerMemberName] string propertyName = null) 
{ 
    if (_notifyvalidationErrors.ContainsKey(propertyName)) 
    { 
     _notifyvalidationErrors.Remove(propertyName); 
     RaiseErrorsChanged(propertyName); 
    } 
} 

視圖可以通過調用GetValidationErrors和GetValidationErrorsString方法得到視圖模型基地目前所有的驗證錯誤的列表。

public List<string> GetValidationErrors() 
{ 
    var errors = new List<string>(); 
    foreach (var key in _notifyvalidationErrors.Keys) 
    { 
     errors.AddRange(_notifyvalidationErrors[key]); 

     if (_privateValidationErrors.ContainsKey(key)) 
     { 
      errors.AddRange(_privateValidationErrors[key]); 
     } 
    } 
    return errors; 
} 

public string GetValidationErrorsString() 
{ 
    var errors = GetValidationErrors(); 
    var sb = new StringBuilder(); 
    foreach (var error in errors) 
    { 
     sb.Append("● "); 
     sb.AppendLine(error); 
    } 
    return sb.ToString(); 
} 
0

ValidatesOnExceptions="True"

在你的綁定表達式。

+0

我已經嘗試過了: <綁定路徑= 「SomeProperty」 ValidatesOnExceptions = 「真」/> 遺憾的是它不工作 –

+0

您是收到異常? 發佈SomeProperty和引發異常的代碼。 –

+0

財產: 私人詮釋_someProperty; public int SomeProperty { get {return _someProperty; } set { _someProperty = value; OnValidate(value); } } –

3

INT情況下不與MVVM工作,因爲你的視圖模型沒有得到,因爲綁定異常的任何信息。

我看到兩種方式來獲得你想要的驗證:

  1. 只需使用字符串屬性在您的視圖模型,當你必須去你的模型只是字符串轉換爲模型類型。
  2. 創建行爲或「特殊」控件,以便視圖中的輸入始終可以「轉換」爲視圖模型類型。

btw我使用第二種方法,因爲我必須:)但第一個將始終工作,似乎對我來說更容易。

+0

謝謝。 不幸的是,我不能使用選項1,因爲我試圖爲已有的代碼實現驗證系統,這些代碼有很多int和double屬性。 我現在正在努力尋找一種方法來捕獲綁定錯誤。 –

+0

afaik,你不能用mvvm和INotifyDataErrorInfo來做到這一點:(你的viewmodel沒有獲得任何信息,採取第二種方法,並創建行爲或控件,它只接受正確的輸入類型,像http://stackoverflow.com/questions/16914224/ wpf-textbox-to-enter-decimal-values – blindmeis

+0

@TalSegal這是正確的答案,也許你應該用一個處理轉換和驗證的層包裝你的「已經存在的代碼」,實現IDEI而不是已經存在的代碼?實現它作爲一個TypeDescriptor,WPF綁定將用於與您的類型進行交互(如果已定義) – Will