1

我想尽可能多地为这个问题提供背景信息,但总的来说,我基本上是在问两个问题:

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

我目前有一个模型类,它通过从属性设置器抛出异常来实现验证。此外,许多属性是耦合的,因此修改其中一个的值可能会导致重新计算其他几个。它实现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 没有任何额外的支持字段。它的所有属性都直接链接到 Model 中的相应属性,并且PropertyChanged来自 Model 的任何通知都由 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 !

PropertyChanged在我看来,WPF 会在没有收到通知的情况下自动调用属性获取器(在窗口初始化之后),这似乎很疯狂。并且它不会在抛出异常后尝试调用getter,那么为什么在IDataErrorInfo使用时会这样做呢?

我做错了什么导致这种行为吗?有没有办法阻止 WPF 在 ViewModel 实现时在 setter 中发生错误后调用 ViewModel 中的属性 getter IDataErrorInfo

我尝试将支持字段添加到 ViewModel 以存储无效值,但由于可以在 ViewModel 之外修改模型属性(这就是它INotifyPropertyChanged首先实现的原因),解决方案最终变得非常复杂并且在具有不同技能水平的程序员的环境中基本上是不可持续的。

4

2 回答 2

0

这是您希望在 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 发送到您的模型,您可以对其进行检查。

于 2013-07-16T14:09:00.070 回答
0

虽然它没有回答我提出的问题,但我的同事建议可以通过将无效值添加到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 进行小的更改。

于 2013-07-16T14:25:36.493 回答