2011-06-11 84 views
9

某些属性上我viewmodel更新相关属性使用MVVM

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"); 
     }; 
} 

有没有更优雅的方式来做到这一点?

回答

9

关于Count,你根本不需要这样做。只需绑定到Tasks.Count,您的绑定将通过ObservableCollection得到通知。

Completed是一个不同的故事,因为这是ObservableCollection之外。尽管如此,从抽象/界面的层面来看,你真的想要Completed成为该集合的一个属性。

对于这一点,我认为更好的方法是创建“子”视图模型为您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属性部分。我们仍然没有捕捉到完成项目数量真正发生变化的情况,但我们已经在一定程度上减少了冗余通知的数量。

现在视图模型只是有属性:

public TasksViewModel Tasks { get; set; } 

...你可以绑定到TasksTasks.CountTasks.Completed轻松。


作为替代方案,如果你宁愿创建“主”视图模型这些特性,你可以把一个子类ObservableCollection<T>这个概念来打造一个具有一些方法,你可以在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>

public class ViewModel : INotifyPropertyChanged 
{ 
    public ViewModel() 
    { 
     var tasks = new ObservableCollectionWithSubscribers<Task>(); 
     tasks.SubscribeToChanges(Notify, "Completed"); 
     Tasks = tasks; 
    } 

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

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

    public event PropertyChangedEventHandler PropertyChanged; 
    void Notify(string property) 
    { 
     var handler = PropertyChanged; 
     if(handler != null) handler(this, new PropertyChangedEventArgs(property)); 
    } 
} 
+1

这当然是一个更好的设计。 – 2011-06-11 02:52:12

4

看起来很优雅。我真的不知道你会如何更简洁。

(如何奇怪,写这样一个答案。如果有人确实有一些这样的内容更优雅,我可能会删除此。)

好吧,我注意到一两件事,无关的原问题:你的Tasks属性有一个公开的二传手。使其成为private set;,或者您需要实施带有后台字段的set,以便您可以删除先前实例上的代理,替换并连接新代理,并使用“任务”,“计数”和“计数”执行OnPropertyChanged完成”。 (而且看到Tasks是如何在构造函数中设置,我猜private set;是更好的选择。)

不作通知有关CountCompleted更优雅,但它修复了一个错误。

许多MVVM框架从lambda中获取属性名称,因此您可以使用OnPropertyChanged(() => Count)来代替OnPropertyChanged("Count"),以便在重构工具帮助下完成重命名。尽管如此,我不认为重命名经常发生,但它确实避免了一些字符串文字。