2011-01-17 17 views
9

Tengo una clase .NET que me gustaría mostrar en un DataGridView, y el enlace de datos predeterminado - al establecer el DataSource del DGV al objeto - produce El 90% de mis requisitos (es decir, está generando las propiedades públicas correctamente y puedo agregar clasificaciones fácilmente).WinForms DataGridView - databind a un objeto con una propiedad de lista (número variable de columnas)

Sin embargo, una de las propiedades que necesito vincular es una lista que contiene datos que deben estar en columnas separadas después de los otros elementos de datos. Estoy atascado sobre la mejor manera de implementar esto.

Mi clase es como la siguiente:

public class BookDetails 
{ 
    public string Title { get; set; } 
    public int TotalRating { get; set; } 
    public int Occurrence { get; set; } 
    public List<int> Rating { get; set; } 
} 

Idealmente, me gustaría ser capaz de ampliar esa propiedad Clasificación en una serie de columnas numéricas para dar una salida como esta en tiempo de ejecución:

Título | Puntuación total | Ocurrencia | R1 | R2 | R3 ... RN

También sería útil que la Calificación total se calcule como la suma de todas las calificaciones individuales, pero la actualizo manualmente en este momento sin problemas.

+0

Va a tener que implementar un TypeDescriptor (o quizás TypeConverter) para el tipo. Muy trivial si sabes qué hacer. Lamentablemente, un buen ejemplo que he escrito está en funcionamiento ahora. – leppie

+0

@leppie - TypeConverter no se aplica aquí; de hecho, ITypedList es probablemente el más fácil; después de eso - TypeDescriptionProvider (ya que no usará ICustomTypeDescriptor para una lista tipeada) –

+1

@leppie - debemos ser los únicos dos tontos que conozco lo suficientemente locos como para meterse con este rincón oscuro del framework; p –

Respuesta

18

Como esta?

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows.Forms; 
public class BookDetails 
{ 
    public string Title { get; set; } 
    public int TotalRating { get; set; } 
    public int Occurrence { get; set; } 
    public List<int> Rating { get; set; } 
} 
class BookList : List<BookDetails>, ITypedList 
{ 

    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) 
    { 
     var origProps = TypeDescriptor.GetProperties(typeof(BookDetails)); 
     List<PropertyDescriptor> newProps = new List<PropertyDescriptor>(origProps.Count); 
     PropertyDescriptor doThisLast = null; 
     foreach (PropertyDescriptor prop in origProps) 
     { 

      if (prop.Name == "Rating") doThisLast = prop; 
      else newProps.Add(prop); 
     } 
     if (doThisLast != null) 
     { 
      var max = (from book in this 
         let rating = book.Rating 
         where rating != null 
         select (int?)rating.Count).Max() ?? 0; 
      if (max > 0) 
      { 
       // want it nullable to account for jagged arrays 
       Type propType = typeof(int?); // could also figure this out from List<T> in 
               // the general case, but make it nullable 
       for (int i = 0; i < max; i++) 
       { 
        newProps.Add(new ListItemDescriptor(doThisLast, i, propType)); 
       } 
      } 
     } 
     return new PropertyDescriptorCollection(newProps.ToArray()); 
    } 

    public string GetListName(PropertyDescriptor[] listAccessors) 
    { 
     return ""; 
    } 
} 
class ListItemDescriptor : PropertyDescriptor 
{ 
    private static readonly Attribute[] nix = new Attribute[0]; 
    private readonly PropertyDescriptor tail; 
    private readonly Type type; 
    private readonly int index; 
    public ListItemDescriptor(PropertyDescriptor tail, int index, Type type) : base(tail.Name + "[" + index + "]", nix) 
    { 
     this.tail = tail; 
     this.type = type; 
     this.index = index; 
    } 
    public override object GetValue(object component) 
    { 
     IList list = tail.GetValue(component) as IList; 
     return (list == null || list.Count <= index) ? null : list[index]; 
    } 
    public override Type PropertyType 
    { 
     get { return type; } 
    } 
    public override bool IsReadOnly 
    { 
     get { return true; } 
    } 
    public override void SetValue(object component, object value) 
    { 
     throw new NotSupportedException(); 
    } 
    public override void ResetValue(object component) 
    { 
     throw new NotSupportedException(); 
    } 
    public override bool CanResetValue(object component) 
    { 
     return false; 
    } 
    public override Type ComponentType 
    { 
     get { return tail.ComponentType; } 
    } 
    public override bool ShouldSerializeValue(object component) 
    { 
     return false; 
    } 
} 
static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     var data = new BookList { 
      new BookDetails { Title = "abc", TotalRating = 3, Occurrence = 2, Rating = new List<int> {1,2,1}}, 
      new BookDetails { Title = "def", TotalRating = 3, Occurrence = 2, Rating = null }, 
      new BookDetails { Title = "ghi", TotalRating = 3, Occurrence = 2, Rating = new List<int> {3, 2}}, 
      new BookDetails { Title = "jkl", TotalRating = 3, Occurrence = 2, Rating = new List<int>()}, 
     }; 
     Application.Run(new Form 
     { 
      Controls = { 
       new DataGridView { 
        Dock = DockStyle.Fill, 
        DataSource = data 
       } 
      } 
     }); 

    } 
} 
+0

(También he pasado por alto todo el asunto del "descriptor de acceso a la lista" - si lo necesita *, es solo un caso de seguir cada llamada a través de la cadena) –

+0

+1 Genial, un poco más complicado de lo que pensaba, debido a tener una cantidad variable de elementos en la Lista :) – leppie

+0

¡Perfecto! Funciona como un encanto para mí. Muchas gracias :) – vitorbal

Cuestiones relacionadas