2011-03-15 6 views
10

ResumenCustom ObservableCollection <T> o BindingList <T> con soporte para notificaciones periódicas

I tienen un gran un conjunto de datos que cambia rápidamente que deseo de unirse a una interfaz de usuario (Datagrid con la agrupación). Los cambios están en dos niveles;

  • se añaden productos que con frecuencia o se elimina de la colección (500 un segundo cada trayecto)
  • Cada artículo tiene un 4 propiedades que cambiarán hasta 5 veces en su vida útil

Las características de los datos son los siguientes;

  • Hay ~ 5000 elementos de la colección
  • puede menos de un segundo, se añadirá un artículo, entonces tienen 5 cambios de propiedades y luego se retira.
  • Un artículo también puede permanecer en algún estado provisional por un tiempo y debe mostrarse al usuario.

El requisito clave con el que estoy teniendo problemas;

  • El usuario debe ser capaz de ordenar el conjunto de datos por cualquier propiedad en el objeto

Lo que me gustaría hacer;

  • actualizar la interfaz de usuario sólo cada N segundos
  • presentar solamente los NotifyPropertyChangedEvents pertinentes

Si el artículo 1 tiene un estado de propiedad, que se mueve de A -> B -> C - > D en el intervalo Necesito/quiero solo un evento de 'Estado' que se va a generar, A-> D.

Aprecio que un usuario no necesite tener la UI actualizada miles de veces por segundo. si se agrega un elemento, se cambia su estado y se elimina todo dentro de la ventana de N segundos entre las actualizaciones de UI, nunca debe golpear DataGrid.

DataGrid

La cuadrícula de datos es el componente que estoy usando para mostrar los datos. Actualmente estoy usando XCeed DataGrid ya que proporciona una agrupación dinámica trivial. No estoy emocionalmente involucrado en él, el stock DataGrid estaría bien si pudiera proporcionar algunas opciones de agrupamiento dinámico (que incluye las propiedades que cambian con frecuencia).

El cuello de botella en mi sistema es actualmente en el tiempo necesario para reordenar cuando las propiedades de un elemento cambian

Esto toma el 98% de la CPU en el YourKit Profiler.

una forma diferente de expresar la pregunta

Habida cuenta de dos casos/ObservableCollection BindingList que eran inicialmente idénticos pero la primera lista desde entonces ha tenido una serie de actualizaciones adicionales (que se puede escuchar para), genere el conjunto mínimo de cambios para convertir una lista en otra.

lectura externa

Lo que necesito es un equivalente de este ArrayMonitor por George Tryfonas pero generalizado para apoyar la adición y eliminación de elementos (que nunca se moverán).

NB Realmente apreciaría que alguien edite el título de la pregunta si pueden pensar en un mejor resumen.

EDITAR - Mi solución

La rejilla XCEED se une a las células directamente a los elementos de la rejilla mientras que el & funcionalidad de agrupación de clasificación es impulsado por el ListChangedEvents planteadas en el BindingList. Esto es ligeramente contrario a la intuición y descarta MontioredBindingList a continuación, ya que las filas se actualizarían antes que los grupos.

En lugar de eso, envuelvo los artículos por sí mismos, atrapando los eventos cambiados en la propiedad y almacenándolos en un HashSet como sugirió Daniel. Esto funciona bien para mí, repito periódicamente los artículos y les pido que notifiquen cualquier cambio.

MonitoredBindingList.cs

Aquí está mi intento de una lista de enlaces que pueden ser sondeada en busca de notificaciones de actualización. Es probable que haya algunos errores con él, ya que no fue útil para mí al final.

Crea una cola de Agregar/Eliminar eventos y realiza un seguimiento de los cambios a través de una lista. ChangeList tiene el mismo orden que la lista subyacente, de modo que, después de que hayamos notificado las operaciones de agregar/eliminar, puede aumentar los cambios con respecto al índice correcto.

/// <summary> 
/// A binding list which allows change events to be polled rather than pushed. 
/// </summary> 
[Serializable] 

public class MonitoredBindingList<T> : BindingList<T> 
{ 
    private readonly object publishingLock = new object(); 

    private readonly Queue<ListChangedEventArgs> addRemoveQueue; 
    private readonly LinkedList<HashSet<PropertyDescriptor>> changeList; 
    private readonly Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>> changeListDict; 

    public MonitoredBindingList() 
    { 
     this.addRemoveQueue = new Queue<ListChangedEventArgs>(); 
     this.changeList = new LinkedList<HashSet<PropertyDescriptor>>(); 
     this.changeListDict = new Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>>(); 
    } 

    protected override void OnListChanged(ListChangedEventArgs e) 
    { 
     lock (publishingLock) 
     { 
      switch (e.ListChangedType) 
      { 
       case ListChangedType.ItemAdded: 
        if (e.NewIndex != Count - 1) 
         throw new ApplicationException("Items may only be added to the end of the list"); 

        // Queue this event for notification 
        addRemoveQueue.Enqueue(e); 

        // Add an empty change node for the new entry 
        changeListDict[e.NewIndex] = changeList.AddLast(new HashSet<PropertyDescriptor>()); 
        break; 

       case ListChangedType.ItemDeleted: 
        addRemoveQueue.Enqueue(e); 

        // Remove all changes for this item 
        changeList.Remove(changeListDict[e.NewIndex]); 
        for (int i = e.NewIndex; i < Count; i++) 
        { 
         changeListDict[i] = changeListDict[i + 1]; 
        } 

        if (Count > 0) 
         changeListDict.Remove(Count); 
        break; 

       case ListChangedType.ItemChanged: 
        changeListDict[e.NewIndex].Value.Add(e.PropertyDescriptor); 
        break; 
       default: 
        base.OnListChanged(e); 
        break; 
      } 
     } 
    } 

    public void PublishChanges() 
    { 
     lock (publishingLock) 
      Publish(); 
    } 

    internal void Publish() 
    { 
     while(addRemoveQueue.Count != 0) 
     { 
      base.OnListChanged(addRemoveQueue.Dequeue()); 
     } 

     // The order of the entries in the changeList matches that of the items in 'this' 
     int i = 0; 
     foreach (var changesForItem in changeList) 
     { 
      foreach (var pd in changesForItem) 
      { 
       var lc = new ListChangedEventArgs(ListChangedType.ItemChanged, i, pd); 
       base.OnListChanged(lc); 
      } 
      i++; 
     } 
    } 
} 

Respuesta

5

Estamos hablando de dos cosas aquí:

  1. Los cambios en la colección. Esto plantea el evento INotifyCollectionChanged.CollectionChanged
  2. Los cambios en las propiedades de los elementos. Esto genera el evento INotifyPropertyChanged.PropertyChanged

La interfaz INotifyCollectionChanged necesita ser ejecutado por su colección personalizada. La interfaz INotifyPropertyChanged necesita ser implementada por sus artículos. Además, el evento PropertyChanged solo le dice qué propiedad se modificó en un artículo pero no cuál fue el valor anterior.
Esto significa, sus artículos deben tener una aplicación que va más o menos así:

  • Tener un contador de tiempo que pasa cada N segundo
  • Crear una HashSet<string> que contiene los nombres de todas las propiedades que han sido cambiado Debido a que es un conjunto, cada propiedad solo puede contenerse una o cero veces.
  • Cuando se cambia una propiedad, agregue su nombre al conjunto de hash si no está ya en él.
  • Cuando el temporizador haya transcurrido, plantee el evento PropertyChanged para todas las propiedades en el conjunto de hash y desactívelo después.

Su colección tendría una implementación similar. Sin embargo, es un poco más difícil porque necesita contabilizar los elementos que se han agregado y eliminado entre los eventos del temporizador. Esto significa que, cuando se agrega un elemento, lo debe agregar a un hash que establece "addedItems". Si se elimina un elemento, lo agrega a un conjunto de hash "removedItems", si aún no está en "addedItems". Si ya está en "AddedItems", elimínelo de allí. Creo que te haces una idea.

Para cumplir con el principio de separación de preocupaciones y responsabilidad única, sería incluso mejor tener sus elementos implementar INotifyPropertyChanged de la manera predeterminada y crear un contenedor que realice la consolidación de los eventos. Eso tiene la ventaja de que sus artículos no están llenos de código que no pertenece allí y este contenedor se puede hacer genérico y se puede usar para cada clase que implemente INotifyPropertyChanged.
Lo mismo vale para la colección: puede crear un contenedor genérico para todas las colecciones que implementan INotifyCollectionChanged y dejar que el contenedor realice la consolidación de los eventos.

+0

Hola Daniel - Gracias por una respuesta tan rápida y en profundidad. He estado tratando de lograr exactamente lo que describes aunque hayas corregido un par de errores que estaba cometiendo. Cuando haya realizado la clase, la volveré a publicar aquí para que cualquiera pueda usarla o adaptarla. Tengo una pregunta adicional, en mi CustomObservableCollection, ¿qué evento debo plantear cuando un artículo cambia? ¿O cómo propago 'INotifyPropertyChanged' a la cuadrícula de datos? 'NotifyCollectionChangedAction.Replace' no parece correcto. – CityView

+0

@CityView: no es tarea de la colección notificar a la cuadrícula de datos sobre los cambios en las propiedades de sus elementos. Cuando vincula una colección a una cuadrícula de datos, la cuadrícula de datos se une al evento CollectionChanged de su colección y al evento PropertyChanged de cada elemento individual que se muestra. –

+0

@Daniel: Tiene sentido: este no es el caso de [XCeed Grid] (http://xceed.com/CS/blogs/dontpanic/archive/2009/04/01/i-notify-we-notify-we -all-wait-no-we-don-t.aspx) que es por lo que me estaba confundiendo. Para Exceed necesitarías extender BindingList, parecería. Gracias, publicaré mi intento hoy. – CityView

Cuestiones relacionadas