- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个 ObservableCollection 类型的数据集合(比如实例作为 myClassTypes)。在一些用户操作之后,这个 myClassTypes 填充了 ViewModel 中的值。在 View 中,有一个文本框,用户可以在其中输入文本。我需要根据 myClassTypes 值验证文本框数据。因此,如果 myClassTypes 包含用户在文本框中插入的文本,则验证通过,否则将失败。
我的代码片段是:
View 模型:
public ObservableCollection < MyClassType > ViewModelClassTypes {
get {
return _myClassTypes;
}
set {
_myClassTypes = value;
NotifyOfPropertyChange(() = >MyClassTypes);
}
}
public class TestValidationRule: ValidationRule {
public ObservableCollection < MyClassType > MyClassTypes {
get = >(ObservableCollection < MyClassType > ) GetValue(MyClassTypesProperty);
set = >SetValue(MyClassTypesProperty, value);
}
}
<TextBox>
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationRules:TestValidationRule MyClassTypes="{Binding ViewModelClassTypes}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
最佳答案
自 .Net 4.5 以来,实现数据验证的首选方法是让您的 View 模型实现 INotifyDataErrorInfo
(来自 Technet 的示例,来自 MSDN (Silverlight) 的示例)。
注意: INotifyDataErrorInfo
替换过时的 IDataErrorInfo
。INotifyDataErrorInfo
的工作原理
当 ValidatesOnNotifyDataErrors
的 Binding
属性设置为 true
时,绑定(bind)引擎将在绑定(bind)源上搜索 INotifyDataErrorInfo
实现并订阅 INotifyDataErrorInfo.ErrorsChanged
事件。
如果绑定(bind)源的 ErrorsChanged
事件被引发并且 INotifyDataErrorInfo.HasErrors
评估为 true
,则绑定(bind)引擎将调用实际源属性的 INotifyDataErrorInfo.GetErrors()
方法以检索相应的错误消息,然后将可自定义的验证错误模板应用于目标控件以可视化验证错误。
默认情况下,会在验证失败的元素周围绘制红色边框。
此验证反馈可视化过程仅在特定数据绑定(bind)上将 Binding.ValidatesOnNotifyDataErrors
设置为 true
并且 Binding.Mode
设置为 BindingMode.TwoWay
或 BindingMode.OneWayToSource
时才执行。
如何实现INotifyDataErrorInfo
以下示例显示使用 ValidationRule
(用于封装实际数据验证实现)和 Lambda(或委托(delegate))的默认验证。最后一个示例展示了如何使用验证属性实现数据验证。
代码没有经过测试。这些片段应该都可以工作,但由于输入错误可能无法编译。此代码旨在提供一个关于如何实现 INotifyDataErrorInfo
接口(interface)的简单示例。
ViewModel.cs
View 模型负责验证其属性以确保模型的数据完整性。
从 .NET 4.5 开始,推荐的方式是让 View 模型实现 INotifyDataErrorInfo
接口(interface)。
关键是为每个属性或规则有单独的 ValidationRule
实现:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
// Example property, which validates its value before applying it
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Validate the value
ValidateProperty(value);
this.userInput = value;
OnPropertyChanged();
}
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, List<string>>();
this.ValidationRules = new Dictionary<string, List<ValidationRule>>();
// Create a Dictionary of validation rules for fast lookup.
// Each property name of a validated property maps to one or more ValidationRule.
this.ValidationRules.Add(nameof(this.UserInput), new List<ValidationRule>() {new UserInputValidationRule()});
}
// Validation method.
// Is called from each property which needs to validate its value.
// Because the parameter 'propertyName' is decorated with the 'CallerMemberName' attribute.
// this parameter is automatically generated by the compiler.
// The caller only needs to pass in the 'propertyValue', if the caller is the target property's set method.
public bool ValidateProperty<TValue>(TValue propertyValue, [CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
this.Errors.Remove(propertyName);
OnErrorsChanged(propertyName);
if (this.ValidationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
// Apply all the rules that are associated with the current property
// and validate the property's value
propertyValidationRules
.Select(validationRule => validationRule.Validate(propertyValue, CultureInfo.CurrentCulture))
.Where(result => !result.IsValid)
.ToList()
.ForEach(invalidResult => AddError(propertyName, invalidResult.ErrorContent as string));
return !PropertyHasErrors(propertyName);
}
// No rules found for the current property
return true;
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if 'isWarning' is
// false. Raises the ErrorsChanged event if the Errors collection changes.
// A property can have multiple errors.
public void AddError(string propertyName, string errorMessage, bool isWarning = false)
{
if (!this.Errors.TryGetValue(propertyName, out List<string> propertyErrors))
{
propertyErrors = new List<string>();
this.Errors[propertyName] = propertyErrors;
}
if (!propertyErrors.Contains(errorMessage))
{
if (isWarning)
{
// Move warnings to the end
propertyErrors.Add(errorMessage);
}
else
{
propertyErrors.Insert(0, errorMessage);
}
OnErrorsChanged(propertyName);
}
}
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out List<string> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// Returns all errors of a property. If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out List<string> errors)
? errors
: new List<string>();
// Returns if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<String, List<String>> Errors { get; }
// Maps a property name to a list of ValidationRules that belong to this property
private Dictionary<String, List<ValidationRule>> ValidationRules { get; }
}
UserInputValidationRule.cs
ValidationRule
并检查输入是否以“@”字符开头。如果不是,它会返回一个无效的
ValidationResult
并带有错误消息:
public class UserInputValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!(value is string userInput))
{
return new ValidationResult(false, "Value must be of type string.");
}
if (!userInput.StartsWith("@"))
{
return new ValidationResult(false, "Input must start with '@'.");
}
return ValidationResult.ValidResult;
}
}
MainWindow.xaml
Binding.ValidatesOnNotifyDataErrors
上将
true
属性设置为
Binding
。然后 WPF 框架将显示控件的默认错误反馈。请注意,要使此工作正常运行,
Binding.Mode
必须是
OneWayToSource
或
TwoWay
(这是
TextBox.Text
属性的默认值):
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<!-- Important: set ValidatesOnNotifyDataErrors to true to enable visual feedback -->
<TextBox Text="{Binding UserInput, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</Window>
以下是验证错误模板,以防您想自定义可视化表示(可选)。它通过附加属性
TextBox
(见上文)设置在经过验证的元素(在本例中为
Validation.ErrorTemplate
)上:
<ControlTemplate x:Key=ValidationErrorTemplate>
<StackPanel>
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
INotifyDataErrorInfo
的实现移动到基类中(例如
BaseViewModel) together with
INotifyPropertyChanged` 并让所有 View 模型继承它。这使得验证逻辑可重用并保持 View 模型类清洁。
INotifyDataErrorInfo
的实现细节以满足要求。
ValidationRule
替换为委托(delegate)以启用 Lambda 表达式或方法组:
// Example uses System.ValueTuple
public bool ValidateProperty<TValue>(
TValue value,
Func<TValue, (bool IsValid, IEnumerable<string> ErrorMessages)> validationDelegate,
[CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
this.Errors.Remove(propertyName);
OnErrorsChanged(propertyName);
// Validate using the delegate
(bool IsValid, IEnumerable<string> ErrorMessages) validationResult = validationDelegate?.Invoke(value) ?? (true, string.Empty);
if (!validationResult.IsValid)
{
// Store the error messages of the failed validation
foreach (string errorMessage in validationResult.ErrorMessages)
{
// See previous example for implementation of AddError(string,string):void
AddError(propertyName, errorMessage);
}
}
return validationResult.IsValid;
}
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Validate the new property value before it is accepted
if (ValidateProperty(value,
newValue => newValue.StartsWith("@")
? (true, new List<string>())
: (false, new List<string> {"Value must start with '@'."})))
{
// Accept the valid value
this.userInput = value;
OnPropertyChanged();
}
}
}
// Alternative usage example property which validates its value
// before applying it using a Method group.
// Example uses System.ValueTuple.
private string userInputAlternativeValidation;
public string UserInputAlternativeValidation
{
get => this.userInputAlternativeValidation;
set
{
// Use Method group
if (ValidateProperty(value, AlternativeValidation))
{
this.userInputAlternativeValidation = value;
OnPropertyChanged();
}
}
}
private (bool IsValid, string ErrorMessage) AlternativeValidation(string value)
{
return value.StartsWith("@")
? (true, string.Empty)
: (false, "Value must start with '@'.");
}
ValidationAttribute
进行数据验证
INotifyDataErrorInfo
支持的
ValidationAttribute
的示例实现,例如
MaxLengthAttribute
。该解决方案结合了之前的 Lamda 版本,以额外支持同时使用 Lambda 表达式/委托(delegate)的验证:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
private string userInputAttributeValidation;
[Required(ErrorMessage = "Value is required.")]
public string UserInputAttributeValidation
{
get => this.userInputAttributeValidation;
set
{
// Use only the attribute (can be combined with a Lambda or Method group)
if (ValidateProperty(value))
{
this.userInputAttributeValidation = value;
OnPropertyChanged();
}
}
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, List<string>>();
}
// Validate properties using decorated attributes and/or a validation delegate.
// The validation delegate is optional.
public bool ValidateProperty<TValue>(
TValue value,
Func<TValue, (bool IsValid, IEnumerable<string> ErrorMessages)> validationDelegate = null,
[CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
this.Errors.Remove(propertyName);
OnErrorsChanged(propertyName);
bool isValueValid = ValidatePropertyUsingAttributes(value, propertyName);
if (validationDelegate != null)
{
isValueValid |= ValidatePropertyUsingDelegate(value, validationDelegate, propertyName);
}
return isValueValid;
}
// Validate properties using decorated attributes.
public bool ValidatePropertyUsingAttributes<TValue>(TValue value, string propertyName)
{
// The result flag
bool isValueValid = true;
// Check if property is decorated with validation attributes
// using reflection
IEnumerable<Attribute> validationAttributes = GetType()
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
?.GetCustomAttributes(typeof(ValidationAttribute)) ?? new List<Attribute>();
// Validate using attributes if present
if (validationAttributes.Any())
{
var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName };
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateProperty(value, validationContext, validationResults))
{
isValueValid = false;
foreach (ValidationResult attributeValidationResult in validationResults)
{
AddError(propertyName, attributeValidationResult.ErrorMessage);
}
}
}
return isValueValid;
}
// Validate properties using the delegate.
public bool ValidatePropertyUsingDelegate<TValue>(
TValue value,
Func<TValue, (bool IsValid, IEnumerable<string> ErrorMessages)> validationDelegate,
string propertyName)
{
// The result flag
bool isValueValid = true;
// Validate using the delegate
(bool IsValid, IEnumerable<string> ErrorMessages) validationResult = validationDelegate.Invoke(value);
if (!validationResult.IsValid)
{
isValueValid = false;
// Store the error messages of the failed validation
foreach (string errorMessage in validationResult.ErrorMessages)
{
AddError(propertyName, errorMessage);
}
}
return isValueValid;
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if 'isWarning' is
// false. Raises the ErrorsChanged event if the Errors collection changes.
// A property can have multiple errors.
public void AddError(string propertyName, string errorMessage, bool isWarning = false)
{
if (!this.Errors.TryGetValue(propertyName, out List<string> propertyErrors))
{
propertyErrors = new List<string>();
this.Errors[propertyName] = propertyErrors;
}
if (!propertyErrors.Contains(errorMessage))
{
if (isWarning)
{
// Move warnings to the end
propertyErrors.Add(errorMessage);
}
else
{
propertyErrors.Insert(0, errorMessage);
}
OnErrorsChanged(propertyName);
}
}
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out List<string> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// Returns all errors of a property. If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IEnumerable<string> errors)
? errors
: new List<string>();
// Returns if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<String, List<String>> Errors { get; }
}
关于wpf - 如何添加验证以查看模型属性或如何实现 INotifyDataErrorInfo,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56606441/
可不可以命名为MVVM模型?因为View通过查看模型数据。 View 是否应该只与 ViewModelData 交互?我确实在某处读到正确的 MVVM 模型应该在 ViewModel 而不是 Mode
我正在阅读有关设计模式的文章,虽然作者们都认为观察者模式很酷,但在设计方面,每个人都在谈论 MVC。 我有点困惑,MVC 图不是循环的,代码流具有闭合拓扑不是很自然吗?为什么没有人谈论这种模式: mo
我正在开发一个 Sticky Notes 项目并在 WPF 中做 UI,显然将 MVVM 作为我的架构设计选择。我正在重新考虑我的模型、 View 和 View 模型应该是什么。 我有一个名为 Not
不要混淆:How can I convert List to Hashtable in C#? 我有一个模型列表,我想将它们组织成一个哈希表,以枚举作为键,模型列表(具有枚举的值)作为值。 publi
我只是花了一些时间阅读这些术语(我不经常使用它们,因为我们没有任何 MVC 应用程序,我通常只说“模型”),但我觉得根据上下文,这些意味着不同的东西: 实体 这很简单,它是数据库中的一行: 2) In
我想知道你们中是否有人知道一些很好的教程来解释大型应用程序的 MVVM。我发现关于 MVVM 的每个教程都只是基础知识解释(如何实现模型、 View 模型和 View ),但我对在应用程序页面之间传递
我想realm.delete() 我的 Realm 中除了一个模型之外的所有模型。有什么办法可以不列出所有这些吗? 也许是一种遍历 Realm 中当前存在的所有类型的方法? 最佳答案 您可以从您的 R
我正在尝试使用 alias 指令模拟一个 Eloquent 模型,如下所示: $transporter = \Mockery::mock('alias:' . Transporter::class)
我正在使用 stargazer 创建我的 plm 汇总表。 library(plm) library(pglm) data("Unions", package = "pglm") anb1 <- pl
我读了几篇与 ASP.NET 分层架构相关的文章和问题,但是读得太多后我有点困惑。 UI 层是在 ASP.NET MVC 中开发的,对于数据访问,我在项目中使用 EF。 我想通过一个例子来描述我的问题
我收到此消息错误: Inceptionv3.mlmodel: unable to read document 我下载了最新版本的 xcode。 9.4 版测试版 (9Q1004a) 最佳答案 您没有
(同样,一个 MVC 验证问题。我知道,我知道......) 我想使用 AutoMapper ( http://automapper.codeplex.com/ ) 来验证我的创建 View 中不在我
需要澄清一件事,现在我正在处理一个流程,其中我有两个 View 模型,一个依赖于另一个 View 模型,为了处理这件事,我尝试在我的基本 Activity 中注入(inject)两个 View 模型,
如果 WPF MVVM 应该没有代码,为什么在使用 ICommand 时,是否需要在 Window.xaml.cs 代码中实例化 DataContext 属性?我已经并排观看并关注了 YouTube
当我第一次听说 ASP.NET MVC 时,我认为这意味着应用程序由三个部分组成:模型、 View 和 Controller 。 然后我读到 NerdDinner并学习了存储库和 View 模型的方法
Platform : ubuntu 16.04 Python version: 3.5.2 mmdnn version : 0.2.5 Source framework with version :
我正在学习本教程:https://www.raywenderlich.com/160728/object-oriented-programming-swift ...并尝试对代码进行一些个人调整,看看
我正试图围绕 AngularJS。我很喜欢它,但一个核心概念似乎在逃避我——模型在哪里? 例如,如果我有一个显示多个交易列表的应用程序。一个列表向服务器查询匹配某些条件的分页事务集,另一个列表使用不同
我在为某个应用程序找出最佳方法时遇到了麻烦。我不太习惯取代旧 TLA(三层架构)的新架构,所以这就是我的来源。 在为我的应用程序(POCO 类,对吧??)设计模型和 DAL 时,我有以下疑问: 我的模
我有两个模型:Person 和 Department。每个人可以在一个部门工作。部门可以由多人管理。我不确定如何在 Django 模型中构建这种关系。 这是我不成功的尝试之一 [models.py]:
我是一名优秀的程序员,十分优秀!