使用MVVM更新依赖属性

时间:2011-06-11 02:18:57

标签: c# wpf mvvm

viewmodel上的一些属性:

public ObservableCollection<Task> Tasks { get; set; }

public int Count
{
    get { return Tasks.Count; }
}

public int Completed
{
    get { return Tasks.Count(t => t.IsComplete); }
}

Tasks更改时更新这些属性的最佳方法是什么?

我目前的方法

public TaskViewModel()
{
    Tasks = new ObservableCollection<Task>(repository.LoadTasks());
    Tasks.CollectionChanged += (s, e) => 
        {
            OnPropertyChanged("Count");
            OnPropertyChanged("Completed");
        };
}

有更优雅的方法吗?

2 个答案:

答案 0 :(得分:9)

关于Count,您根本不需要这样做。只需绑定到Tasks.Count,您的绑定就会通过ObservableCollection收到有关更改的通知。

Completed是一个不同的故事,因为它超出了ObservableCollection。不过,从抽象/界面的层面来看,你真的希望Completed成为Tasks集合的属性。

为此,我认为更好的方法是为您的Tasks属性创建“子”视图模型:

public class TasksViewModel : ObservableCollection<Task>
{
    public int Completed
    {
        get { return this.Count(t => t.IsComplete); }
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if(e.PropertyName == "Count") NotifyCompletedChanged();
    }

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        NotifyCompletedChanged();
    }

    void NotifyCompletedChanged()
    {
        OnPropertyChanged(_completedChangedArgs);
    }
    readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}

这为您提供了ObservableCollection的所有好处,并有效地使Completed属性成为其中的一部分。我们仍然没有捕获完成项目数量确实发生变化的情况,但我们在一定程度上减少了冗余通知的数量。

现在viewmodel只有属性:

public TasksViewModel Tasks { get; set; }

...您可以轻松绑定到TasksTasks.CountTasks.Completed


作为替代方案,如果您希望在“主”视图模型上创建这些其他属性,则可以使用子类ObservableCollection<T>的概念来创建一个具有某种方法的概念,您可以在其中传入{ {1}}委托,表示在主视图模型上提出属性更改通知,以及一些属性名称列表。然后,此集合可以有效地在视图模型上引发属性更改通知:

Action<string>

您甚至可以将属性类型保留为public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T> { Action<string> _notificationAction = s => { }; // do nothing, by default readonly IList<string> _subscribedProperties = new List<string>(); public void SubscribeToChanges(Action<string> notificationAction, params string[] properties) { _notificationAction = notificationAction; foreach (var property in properties) _subscribedProperties.Add(property); } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); NotifySubscribers(); } protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); NotifySubscribers(); } void NotifySubscribers() { foreach (var property in _subscribedProperties) _notificationAction(property); } }

ObservableCollection<Task>

答案 1 :(得分:4)

对我来说相当优雅。我真的不知道你怎么做得更简洁。

(写这样的答案有多奇怪。如果有人提出更优雅的东西,我可能会删除它。)

好的,我注意到一件事,与原来的问题无关:你的Tasks财产有一个公共制定者。设为private set;,或者您需要使用支持字段实现set,这样您就可以删除前一个实例上的委托,替换并连接新的委托,并执行{{1}使用“任务”,“计数”和“已完成”。 (看看如何在构造函数中设置OnPropertyChanged,我猜测Tasks是更好的选择。)

不会通知private set;Count更优雅,但它修复了错误。

许多MVVM框架从lambda获取属性名称,因此您可以编写Completed而不是OnPropertyChanged("Count"),以便在重构工具的帮助下完成重命名。我不认为重命名经常发生,但它确实避免了一些字符串文字。