当模型更改时,刷新ViewModel所有属性的数据绑定的好方法是什么?

36

简短版本

如果我更新 ViewModel 包装的 Model 对象,有什么好的方法可以为 ViewModel 公开的所有模型属性触发属性更改通知?

详细版本

我正在开发一个遵循 MVVM 模式的 WPF 客户端,并尝试处理从服务接收到的用于显示数据的更新。当客户端接收到更新时,更新以 DTO 的形式出现,我将其用作 Model。

如果此模型是要更新现有视图中显示的模型,则希望相关联的 ViewModel 更新其数据绑定属性,以便视图反映更改。

举个例子说明。 考虑我的 Model:

class FooModel
{
  public int FooModelProperty { get; set; }
}

包装在 ViewModel 中:

class FooViewModel
{
  private FooModel _model;

  public FooModel Model 
  { 
    get { return _model; }
    set 
    { 
      _model = value; 
      OnPropertyChanged("Model"); 
    }
  }

  public int FooViewModelProperty
  {
    get { return Model.FooModelProperty; }
    set 
    {
      Model.FooModelProperty = value;
      OnPropertyChanged("FooViewModelProperty");
    }    
}

问题:

当一个更新的模型到达时,我会这样设置 ViewModel 的 Model 属性:

instanceOfFooVM.Model = newModel;

这会导致OnPropertyChanged("Model")被触发,但不会自动触发OnPropertyChanged("FooViewModelProperty"),除非我在Model的setter中显式地调用后者。因此,绑定到FooViewModelProperty的视图在更改Model并更新其属性时不会显示该属性的新值。

显式地为每个公开的Model属性调用OnPropertyChanged显然不是一个理想的解决方案,也不是获取newModel并逐个更新ViewModel属性的最佳方案。

针对这个需要更新整个模型并且需要触发所有已公开属性的变化通知的问题,有哪些更好的解决方案?

4个回答

68
根据文档 (传送门)

PropertyChanged 事件可以使用 null 或 String.Empty 作为 PropertyChangedEventArgs 中的属性名称,以指示对象上的所有属性均已更改。


2
我确信我曾经在某个时候读过这个,但我已经完全忘记了。当ViewModel的整个Model发生变化时,这似乎是最好的方法。非常感谢! - Dan J
1
我经常使用此方法来刷新对象。我不建议在每次属性更改后都这样做,但对于初始对象加载,它非常有效。 - Brady
如果使用MVVM-light,这将会出现一个错误。不幸的是,这里没有实际的解决方案,但请参见http://stackoverflow.com/questions/3554831。 - Simon_Weaver
4
我的测试结果显示只有 String.Empty 起作用,Null 对我没有效果。 - Alon Catz
@Alon Catz没错,我也尝试使用null作为propertyName的值,但并没有更新所有绑定类的属性。然而,使用string.Empty作为propertyName的值确实可以更新所有绑定类的属性。 - Eido95
@AlonCatz 我同意,null 不起作用。应该使用 "" 或者 string.Empty - StayOnTarget

7
一种方法是监听您自己的事件,并创建一个辅助程序,根据需要触发其他通知。
这可以很简单地在构造函数中添加:
public FooViewModel()
{
    this.PropertyChanged += (o,e) =>
      {
          if (e.PropertyName == "Model")
          {
               OnPropertyChanged("FooViewModelProperty");
               // Add other properties "dependent" on Model here...
          }
      };
}

我本来期望在答案中看到类似AOP的东西,但我真的很喜欢简单明了的方法。点赞 :)。也许另一种方法是将所有依赖OnPropertyChanged调用提取到一个单独的方法中,并从FooModel的setter中调用它。 - Anvaka
@Anvaka:内部有一个帮助程序可以通过“AddDepedentProperty”和表达式树来完成此操作...这是一个有用的选项。 - Reed Copsey
感谢您提供这种方法。然而,它的缺点是需要维护一组OnPropertyChanged调用来处理ViewModel公开的所有属性的问题;例如,当添加属性时,必须保持此集合最新。我相信@Joe White已经找到了我需要的... - Dan J
@djacobson:这也是一个不错的选择,但要注意它会重新绑定到该对象的每个属性 - 如果您的 VM 是一个简单的模型包装器,那么它就很完美 - 但如果它包含其他应用程序逻辑,则可能会触发更多的通知。 - Reed Copsey
好的建议,我一定会记住的!在我的情况下,我认为这将是一个值得权衡的抉择……直到被证明不然。 :) - Dan J

1
每当设置您的“Model”属性时,请订阅其自身的“PropertyChanged”事件。当调用处理程序时,触发自己的“PropertyChanged”事件。当“Model”设置为其他内容时,请从旧的“Model”中删除处理程序。
例如:
class FooViewModel
{
    private FooModel _model;

    public FooModel Model 
    { 
        get { return _model; }
        set 
        { 
            if (_model != null)
            {
                _model.PropertyChanged -= ModelPropertyChanged;
            }

            if (value != null)
            {
                value.PropertyChanged += ModelPropertyChanged;
            }

            _model = value; 
            OnPropertyChanged("Model"); 
        }
    }

    public int FooViewModelProperty
    {
        get { return Model.FooModelProperty; }
        set 
        {
            Model.FooModelProperty = value;
            OnPropertyChanged("FooViewModelProperty");
        } 
    }

    private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Here you will need to translate the property names from those
        // present on your Model to those present on your ViewModel.
        // For example:
        OnPropertyChanged(e.PropertyName.Replace("FooModel", "FooViewModel"));
    }
}

0
    Implements INotifyPropertyChanged
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged 

    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(String.Empty))

对于需要VB.net的人。如果您已经实现了“INotifyPropertyChanged”,那么最后一行就是您所需的全部内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接