WPF MVVM验证数据网格和禁用命令按钮
本文关键字:命令 按钮 网格 MVVM 验证 数据 数据网 WPF | 更新日期: 2023-09-27 18:12:41
我已经创建了一个示例MVVM应用程序。
- 我使用数据网格
- 我有一个按钮绑定到命令
- 我有一些自定义的验证规则应用于某些单元格和文本框。
我想实现的是:
- 我喜欢在键入时进行验证(这已经与验证规则和UpdateSourceTrigger=PropertyChanged一起工作)。
-
我想验证单个单元格/行(这已经在工作了)
-
我想做"表单"验证。例如,交叉行验证,以验证在数据网格的第一列中没有重复的字符串。
- 我想禁用命令,如果任何验证规则或视图模型表单验证有一个错误。
- 如果表单有效,我想启用该命令。
你会怎么做?我不知道如何在视图模型中实现表单验证。
我的第一个想法是在每次发生任何变化时从后面的代码调用视图模型上的验证方法。但是这样做,我仍然不知道如何通知视图模型关于视图的验证规则的验证错误(例如,如果有人输入文本到ID列)。视图模型根本不知道它并最终成功验证,因为错误的值永远不会到达它。我可以使用字符串并在视图模型中进行整个转换-但我不喜欢这个想法,因为我想使用WPF中转换器/验证器的全部功能。
有人做过这样的事吗?
https://www.dropbox.com/s/f3a1naewltbl9yp/DataGridValidationTest.zip?dl=0
我们实际上需要处理3种类型的错误。
- WPF绑定引擎在需要输入Int的地方输入String时产生的错误。UpdateSourceExceptionFilter解决了这个问题。
- 自定义UI级别验证。使用我们自己的接口和以下通知模式,如INotifyPropertyChanged解决了这个问题。
- 自定义后端级别验证。在ViewModel中处理PropertyChanged事件可以解决这个问题。
一一解
-
WPF绑定引擎在需要输入Int的地方输入String时产生的错误。
<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级别验证
自定义后端级别验证。这是通过处理Class1对象的PropertyChanged事件来实现的。参见上面的ViewModel.cs清单。
void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { // if all back-end last level validations run good here // canHello = true; }
要使Button正确响应,我们需要将4个东西粘合在一起;ViewModel、Button、ValidationRules和DataGrid的模板列的文本框。否则,视图模型。CanHello属性不能正确设置,从而使RelayCommand不起作用。现在ValidationRules: CustomValidRule和NegValidRule还没有粘到ViewModel上。为了让它们通知ViewModel它们的验证结果,它们需要触发一些事件。我们将使用InotifyPropertyChanged来使用WPF遵循的通知模式。我们将为UI级验证规则创建一个接口iviewmodeluirrule,以便与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添加这些规则,所以我们将使用TextBox的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);
}
注意:我们可以使用反射来避免处理TextBox Loaded事件。因此,只需向其中添加验证规则就可以了。
我不相信在DataGrid
中使用多列来验证行是可能的。但是,正如您所提到的,您可以使用视图模型来实现它。
您必须将DataGrid
的行存储在ViewModel
中(但我希望您已经这样做了)。您需要实现INotifyDataErrorInfo
。如果某些错误发生了变化,这个接口允许您通知视图。
然后,每次更改name
属性时,验证是否有任何重复。
您的保存按钮应该使用ICommand
来调用保存操作。在CanExecute
方法中,您可以检查实现INotifyDataErrorInfo
的对象的HasErrors
属性,并返回相应的boolean
。这会相应地禁用按钮。
有点蛮力的方法。我在我的项目中做了以下设计。用文字解释有点困难,所以希望你能理解我在这里输入的内容
我将有如下的设计
- FormLevelViewModel -它包含一个集合的InnerViewModels (DataRowViewModel -即,每一行是一个视图模型)和按钮命令
- DataRowLevelViewModel -包含一个InnerViewModels(即CellViewModel)的集合
-
CellLevelViewModel
- 对于CellViewModel,可以在那里执行属性级验证并相应地填充错误到控件
- 对于DataRowViewModel,可以执行对象级验证和从所有InnerViewModels执行验证
- 类似FormViewModel,可以用递归的方法执行验证,触发所有innerviewmodel的验证,得到聚合的结果。