繁体   English   中英

带有IDataErrorInfo的ViewModel总是在设置属性后调用getter吗?

[英]ViewModel with IDataErrorInfo always calls getter after setting property?

我想给这个问题尽可能的上下文,但是总而言之,我基本上是在问两个问题:

  • 当setter不会引发异常时,WPF是否总是在设置绑定属性之后调用getter?
  • ViewModel实现IDataErrorInfo时,是否可以防止在setter中发生错误后调用绑定属性的getter?

我目前有一个Model类,该类通过抛出属性设置器中的异常来实现验证。 此外,许多属性是耦合的,因此修改其中一个属性的值可能导致重新计算其他多个属性。 它实现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传递。

但是,我经常听到使用异常进行验证可能会受到限制,并且我开始意识到这一点,特别是因为此应用程序对交叉耦合验证规则的需求有所增加。

我不想更改Model的行为,因为它已经在其他几个地方使用了,但是我打算更改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,所以为什么在使用IDataErrorInfo时会这样做呢?

我做错什么了导致这种现象吗? 当ViewModel实现IDataErrorInfo时,有什么方法可以防止WPF在setter中发生错误之后调用ViewModel中的属性getter?

我尝试将后备字段添加到ViewModel来存储无效值,但是由于可以在ViewModel外部修改Model属性(这是它首先实现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进行少量更改。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM