2011-01-03 17 views
16

He agregado CollectionChanged eventhandler(onCollectionChanged) a uno de la propiedad ObservableCollection.Implementando CollectionChanged

He descubierto que el método onCollectionChanged se invoca solo en caso de agregar elementos o quitar elementos de la colección, pero no en el caso de que el elemento de colección se edite.

Me gustaría saber cómo enviar la lista/colección de elementos recién agregados, eliminados y editados en una sola colección.

Gracias.

Respuesta

36

Hay que añadir un detector PropertyChanged a cada elemento (que debe aplicar INotifyPropertyChanged) para obtener la notificación sobre la edición de objetos en una lista observable.

public ObservableCollection<Item> Names { get; set; } 
public List<Item> ModifiedItems { get; set; } 

public ViewModel() 
{ 
    this.ModifiedItems = new List<Item>(); 

    this.Names = new ObservableCollection<Item>(); 
    this.Names.CollectionChanged += this.OnCollectionChanged; 
} 

void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
{ 
    if (e.NewItems != null) 
    { 
     foreach(Item newItem in e.NewItems) 
     { 
      ModifiedItems.Add(newItem); 

      //Add listener for each item on PropertyChanged event 
      newItem.PropertyChanged += this.OnItemPropertyChanged;   
     } 
    } 

    if (e.OldItems != null) 
    { 
     foreach(Item oldItem in e.OldItems) 
     { 
      ModifiedItems.Add(oldItem); 

      oldItem.PropertyChanged -= this.OnItemPropertyChanged; 
     } 
    } 
} 

void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    Item item = sender as Item; 
    if(item != null) 
     ModifiedItems.Add(item); 
} 

Tal vez usted tiene que comprobar si algún objeto ya está en el ModifedItems-List (con el método de lista contiene (objeto obj)) y sólo añadir un nuevo elemento si el resultado de ese método es falsa.

La clase Item debe implementar INotifyPropertyChanged. Consulte esto example para saber cómo. Como dijo Robert Rossney, también puede hacerlo con IEditableObject, si tiene ese requisito.

+2

Esto generará una excepción en las operaciones Agregar y Eliminar, a menos que modifique la código para verificar si e.NewItems y e.OldItems son nulos antes de tratar de evitarlos. – Alain

+0

¿Por qué necesita esta lista ModifiedItems cuando tiene la colección Names? –

+0

Consulte mi edición de la respuesta anterior en http://stackoverflow.com/a/36398137/638977 –

1

I piensan que poblar la ObservableCollection con los objetos que implementen INotifyPropertyChanged hará que el evento CollectionChanged al fuego cuando un artículo plantea su notificación PropertyChanged.

Pensándolo bien, creo que debe usar BindingList<T> para que los cambios individuales de los elementos se propaguen de esta forma desde el primer momento.

De lo contrario, deberá suscribirse manualmente a las notificaciones de cambio de cada elemento y plantear el evento CollectionChanged. Tenga en cuenta que si está creando el suyo propio derivado ObservableCollection<T>, deberá suscribirse en la instanciación y en Add() y Insert(), y darse de baja en Remove(), RemoveAt() y Clear(). De lo contrario, puede suscribirse al evento CollectionChanged y usar los elementos agregados y eliminados de los eventos args para suscribirse/cancelar suscripción.

+3

Eso es correcto en que no se va a cambiar notificaciones cuando los elementos dentro del contenedor de cambio. –

+0

1up por mencionar BindingList. En realidad funciona bien con WPF y en mi humilde opinión, en mi opinión es mejor que ObservableCollection. – Riva

0

en winforms, BindingList es una práctica estándar. & en WPF Silverlight, generalmente se pegan a trabajar con ObservableCollection y necesita escuchar para PropertyChanged en cada artículo

+0

Afaik BindingList en realidad funciona bastante bien con WPF. – Riva

2

INotifyCollectionChanged no es uno en el mismo con INotiftyPropertyChanged. Cambiar las propiedades de los objetos subyacentes de ninguna manera sugiere que la colección haya cambiado.

Una forma de lograr este comportamiento es crear una colección personalizada que interrogará al objeto una vez agregado y se registrará para el evento INotifyPropertyChanged.PropertyChanged; entonces necesitaría cancelar el registro apropiadamente cuando se elimine un artículo.

Una advertencia con este enfoque es cuando sus objetos están anidados en N niveles de profundidad. Para resolver esto, necesitará esencialmente interrogar cada propiedad usando reflexión para determinar si es quizás otra colección que implemente INotifyCollectionChanged u otro contenedor que deba atravesarse.

Aquí está un ejemplo rudimentario no-probado ...

