[英]WPF MVVM Validation DataGrid and disable CommandButton
我创建了一个示例MVVM应用程序。
我想要实现的是:
我想验证单个单元格/行(这已经在起作用)
我想进行“表格”验证。 例如,进行跨行验证,以验证datagrid的第一列中是否没有重复的字符串。
你会怎么做? 我不知道如何在视图模型中实现表单验证。
我的第一个想法是,每次发生任何更改时,都从背后的代码中调用视图模型上的验证方法。 但是这样做,我仍然不知道如何将视图验证规则中的验证错误通知给ViewModel(例如,如果有人在ID列中输入文本)。 视图模型将根本不知道并最终成功验证,只是因为错误的值永远不会到达它。 好的,我可以使用字符串并在viewmodel中进行整个转换-但是我不喜欢这个想法,因为我想在WPF中使用转换器/验证器的全部功能。
有人做过类似的事情吗?
https://www.dropbox.com/s/f3a1naewltbl9yp/DataGridValidationTest.zip?dl=0
我们实际上需要处理3种类型的错误。
一对一的解决方案
当我们在需要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.";
}
为了使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);
}
自定义后端级别验证。 这是通过处理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
。 这将相应地禁用按钮。
金达蛮力进取。 我在我的项目中做了以下设计。 文字很难解释,希望您能理解我在这里输入的内容
我将有以下设计
CellLevelViewModel
通过上述设计设置,您所需要做的就是在ViewModelBase上拥有一个EventHandler,该事件处理程序将在ViewModel执行验证后触发。 使用此事件触发父ViewModel来执行其自己的验证级别,并将错误结果填充回根视图模型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.