gpt4 book ai didi

c# - 将 INotifyDataErrorInfo 与模型中需要自定义验证的子对象一起使用

转载 作者:太空宇宙 更新时间:2023-11-03 12:45:05 24 4
gpt4 key购买 nike

我正在尝试实现 INotifyDataErrorInfo,我的模型有一些自定义类型,需要根据其使用情况进行不同的验证。我不确定如何实现此验证。

我试图在下面创建一个简单的示例来展示我正在尝试完成的工作。我不是在寻找有关更改模型的建议,因为我的实际模型要复杂得多。

简单示例

我的示例模型适用于将有主持人和嘉宾的媒体事件。在安排媒体事件时,用户将输入姓名、最小和最大演示者以及最小和最大 guest 。通常,一个媒体必须有至少 1 名主持人且不超过 5 名,并且必须至少有 10 名嘉宾且不超过 50 名。

我有以下类,取自在线示例,用作我的模型类的基础。

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace NotifyDataErrorInfo
{
public class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
public ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();

public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;

if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}

ValidateAsync();
}

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;

if (handler != null)
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
}

public IEnumerable GetErrors(string propertyName)
{
if (propertyName == null) return null;

List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);

return errorsForName;
}

public bool HasErrors
{
get
{
return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0);
}
}

public Task ValidateAsync()
{
return Task.Run(() => Validate());
}

private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();

Validator.TryValidateObject(this, validationContext, validationResults, true);

foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}

var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;

foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();

if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}

_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
}
}

因为我在两个地方使用最小值和最大值,所以我创建了以下类来存储最小值和最大值。这是我示例中过于简化的部分,但应该能说明问题。

namespace NotifyDataErrorInfo
{
public class MinMaxValues : ValidatableModel
{
private int min;
private int max;

public int Min
{
get
{
return min;
}

set
{
if (!min.Equals(value))
{
min = value;
RaisePropertyChanged(nameof(Min));
OnErrorsChanged(nameof(Min));
}
}
}

public int Max
{
get
{
return max;
}

set
{
if (!max.Equals(value))
{
max = value;
RaisePropertyChanged(nameof(Max));
OnErrorsChanged(nameof(Max));
}
}
}

public MinMaxValues()
{
Min = 0;
Max = 0;
}
}
}

这是我的 MediaEvent 类,您可以看到它正在为 MinMaxPresenters 和 MinMaxGuests 使用 MinMaxValues 类。

using System.ComponentModel.DataAnnotations;

namespace NotifyDataErrorInfo
{
public class MediaEvent: ValidatableModel
{
private string name;
private MinMaxValues minMaxPresenters;
private MinMaxValues minMaxGuests;

public MediaEvent()
{
name = string.Empty;
minMaxPresenters = new MinMaxValues();
minMaxGuests = new MinMaxValues();

this.Validate();
this.minMaxPresenters.Validate();
this.minMaxGuests.Validate(); }
}

[Required]
[StringLength(10, MinimumLength = 5)]
public string Name
{
get
{
return name;
}

set
{
if(!name.Equals(value))
{
name = value;
RaisePropertyChanged(nameof(Name));
}
}
}

public MinMaxValues MinMaxPresenters
{
get
{
return minMaxPresenters;
}

set
{
if (!minMaxPresenters.Equals(value))
{
minMaxPresenters = value;
RaisePropertyChanged(nameof(MinMaxPresenters));
}
}
}

public MinMaxValues MinMaxGuests
{
get
{
return minMaxGuests;
}

set
{
if (!minMaxGuests.Equals(value))
{
minMaxGuests = value;
RaisePropertyChanged(nameof(MinMaxGuests));
}
}
}
}
}

这是我的主窗口的 XAML

<Window 
x:Class="NotifyDataErrorInfo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NotifyDataErrorInfo"
mc:Ignorable="d"
Title="MainWindow"
Height="209" Width="525"
ResizeMode="NoResize">

<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>

<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition Height="42*"/>
<RowDefinition Height="43*"/>
<RowDefinition Height="42*"/>
<RowDefinition Height="43*"/>
</Grid.RowDefinitions>

<Label
Content="Meeting Name: "
Grid.Row="0" Grid.Column="0"/>

<TextBox
Text="{Binding Name}"
Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3"/>

<Label
Content="Min Presenters: "
Grid.Row="1" Grid.Column="0"/>

<TextBox
Text="{Binding MinMaxPresenters.Min}"
Grid.Row="1" Grid.Column="1"/>

<Label
Content="Max Presenters: "
Grid.Row="1" Grid.Column="2"/>

<TextBox
Text="{Binding MinMaxPresenters.Max}"
Grid.Row="1" Grid.Column="3"/>

<Label
Content="Min Guests: "
Grid.Row="2" Grid.Column="0"/>

<TextBox
Text="{Binding MinMaxGuests.Min}"
Grid.Row="2" Grid.Column="1"/>

<Label
Content="Max Guests: "
Grid.Row="2" Grid.Column="2"/>

