2011-09-02 53 views
9

Tengo un ObservableCollection y un UserControl de WPF está vinculado a los datos. El Control es un gráfico que muestra una barra vertical para cada elemento de tipo BarData en ObservableCollection.cómo ordenar ObservableCollection

ObservableCollection<BarData> 

class BarData 
{ 
    public DateTime StartDate {get; set;} 
    public double MoneySpent {get; set;} 
    public double TotalMoneySpentTillThisBar {get; set;} 
} 

Ahora quiero resolver el ObservableCollection basado en StartDate modo que los de BarData estarán en orden creciente de StartDate en la colección. entonces puedo calcular los valores de TotalMoneySpentTillThisBar en cada BarData como este -

var collection = new ObservableCollection<BarData>(); 
//add few BarData objects to collection 
collection.Sort(bar => bar.StartData); // this is ideally the kind of function I was looking for which does not exist 
double total = 0.0; 
collection.ToList().ForEach(bar => { 
            bar.TotalMoneySpentTillThisBar = total + bar.MoneySpent; 
            total = bar.TotalMoneySpentTillThisBar; 
            } 
          ); 

Sé que puedo utilizar ICollectionView para ordenar, filtrar los datos para veiwing pero eso no quiere cambiar la colección real. Necesito ordenar la colección real para poder calcular TotalMoneySpentTillThisBar para cada artículo. Su valor depende del orden de los elementos en la colección.

Gracias.

+0

¿Se trata de una tarea de una sola vez, es decir, algo que se puede hacer antes de que la colección esté ligada al control? –

+0

la colección sigue cambiando incluso cuando está vinculada (esa es la razón por la que estoy usando ObservableCollection para que la UI se actualice si la colección cambia). Una opción para resolver este problema es manejarlo mientras agrego un elemento a la colección para asegurarme de que está insertado en el índice correcto según el orden de clasificación o la segunda opción es ordenar la colección cada vez que se agrega o elimina un elemento. Estoy tratando de evaluar la segunda opción aquí. –

+1

En mi opinión, es un error de diseño que el objeto en sí mismo sepa cuánto dinero se ha gastado hasta ahora y que esta información depende del pedido. Esta debería ser una característica del control de usuario ('ShowTotal = true'). –

Respuesta

10

El problema con la ordenación de un ObservableCollection es que cada vez que cambie la colección, un evento se disparará. Entonces, para un tipo que está eliminando elementos de una posición y agregándolos a otra, terminará teniendo toneladas de eventos disparando.

Creo que lo mejor es insertar las cosas en el ObservableCollection en el orden correcto para empezar. La eliminación de elementos de la colección no afectará el orden. Saqué un método de extensión rápida para ilustrar

public static void InsertSorted<T>(this ObservableCollection<T> collection, T item, Comparison<T> comparison) 
    { 
     if (collection.Count == 0) 
      collection.Add(item); 
     else 
     { 
      bool last = true; 
      for (int i = 0; i < collection.Count; i++) 
      { 
       int result = comparison.Invoke(collection[i], item); 
       if (result >= 1) 
       { 
        collection.Insert(i, item); 
        last = false; 
        break; 
       } 
      } 
      if (last) 
       collection.Add(item); 
     } 
    } 

Así que si usted fuera a utilizar cadenas (por ejemplo), el código se vería así

 ObservableCollection<string> strs = new ObservableCollection<string>(); 
     Comparison<string> comparison = new Comparison<string>((s1, s2) => { return String.Compare(s1, s2); }); 
     strs.InsertSorted("Mark", comparison); 
     strs.InsertSorted("Tim", comparison); 
     strs.InsertSorted("Joe", comparison); 
     strs.InsertSorted("Al", comparison); 

Editar

Puede mantenga las llamadas idénticas si extiende el ObservableCollection y proporciona sus propios métodos de inserción/adición. Algo como esto:

public class BarDataCollection : ObservableCollection<BarData> 
{ 
    private Comparison<BarData> _comparison = new Comparison<BarData>((bd1, bd2) => { return DateTime.Compare(bd1.StartDate, bd2.StartDate); }); 

    public new void Insert(int index, BarData item) 
    { 
     InternalInsert(item); 
    } 

    protected override void InsertItem(int index, BarData item) 
    { 
     InternalInsert(item); 
    } 

    public new void Add(BarData item) 
    { 
     InternalInsert(item); 
    } 

    private void InternalInsert(BarData item) 
    { 
     if (Items.Count == 0) 
      Items.Add(item); 
     else 
     { 
      bool last = true; 
      for (int i = 0; i < Items.Count; i++) 
      { 
       int result = _comparison.Invoke(Items[i], item); 
       if (result >= 1) 
       { 
        Items.Insert(i, item); 
        last = false; 
        break; 
       } 
      } 
      if (last) 
       Items.Add(item); 
     } 
    } 
} 

El índice de inserción se ignora.

 BarData db1 = new BarData(DateTime.Now.AddDays(-1)); 
     BarData db2 = new BarData(DateTime.Now.AddDays(-2)); 
     BarData db3 = new BarData(DateTime.Now.AddDays(1)); 
     BarData db4 = new BarData(DateTime.Now); 
     BarDataCollection bdc = new BarDataCollection(); 
     bdc.Add(db1); 
     bdc.Insert(100, db2); 
     bdc.Insert(1, db3); 
     bdc.Add(db4); 
+0

Sí, tuve esta como la primera opción para insertar en el índice correcto, pero pensé que introduciría un poco de complejidad al agregar un elemento a la colección. Pero tu argumento sobre toneladas de eventos que se disparan es cierto y probablemente debería reconsiderarme sobre esto. –

+0

Puede extender ObserableCollection si desea que las llamadas no cambien. Agregué el código. – mdm20

13

acabo de crear una clase que extiende el ObservableCollection porque con el tiempo también he querido otra funcionalidad que esté acostumbrado a utilizar a partir de una List (Contains, IndexOf, AddRange, RemoveRange, etc)

que suelen utilizar con algo como

MyCollection.Sort(p => p.Name);

Aquí está mi tipo IMPLEMENTA ción

/// <summary> 
/// Expanded ObservableCollection to include some List<T> Methods 
/// </summary> 
[Serializable] 
public class ObservableCollectionEx<T> : ObservableCollection<T> 
{ 

    /// <summary> 
    /// Constructors 
    /// </summary> 
    public ObservableCollectionEx() : base() { } 
    public ObservableCollectionEx(List<T> l) : base(l) { } 
    public ObservableCollectionEx(IEnumerable<T> l) : base(l) { } 

    #region Sorting 

    /// <summary> 
    /// Sorts the items of the collection in ascending order according to a key. 
    /// </summary> 
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam> 
    /// <param name="keySelector">A function to extract a key from an item.</param> 
    public void Sort<TKey>(Func<T, TKey> keySelector) 
    { 
     InternalSort(Items.OrderBy(keySelector)); 
    } 

    /// <summary> 
    /// Sorts the items of the collection in descending order according to a key. 
    /// </summary> 
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam> 
    /// <param name="keySelector">A function to extract a key from an item.</param> 
    public void SortDescending<TKey>(Func<T, TKey> keySelector) 
    { 
     InternalSort(Items.OrderByDescending(keySelector)); 
    } 

    /// <summary> 
    /// Sorts the items of the collection in ascending order according to a key. 
    /// </summary> 
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam> 
    /// <param name="keySelector">A function to extract a key from an item.</param> 
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param> 
    public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer) 
    { 
     InternalSort(Items.OrderBy(keySelector, comparer)); 
    } 

    /// <summary> 
    /// Moves the items of the collection so that their orders are the same as those of the items provided. 
    /// </summary> 
    /// <param name="sortedItems">An <see cref="IEnumerable{T}"/> to provide item orders.</param> 
    private void InternalSort(IEnumerable<T> sortedItems) 
    { 
     var sortedItemsList = sortedItems.ToList(); 

     foreach (var item in sortedItemsList) 
     { 
      Move(IndexOf(item), sortedItemsList.IndexOf(item)); 
     } 
    } 

    #endregion // Sorting 
} 
+4

Cuando llame a Sort(), obtendrá un evento CollectionChanged para cada elemento de la colección ... – mdm20

+0

¿Es posible deshabilitar las notificaciones durante el ordenamiento? –

+0

@romkyns. No estoy al tanto de que ObservableCollection apoye directamente la capacidad de deshabilitar la notificación de la forma en que lo hace BindingList (es decir, list.RaiseListChangedEvents = false). PERO puede anular el registro de su controlador antes del ordenamiento y volver a registrarse luego. – Berryl

26

hummm primera pregunta que tengo para ti es: es realmente importante que su ObservableCollection se ordena, o es lo que realmente quiere es tener la pantalla de interfaz gráfica de usuario ordenados?

Supongo que el objetivo es tener una pantalla ordenada que se actualizará "en tiempo real".Entonces veo 2 soluciones

  1. obtener el ICollectionView de su ObservableCollection y ordenar que, como se explica aquí http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/

  2. unen su ObservableCollection a un CollectionViewsource, añaden una especie en él, a continuación, utilizar ese CollectionViewSource como el ItemSource de un ListView.

es decir:

agregar este espacio de nombres

xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" 

continuación

<CollectionViewSource x:Key='src' Source="{Binding MyObservableCollection, ElementName=MainWindowName}"> 
    <CollectionViewSource.SortDescriptions> 
     <scm:SortDescription PropertyName="MyField" /> 
    </CollectionViewSource.SortDescriptions> 

</CollectionViewSource> 

y unirse como éste

<ListView ItemsSource="{Binding Source={StaticResource src}}" > 
+0

ok me extrañé por completo el punto lo siento ... – Gregfr

+0

Luego, para tratar de responder a su pregunta, creo que crearía una nueva clase que heredaría de ObservableCollection. Luego, anularía el constructor para recalcular TotalMoneySpentTillThisBar para cada elemento. Algo así como para cada artículo obtiene la colección del artículo con una fecha este más pronto que la actual, haga la suma y actualice la corriente. Luego, anule Add() con un mecanismo similar para cada nueva instancia agregada en la colección, y use un ICollectionViewSource para ordenar la pantalla – Gregfr

+0

+1 para ordenar fuera de ObservableCollection: así es como debe hacerse. – Doug

1

¿Qué pasa con la clasificación de los datos utilizando LINQ en la colección diferente:

var collection = new List<BarData>(); 
//add few BarData objects to collection 

// sort the data using LINQ 
var sorted = from item in collection orderby item.StartData select item; 

// create observable collection 
var oc = new ObservableCollection<BarData>(sorted); 

Esto funcionó para mí.

0

También utilizando LINQ/Extensionmethod se puede disparar el evento NotifyPropertyChanged al no establecer el origen de la columna en el ordenado, pero borrar el original y agregar los elementos de la ordenada. (esto continuará activando el evento de cambio de colección, si se implementa).

<Extension> 
Public Sub SortByProp(Of T)(ByRef c As ICollection(Of T), PropertyName As String) 
    Dim l = c.ToList 
    Dim sorted = l.OrderBy(Function(x) x.GetType.GetProperty(PropertyName).GetValue(x)) 

    c.Clear() 
    For Each i In sorted 
     c.Add(i) 
    Next 

End Sub 
Cuestiones relacionadas