具有IDataErrorInfo的ViewModel总是在设置属性后调用getter
本文关键字:属性 设置 调用 getter IDataErrorInfo ViewModel 具有 | 更新日期: 2023-09-27 18:22:43
我想给这个问题尽可能多的上下文,但总的来说,我基本上问了两个问题:
- 当setter没有抛出异常时,WPF是否总是在设置绑定属性后调用getter
- 当ViewModel实现
IDataErrorInfo
时,是否可以防止在setter中发生错误后调用绑定属性的getter
我目前有一个Model类,它通过从属性setter抛出异常来实现验证。此外,许多属性是耦合的,因此修改其中一个属性的值可能会导致重新计算其他几个属性。它实现INotifyPropertyChanged
,以便在重新计算发生时向外部侦听器发出警报。它看起来像这样:
public class Model : INotifyPropertyChanged
{
private double property1;
public double Property1
{
get { return property1; }
set
{
if (value < 0.0)
throw new Exception("Property1 cannot be less than 0.0.");
property1 = value;
OnPropertyChanged(new PropertyChangedEventArgs("Property1"));
}
}
// ...Everything needed to support INotifyPropertyChanged...
}
最初,我为这个类实现了ViewModel,以充当模型属性的精简包装器,每当发生错误时(标记无效数据、禁用按钮等),都会提供额外的视图特定行为:
public class ViewModel : INotifyPropertyChanged
{
private readonly Model model;
public ViewModel()
{
model = new Model();
model.PropertyChanged += (sender, args) => OnPropertyChanged(args);
}
public string Property1
{
get { return model.Property1.ToString(); }
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch (Exception)
{
// Perform any view-specific actions
throw;
}
}
}
// ...Everything needed to support INotifyPropertyChanged
}
值得注意的是,ViewModel没有任何额外的支持字段;它的所有属性都直接链接到模型中的相应属性,并且来自模型的任何PropertyChanged
通知都由ViewModel传递。
但是,我经常听说使用异常进行验证可能会受到限制,我也开始意识到这一点,特别是在这个应用程序中对交叉耦合验证规则的需求增加的时候。
我不想改变模型的行为,因为它已经在其他几个地方使用了,但我开始改变ViewModel以实现IDataErrorInfo
:
public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private readonly Model model;
public ViewModel()
{
model = new Model();
model.PropertyChanged += (sender, args) =>
{
errorList.Remove(args.PropertyName);
OnPropertyChanged(args);
};
}
public string Property1
{
get { return model.Property1.ToString(); }
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch(Exception ex)
{
// Perform any view-specific actions
errorList["Property1"] = ex.Message;
}
}
}
private readonly Dictionary<string, string> errorList = new Dictionary<string, string>();
public string this[string propertyName]
{
get
{
string errorMessage;
if (errorList.TryGetValue(propertyName, out errorMessage))
return errorMessage;
return String.Empty;
}
}
public string Error { get { return String.Empty; } }
// ...Everything needed to support INotifyPropertyChanged
}
然而,这导致了行为上的剧烈变化。当使用异常时,在用户输入无效值后,将显示控件的Validation.ErrorTemplate
,并且无效值保留在控件中,从而为用户提供纠正错误的机会。当使用IDataErrorInfo
时,WPF似乎在setter完成后调用属性getter。由于每当发生错误时,模型都不会更改,因此无效值将替换为以前的值。所以现在我有一个显示Validation.ErrorTemplate
的控件,但显示了一个VALID值!
WPF在没有收到PropertyChanged
通知的情况下(在窗口初始化后)自动调用属性getter,这对我来说似乎很疯狂。而且它不会在抛出异常后尝试调用getter,那么为什么在使用IDataErrorInfo
时会这样做呢?
我是不是做错了什么导致了这种行为?当ViewModel实现IDataErrorInfo
时,在setter中发生错误后,是否有方法防止WPF调用ViewModel中的属性getter?
我曾尝试在ViewModel中添加后备字段来存储无效值,但由于Model属性可以在ViewModel之外进行修改(这就是它最初实现INotifyPropertyChanged
的原因),因此该解决方案最终非常复杂,并且在具有不同技能水平的程序员的环境中基本上是不可持续的。
以下是您希望在MVVM 中进行表单验证的方法
您的型号
public class Product:IDataErrorInfo
{
public string ProductName {get;set;}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "ProductName":
validationResult = ValidateName();
}
}
}
}
然后在您的ViewModel 中
public string ProductName
{
get { return currentProduct.ProductName; }
set
{
if (currentProduct.ProductName != value)
{
currentProduct.ProductName = value;
base.OnPropertyChanged("ProductName");
}
}
}
另一个考虑因素是,当我想验证数字时(例如您的双重验证),请将模型保持为具有双重而不是字符串
public double? Property1 {get;set;}
那么你可以做这个
<Textbox Name="myDoubleTextbox" >
<Binding ValidatesOnDataErrors="true" Path="Property1" TargetValueNull="" />
/>
因此,当他们在双框中键入不正确的内容时,它会向您的模型发送null,您可以对此进行检查。
虽然它不能回答我提出的问题,但我的同事建议,只要出现错误,就可以通过向errorList
添加无效值并修改ViewModel中的getter来返回无效值,从而实现所需的行为。
因此ViewModel中的属性如下所示:
public string Property1
{
get
{
ErrorInfo error;
if (errorList.TryGetValue("Property1", out error))
return error.Value;
return model.Property1.ToString();
}
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch (Exception ex)
{
// Perform any view-specific actions
errorList["Property1"] = new ErrorInfo(value, ex.Message);
}
}
}
对IDataErrorInfo
方法进行以下更新:
private struct ErrorInfo
{
public readonly string Value;
public readonly string Message;
public ErrorInfo(string value, string message)
{
Value = value;
Message = message;
}
}
private readonly Dictionary<string, ErrorInfo> errorList = new Dictionary<string, ErrorInfo>();
public string this[string propertyName]
{
get
{
ErrorInfo error;
if (errorList.TryGetValue(propertyName, out error))
return error.Message;
return String.Empty;
}
}
public string Error { get { return String.Empty; } }
这允许PropertyChanged
事件处理程序保持不变,并且只需要对属性getter和setter进行小的更改。