ViewModel订阅Model的特定属性的PropertyChanged事件

6

当模型中的属性更改时,我希望执行methodToBeCalledWhenPropertyIsSet()方法。

我该如何做到这一点呢?

如果我理解正确的话,我可以在我的ViewModel中添加MyModel.PropertyChanged += methodToBeCalledWhenPropertyIsSet来订阅PropertyChanged事件,但我只关心当Property被设置时。

public class ViewModel : INotifyPropertyChanged
{
    ...

    public Model MyModel { get; set; }

    public void methodToBeCalledWhenPropertyIsSet() { }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Model : INotifyPropertyChanged
{
    object _propertyField;
    public object Property
    {
        get
        {
            return _propertyField;
        }
        set
        {
            _propertyField = value;
             OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

我不太明白你的意思。你能详细解释一下吗?或者给出一些例子? - dkozl
使用 void methodToBeCalledWhenPropertyIsSet(object sender, PropertyChangedEventArgs e),然后检查 if (e.PropertyName=="Property") - Bolu
@WilliamThomasWaller,订阅 PropertyChanged 事件有什么问题吗?当属性被设置时应该调用它。 - dkozl
我将尝试Bolu的建议,因为那似乎是正确的做法。 - William Thomas Waller
1
@WilliamThomasWaller 记住,如果您在 VM 中订阅模型的事件,则需要正确取消订阅。否则可能会导致内存泄漏。请参阅我的答案,它不需要任何额外的事件订阅。 - Federico Berasategui
显示剩余2条评论
2个回答

14

INotifyPropertyChanged 接口解决了这个问题。订阅你的视图模型到模型的 PropertyChangedEventHandler 并过滤你的结果。

public class ViewModel : INotifyPropertyChanged
{
    ...

    public Model MyModel { get; set; }

    public void methodToBeCalledWhenPropertyIsSet() { }

    public event PropertyChangedEventHandler PropertyChanged;

    public ViewModel()
    {
        // MyModel would need to be set in this example.
        MyModel.PropertyChanged += Model_PropertyChanged;
    }

    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "Property")
        {
             methodToBeCalledWhenPropertyIsSet();
        }
    }
}

MVVM模式中,ViewModel旨在处理这样混乱的情况。这仍然保持了您与Model之间的抽象。

编辑 正如HighCore指出的那样,这段代码不能直接复制粘贴。MyModel需要事先实例化。我使用MEF (http://msdn.microsoft.com/en-us/library/dd460648(v=vs.110).aspx) 来达到此目的。您可以直接获取模型类或使用某种工厂/管理器来获取引用。


1
我使用了你的解决方案,它按照我的需求工作,所以加一分,但我也会尝试HighCore的解决方案。谢谢! - William Thomas Waller
2
我更新了我的答案,解释说它并不打算被复制粘贴到一个解决方案中。如果使用MEF或像Unity(https://unity.codeplex.com/)这样的控制反转容器,它将解决这个问题。我同意这是强类型的,但我认为在视图模型中这并不是一件可怕的事情。我更关心从模型获取更新而不是过于强调类型。 - Daniel Johnson
问题:订阅PropertyChanged事件后,是更好地在ViewModel中使用switch/case按事件名称进行调度,还是可能在模型内部具有多个PropertyChangedEventHandler,并将其命名为prop1Changedprop2Changed?或者这会破坏内部的事件处理? - mefiX
@mefiX 我不认为有什么问题。我现在没有一个好的设置来测试,但这只是一个你要订阅的事件。如果你有多个监听器,这并不重要。根据你的情况,我可以看到两种方式都是有效的。 - Daniel Johnson

3

一种可能的常用解决方案是在ViewModel中使用等效的属性来包装Model的Property

public class ViewModel
{
    public object Property
    {
        get
        {
            return Model.Property;
        }
        set
        {
             Model.Property = value;
             methodToBeCalledWhenPropertyIsSet(); // Here you call your method directly
        }
    }
}

请将您的UI与此属性绑定,而不是Model的。

编辑:如果模型因UI交互而更改,则该交互将“通过”ViewModel进行。如果模型由于模型本身的内部业务逻辑而更改,则您将别无选择,只能订阅其PropertyChanged事件,但请确保稍后正确取消订阅。我通常将这种订阅/取消订阅代码放在VM中的Model属性的setter中:

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

        _model = value;

        if (_model != null
            _model.PropertyChanged += OnModelPropertyChanged;

        NotifyPropertyChange(() => Model);
     } 
}

当Model的属性发生变化时,ViewModel 的属性会发生变化吗?我可能把我的例子过于抽象化了,现在很难描述我实际上想做什么。我实际上有一个 Model 列表,并计划将我的 methodToBeCalled() 关联到它们所有的 PropertyChanged 事件。 - William Thomas Waller
+1. 我认为这是一种比我之前使用的更清晰的订阅/取消订阅方法。您有关于如何处理PropertyChanged事件以便对来自模型的更新做出反应的建议吗? - Daniel Johnson
@wirecat 我通常不这样做,因为我的代码大部分基于T4代码生成,所以我的所有VM基本上都是“克隆”模型并添加应用程序逻辑。我不将业务逻辑放入模型类中,因为我将它们用作DTO,但这只是我的个人习惯。由于我的VM也是平台无关的(存在于PCL而不是特定于WPF的程序集中),我倾向于将所有逻辑放入VM中。 - Federico Berasategui

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