2011-06-11 17 views
9

Algunas propiedades en mi viewmodel:propiedades dependientes de actualización usando el MVVM

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

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

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

Cuál es la mejor manera de actualizar estas propiedades cuando Tasks cambios?

Mi método actual:

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

¿Hay una manera más elegante de hacer esto?

Respuesta

9

Con respecto a Count, no tiene que hacer esto en absoluto. Simplemente enlace a Tasks.Count y sus enlaces serán notificados del cambio por el ObservableCollection.

Completed es una historia diferente, porque está fuera de ObservableCollection. Sin embargo, desde el nivel de la abstracción/interfaz, realmente desea que Completed sea propiedad de esa colección Tasks.

Por ello, creo que un mejor enfoque sería crear "sub" vista-modelo para su propiedad 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"); 
} 

Esto le da todas las ventajas de la ObservableCollection, y hace efectiva la Completed parte de la propiedad. Todavía no hemos capturado solo los casos en los que la cantidad de elementos completados realmente cambia, pero hemos reducido algo el número de notificaciones redundantes.

Ahora el modelo de vista sólo tiene la propiedad:

public TasksViewModel Tasks { get; set; } 

... y usted puede unirse a Tasks, Tasks.Count y Tasks.Completed con facilidad.


Como alternativa, si prefiere crear estas otras propiedades en la vista-modelo "principal", puede aprovechar esta noción de una subclase ObservableCollection<T> para crear uno con algún método en el que se puede pasar en una Action<string> delegar, que representaría elevar una notificación de cambio de propiedad en el modelo de vista principal y una lista de nombres de propiedad. Esta colección podría entonces aumentar de manera efectiva las notificaciones de cambio de propiedad de la vista-modelo:

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

Incluso se puede dejar el tipo de propiedad como 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

Ese es sin duda un mejor diseño. –

4

Parece bastante elegante para mí. Realmente no sé cómo lo harías más sucinto.

(Que raro escribir una respuesta como esta. Si alguien llega realmente a algo más elegante, que podría eliminar este.)

bien, me di cuenta de una cosa, sin relación con el original pregunta: Su propiedad Tasks tiene un organismo público. Conviértelo en private set;, o tendrá que implementar el set con un campo de respaldo para poder eliminar el delegado en la instancia anterior, reemplazar y conectar el nuevo, y hacer OnPropertyChanged con "Tareas", "Recuento" y " Terminado". (y ver cómo Tasks se establece en el constructor, supongo private set; es la mejor opción.)

no hace reciba una notificación sobre Count y Completed más elegante, pero corrige un error.

Y muchas estructuras MVVM obtienen el nombre de propiedad de una lambda, de modo que en lugar de OnPropertyChanged("Count"), puede escribir OnPropertyChanged(() => Count) para que siga los cambios realizados con la ayuda de herramientas de refactorización. No creo que el cambio de nombre ocurra con demasiada frecuencia, pero evita algunos literales de cadenas.

Cuestiones relacionadas