我已经创建了一个附加的示例MVVM应用程序。
我想要实现的目标是:
我想验证单个单元格/行(这也已经有效)
我想做“表单”验证。例如。跨行验证,以验证数据网格的第一列中没有重复的字符串。
你会怎么做?我不知道如何在视图模型中实现表单验证。
我的第一个想法就是每次有任何变化时都会从后面的代码中调用viewmodel上的验证方法。但是这样做,我仍然不知道如何通知viewmodel有关视图验证规则中的验证错误(例如,如果有人将文本输入到ID列)。 viewmodel根本就不知道它并最终成功验证,只是因为错误的值永远不会达到它。好吧,我可以使用字符串并在viewmodel中进行整个转换 - 但我不喜欢这个想法,因为我想在WPF中使用转换器/验证器的全部功能。
有人已经做过类似的事吗?
https://www.dropbox.com/s/f3a1naewltbl9yp/DataGridValidationTest.zip?dl=0
答案 0 :(得分:0)
我不相信可以使用DataGrid
中的多个列来验证行。但是,正如您所提到的,您可以使用viewmodel来完成它。
您必须将DataGrid
的行存储在ViewModel
中(但我希望您已经这样做了)。您需要实施INotifyDataErrorInfo
。此界面允许您在某些错误发生更改时通知视图。
然后,每次更改name
属性时,验证是否存在重复项。
您的保存按钮应使用ICommand
来调用保存操作。在CanExecute
方法中,您可以检查实现HasErrors
的对象的INotifyDataErrorInfo
属性,并返回相应的boolean
。这会相应地禁用该按钮。
答案 1 :(得分:0)
我们需要处理3种类型的错误。
逐个解决方案
当我们输入需要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.";
}
要启用Button正确响应,我们需要将4个东西粘在一起即可。 ViewModel,Button,ValidationRules和DataGrid的模板列的文本框。否则无法正确设置ViewModel.CanHello属性,从而使RelayCommand无用。 现在ValidationRules:CustomValidRule和NegValidRule没有粘贴到ViewModel。为了让他们通知ViewModel他们的验证结果,他们需要触发一些事件。 我们将使用使用InotifyPropertyChanged的WPF跟随的通知模式。 我们将为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添加这些规则,因此我们将使用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事件。因此,只需在遗嘱中添加验证规则即可。