Ordenar columnas personalizadas en un DataGridView enlazado a un BindingList

Tengo un DataGridView que está vinculado a datos a un BindingList. Mi DataGridView también tiene un par de columnas personalizadas que he agregado. Estos no están vinculados a datos, sino que se generan en función de los elementos de mi BindingList (es decir, un elemento de mi BindingList de tipo A tiene una propiedad de tipo B; mi columna personalizada muestra B.Name (EDIT: En este caso, "Name" es una propiedad de la clase B y, por lo tanto, la propiedad representada por la columna no se encuentra directamente en los elementos de BindingList)).

Necesito poder ordenar todas las columnas en mi DataGridView. DataGridView tiene dos métodos de clasificación: Sort (IComparer) y Sort (DataGridViewColumn, ListSortDirection). Utilizo el segundo para ordenar mis columnas vinculadas a datos, pero, por supuesto, arroja una excepción cuando se usa en una columna no vinculada a datos. El primer método generará una excepción si DataSource no es nulo.

Por lo tanto, ninguno de los métodos de ordenación integrados de DataGridView funcionará hasta donde yo sé. ¿De qué otra manera puedo ordenar mi cuadrícula en función de mis columnas personalizadas?

EDIT:

Lo que hago en este momento es manejar el clic en el encabezado de la columna, siguiendo las instrucciones que se ven aquí: http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.columnheadermouseclick.aspx

El problema surge en la línea:

dataGridView1.Sort(newColumn, direction);

Las cosas funcionan muy bien cuando newColumn tiene una de las propiedades de un objeto en mi BindingList. Pero para ordenar una de mis columnas personalizadas, tendré que evitar esta línea por completo y encontrar otra forma de ordenar la cuadrícula de datos en función de esa columna. ¿Eso significa tener que construir mi propia función de clasificación? Parece que puede ser un dolor enorme.

preguntado el 16 de mayo de 11 a las 17:05

1 Respuestas

FINAL Editar / Responder: No puedo pensar en una forma de hacer esto mientras sigo usando el mecanismo de clasificación integrado en DataGridView. Si estuviera en su lugar, probablemente cambiaría el SortMode de cada columna a "Programático" y luego manejaría el "ColumnHeaderMouseClick" usted mismo. En ese punto, debe llamar a un método de clasificación en su clase BindingList modificada que realizará la clasificación según sea necesario de acuerdo con la columna en la que se hizo clic. Esto evita utilizar el método Sort del DGV y ordena la lista subyacente directamente.

Discurso completo Ubicado en la sección de comentarios. La respuesta original sigue inmediatamente:

EDIT: Debido a cierta confusión sobre el problema y la posterior discusión al respecto, tengo una nueva sugerencia en los comentarios de esta Respuesta. Dejo la respuesta original que publiqué para que podamos hacer referencia a ella.

Recientemente tuve que hacer esto, y no voy a mentir, fue un verdadero dolor. Se me ocurrió una solución (con la ayuda de algunos amigos aquí en SO), así que aquí va. Creé una nueva interfaz basada en IComparer que le permite especificar tanto una columna como una dirección. Solo hice esto porque necesito que mi código de clasificación sea lo más genérico posible; tengo DOS cuadrículas que deben ordenar de esta manera y no quiero mantener el doble del código. Aquí está la interfaz, bastante simple:

   public interface IByColumnComparer : IComparer
   {
      string SortColumn { get; set; }
      bool SortDescending { get; set; }
   }

Obviamente, si no le preocupa mantener las cosas genéricas (probablemente debería hacerlo), esto no es estrictamente necesario. Luego, construí una nueva clase que se basa en BindingList <>. Esto me permitió anular el código de clasificación y proporcionar mi propio IByColumnComparer columna por columna que es lo que me permitió la flexibilidad que necesitaba. Mira esto:

public class SortableGenericCollection<T> : BindingList<T>
{
  IByColumnComparer GenericComparer = null; 

  public SortableGenericCollection(IByColumnComparer SortingComparer)
  {
     GenericComparer = SortingComparer;
  }


  protected override bool SupportsSortingCore
  {
     get
     {
        return true;
     }
  }