public class ObservableCollectionExt<T> : ObservableCollection<T> 
    { 
     public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged; 

     protected override void SetItem(int index, T item) 
     { 
      base.SetItem(index, item); 

      if(item is INotifyPropertyChanged) 
       (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged); 
     } 

     protected override void ClearItems() 
     { 
      for (int i = 0; i < this.Items.Count; i++) 
       DeRegisterINotifyPropertyChanged(this.IndexOf(this.Items[i])); 

      base.ClearItems(); 
     } 

     protected override void InsertItem(int index, T item) 
     { 
      base.InsertItem(index, item); 
      RegisterINotifyPropertyChanged(item); 
     } 

     protected override void RemoveItem(int index) 
     { 
      base.RemoveItem(index); 
      DeRegisterINotifyPropertyChanged(index); 
     } 

     private void RegisterINotifyPropertyChanged(T item) 
     { 
      if (item is INotifyPropertyChanged) 
       (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged); 
     } 

     private void DeRegisterINotifyPropertyChanged(int index) 
     { 
      if (this.Items[index] is INotifyPropertyChanged) 
       (this.Items[index] as INotifyPropertyChanged).PropertyChanged -= OnPropertyChanged; 
     } 

     private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      T item = (T)sender; 
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, item)); 
     } 
    } 
+0

Esto fallará. El constructor de NotifyCollectionChangedEventArgs arroja la siguiente excepción cuando agrega un elemento como parámetro: 'System.ArgumentException: 'La acción de reinicio debe inicializarse sin elementos modificados' '. – tjmoore

10

ItemsControl escucha CollectionChanged para gestionar la visualización de la colección de elementos que presenta en la pantalla. A ContentControl escucha PropertyChanged para administrar la visualización del elemento específico que se presenta en la pantalla. Es muy fácil mantener los dos conceptos separados en tu mente una vez que comprendas esto.

El seguimiento de si un elemento se edita no es algo que haga cualquiera de estas interfaces. Los cambios de propiedad no son ediciones, es decir, no representan necesariamente algún tipo de cambio iniciado por el usuario en el estado del objeto. Por ejemplo, un objeto puede tener una propiedad ElapsedTime que se actualiza continuamente mediante un temporizador; la IU necesita ser notificada de estos eventos de cambio de propiedad, pero ciertamente no representan cambios en los datos subyacentes del objeto.

La forma estándar de rastrear si un objeto se edita es primero hacer que ese objeto implemente IEditableObject. Luego puede, internamente a la clase del objeto, decidir qué cambios constituyen una edición (es decir, requiere que llame al BeginEdit) y los cambios no. A continuación, puede implementar una propiedad booleana IsDirty que se establece cuando se llama y se borra BeginEdit cuando se invoca EndEdit o CancelEdit. (Realmente no entiendo por qué esa propiedad no es parte de IEditableObject; aún no he implementado un objeto editable que no lo requiera).

Por supuesto, no hay necesidad de implementar ese segundo nivel de abstracción si no la necesita; sin duda puede escuchar el evento PropertyChanged y solo asumir que el objeto ha sido editado si se lo plantea. Realmente depende de tus requisitos.

0

Utilice el siguiente código:

-mi modelo:

public class IceCream: INotifyPropertyChanged 
{ 
    private int liczba; 

    public int Liczba 
    { 
     get { return liczba; } 
     set { liczba = value; 
     Zmiana("Liczba"); 
     } 
    } 

    public IceCream(){} 

//in the same class implement the below-it will be responsible for track a changes 

    public event PropertyChangedEventHandler PropertyChanged; 

    private void Zmiana(string propertyName) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

Y en mi clase personList implantó el método responsable de activo aumentando el valor de uno después de clic de botón en AppBarControl

async private void Add_Click(object sender, RoutedEventArgs e) 
    { 
     List<IceCream> items = new List<IceCream>(); 
     foreach (IceCream item in IceCreamList.SelectedItems) 
     { 
      int i=Flavors.IndexOf(item); 
      Flavors[i].Liczba =item.Liczba+ 1; 
      //Flavors.Remove(item); 

      //item.Liczba += 1; 

      // items.Add(item); 
      // Flavors.Add(item); 
     } 

     MessageDialog d = new MessageDialog("Zwiększono liczbę o jeden"); 
     d.Content = "Zwiększono liczbę o jeden"; 
     await d.ShowAsync(); 


     IceCreamList.SelectedIndex = -1; 
    } 
} 

Espero que sea útil para alguien a Tenga en cuenta que:

private ObservableCollection<IceCream> Flavors; 

-

0

La solución más fácil que he encontrado para esta limitación, es quitar el elemento utilizando RemoveAt(index) continuación, añadir el artículo modificado en el mismo índice mediante InsertAt(index) y por lo tanto la ObservableCollection notificará a la vista.

1

Mi edición a 'this answer' se rechaza. Así que puse mi edición aquí:

void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
{ 
if (e.NewItems != null) 
{ 
    foreach(Item newItem in e.NewItems) 
    { 
     ModifiedItems.Add(newItem); 

     //Add listener for each item on PropertyChanged event 
     if (e.Action == NotifyCollectionChangedAction.Add) 
      newItem.PropertyChanged += this.ListTagInfo_PropertyChanged; 
     else if (e.Action == NotifyCollectionChangedAction.Remove) 
      newItem.PropertyChanged -= this.ListTagInfo_PropertyChanged; 
    } 
} 

// MSDN: OldItems:Gets the list of items affected by a Replace, Remove, or Move action. 
//if (e.OldItems != null) <--- removed 
}