2011-06-11 80 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"),以便在重構工具幫助下完成重命名。儘管如此,我不認爲重命名經常發生,但它確實避免了一些字符串文字。