  protected override bool IsSortedCore
  {
     get
     {
        for (int i = 0; i < Items.Count - 1; ++i)
        {
           T lhs = Items[i];
           T rhs = Items[i + 1];
           PropertyDescriptor property = SortPropertyCore;
           if (property != null)
           {
              object lhsValue = lhs == null ? null :
              property.GetValue(lhs);
              object rhsValue = rhs == null ? null :
              property.GetValue(rhs);
              int result;
              if (lhsValue == null)
              {
                 result = -1;
              }
              else if (rhsValue == null)
              {
                 result = 1;
              }
              else
              {
                 result = GenericComparer.Compare(lhs, rhs); 
              }
              if (result >= 0)
              {
                 return false;
              }
           }
        }
        return true;
     }
  }

  private ListSortDirection sortDirection;
  protected override ListSortDirection SortDirectionCore
  {
     get
     {
        return sortDirection;
     }
  }

  private PropertyDescriptor sortProperty;
  protected override PropertyDescriptor SortPropertyCore
  {
     get
     {
        return sortProperty;
     }
  }

  protected override void ApplySortCore(PropertyDescriptor prop,
  ListSortDirection direction)
  {
     sortProperty = prop;
     sortDirection = direction;

     GenericComparer.SortColumn = prop.Name;
     GenericComparer.SortDescending = direction == ListSortDirection.Descending ? true : false;

     List<T> list = (List<T>)Items;
     list.Sort(delegate(T lhs, T rhs)
     {
        if (sortProperty != null)
        {
           object lhsValue = lhs == null ? null :
           sortProperty.GetValue(lhs);
           object rhsValue = rhs == null ? null :
           sortProperty.GetValue(rhs);
           int result;
           if (lhsValue == null)
           {
              result = -1;
           }
           else if (rhsValue == null)
           {
              result = 1;
           }
           else
           {
              result = GenericComparer.Compare(lhs, rhs);
           }
           return result;
        }
        else
        {
           return 0;
        }
     });
  }

  protected override void RemoveSortCore()
  {
     sortDirection = ListSortDirection.Ascending;
     sortProperty = null;
  }
}

Ahora, como puede ver en el método ApplySortCore, estoy recibiendo la columna y la dirección directamente desde DataGridView, lo que significa que no estoy llamando a esto mediante programación. Eso no suena como lo que quiere hacer, pero podría modificar fácilmente este código si necesita llamarlo programáticamente y pasar el IByColumnComparer apropiado. Mi punto al mostrarte todo esto es para que puedas entender cómo modificar el algoritmo de clasificación, lo cual es bastante útil.

Un agradecimiento especial a @MartinhoFernandes por las sugerencias relativas a hacer esta clase más genérica.

contestado el 17 de mayo de 11 a las 23:05

Perdóname si no entiendo bien tu respuesta, pero no creo que esto resuelva mi problema. Ya tengo un código similar a este (una lista de enlace personalizada con el método ApplySortCore, etc.). El problema es que las columnas que intento ordenar no están vinculadas a BindingList. Por lo tanto, cuando el método Sort intenta llamar a ApplySortCore, lanza una excepción porque la columna que está tratando de ordenar no existe en BindingList. - C Walker

Ok, no debo entender esta oración correctamente: "Estos no están vinculados a datos, sino que se generan en función de los elementos de mi BindingList (es decir, un elemento de mi BindingList de tipo A tiene una propiedad de tipo B; mi columna personalizada muestra B.Name) ". Si la columna se "genera" en función de las propiedades de los elementos que están en su BindingList, entonces realmente está vinculada a los datos, ¿verdad? ¿No puedes probar el nombre de Prop en el método ApplySortCore? - Chris Barlow

Ah, creo que puedo ver la ambigüedad. No quise decir "Nombre" como en el nombre de la propiedad, sino que el Nombre es otra propiedad dentro de la clase B. Por lo tanto, los datos en la columna personalizada no están representados por ninguna de las propiedades en la clase A. En cualquier caso , no llega tan lejos como ApplySortCore, la excepción se lanza antes de eso. - C Walker

¡OK! Estoy contigo ahora. Entonces, hay una lista de enlace llena con un objeto de tipo A, y una de las propiedades del tipo A es el tipo B, y desea llenar (y ordenar) una columna de la cuadrícula usando una propiedad de tipo B. ¿Puede llenarla? ¿ahora? Si es así, ¿cómo lo está logrando? - Chris Barlow

Sí, eso es exactamente. Ahora mismo lo lleno usando el evento CellFormatting: msdn.microsoft.com/en-us/library/… . Básicamente, el controlador de eventos determina si la celda "actual" está contenida en una de mis columnas personalizadas, luego calcula programáticamente qué Valor poner en la celda. - C Walker

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.