我们实际上需要处理 3 种类型的错误。
- 当我们在需要 Int 的地方输入 String 时,WPF 的绑定引擎产生错误。
使用 UpdateSourceExceptionFilter 解决了这个问题。
- 自定义 UI 级别验证。
使用我们自己的接口并遵循 INotifyPropertyChanged 之类的通知模式可以解决这个问题。
- 自定义后端级别验证。
在我们的 ViewModel 中处理 PropertyChanged 事件可以解决这个问题。
一一解决方案
-
当我们在需要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.";
}
- 自定义 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);
}
-
自定义后端级别验证。
这是通过处理 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 事件。因此,只需在遗嘱中添加验证规则即可。