<TextBox
Text="{Binding MinMaxGuests.Max}"
Grid.Row="2" Grid.Column="3"/>

<Button
x:Name="TestButton"
Content="TEST"
Click="TestButton_Click"
Grid.Row="3" Grid.Column="3"/>
</Grid>
</Window>

使用

在 App.xaml.cs 中加载
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

var mainWindow = new MainWindow();
var mediaEvent = new MediaEvent();

mainWindow.DataContext = mediaEvent;
mainWindow.Show();
}

在 MediaEvent 类中,我用 [Required] 和 [StringLength(10, MinimumLength = 5)] 属性修饰了 Name 属性。这些按预期工作。当输入的名称短于 5 个字符或长于 10 个字符时,我会在名称文本框周围看到一个红色框,表明存在错误。

我想不通的是

现在我不确定如何对 MinMaxPresenters.Min、MinMaxPresenters.Max、MinMaxGuests.Min 和 MinMaxGuests.Max 进行验证

如果我用 [Range(1, 5)] 之类的东西装饰 MinMaxValues 类中的 Min 属性,我可以确认验证正在进行并且 UI 会相应更新。

问题是验证适用于演示者和 guest 的最小值。我需要为演示者和 guest 验证不同的最小值。

我尝试了什么

在 MediaEvent 中,我连接到 minMaxPresenters 的 PropertyChanged 事件。在该事件处理程序中,我尝试根据演示者规则(范围 = 1 到 5)验证最小值和最大值。如果验证失败,我会尝试添加到 _errors 集合。

在我的构造函数中我添加了

minMaxPresenters.PropertyChanged += MinMaxPresenters_PropertyChanged;

然后创建以下内容

private void MinMaxPresenters_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Min")
{
if (minMaxPresenters.Min < 1)
{
_errors.TryAdd("MinMaxPresenters.Min", new List<string> { "A media event requires at least 1 presenter" });
OnErrorsChanged("MinMaxPresenters.Min");
}
}
else if (e.PropertyName == "Max")
{
if (minMaxPresenters.Max <= minMaxPresenters.Min)
{
_errors.TryAdd("MinMaxPresenters.Max", new List<string> { "The max presenters must be greater than the min" });
OnErrorsChanged("MinMaxPresenters.Max");
}
else if (minMaxPresenters.Max > 5)
{
_errors.TryAdd("MinMaxPresenters.Max", new List<string> { "A media event can't have more than 5 presenters" });
OnErrorsChanged("MinMaxPresenters.Max");
}
}
}

当我输入超出演示者范围的最小值和最大值时,我可以看到我的错误被添加到我的模型中的 _errors 集合中,但我的 View 并不表示有任何错误。

我接近了吗?我这样做是不是错了?

我还需要根据其他属性值验证值,因此需要进行自定义验证并通过代码添加错误。一个例子是上面的最大值的验证。演示者的最大值需要小于 5,但也必须大于为最小值输入的值。

编辑

您可以忽略 MainWindow 中的按钮。只需单击并中断后面的代码,这样我就可以看到集合中有什么错误。

此外,如果有人对公开 _errors 发表评论,这只是尝试添加错误的一种快速方法。理想情况下,我会创建 AddError 和 RemoveError 方法。

最佳答案

你的问题在这里

_errors.TryAdd("MinMaxPresenters.Min", new List<string> 
{ "A media event requires at least 1 presenter" });

您正在将错误添加到父对象,但 WPF 绑定(bind)在属性链中的最后一个对象上查找错误。验证和 WPF 是一个令人头疼的问题。使用您的模型,您应该这样做

MinMaxPresenters._errors.TryAdd("Min", new List<string>
{ "A media event requires at least 1 presenter" });

然后错误将被 UI 拾取。

在我开发的框架中,我能够执行您最初尝试的操作,但我解析了错误字符串 "MinMaxPresenters.Min",然后查找名为 "MinMaxPresenters 的属性" 并自动将验证错误转发给子对象。

我对 AddErrors 的实现是

    public void AddErrors(string path, IEnumerable<Exception> errors, bool nest = true)
{
var exceptions = errors as IList<Exception> ?? errors.ToList();

var nestedPath = path.Split('.').ToList();
if (nestedPath.Count > 1 && nest)
{
var tail = string.Join(".", nestedPath.Skip(1));
// Try and get a child property as Maybe<INotifyDataExceptionInfo>
// and if it exists pass the error
// downwards after stripping off the first part of
// the path.
var notifyDataExceptionInfo = this.TryGet<INotifyDataExceptionInfo,INotifyDataExceptionInfo>(nestedPath[0]);
if(notifyDataExceptionInfo.IsSome)
notifyDataExceptionInfo.Value.AddErrors(tail, exceptions);
}

_Errors.RemoveKey(path);
foreach (var error in exceptions)
{
_Errors.Add(path, error);
}

RaiseErrorEvents(path);

}

** TryGet是一种通过反射获取属性值的方法

** 完整的实现可以在 this location 找到.

关于c# - 将 INotifyDataErrorInfo 与模型中需要自定义验证的子对象一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37602219/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com