¿Cómo vinculo un WPF DataGrid a un número variable de columnas?

Resuelto Generic Error asked hace 16 años • 8 respuestas

Mi aplicación WPF genera conjuntos de datos que pueden tener un número diferente de columnas cada vez. En el resultado se incluye una descripción de cada columna que se utilizará para aplicar el formato. Una versión simplificada del resultado podría ser algo como:

class Data
{
    IList<ColumnDescription> ColumnDescriptions { get; set; }
    string[][] Rows { get; set; }
}

Esta clase está configurada como DataContext en un WPF DataGrid pero en realidad creo las columnas mediante programación:

for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
    dataGrid.Columns.Add(new DataGridTextColumn
    {
        Header = data.ColumnDescriptions[i].Name,
        Binding = new Binding(string.Format("[{0}]", i))
    });
}

¿Hay alguna forma de reemplazar este código con enlaces de datos en el archivo XAML?

Generic Error avatar Nov 26 '08 15:11 Generic Error
Aceptado

A continuación se ofrece una solución alternativa para vincular columnas en DataGrid. Dado que la propiedad Columnas es de solo lectura, como todos notaron, creé una propiedad adjunta llamada BindableColumns que actualiza las columnas en DataGrid cada vez que la colección cambia a través del evento CollectionChanged.

Si tenemos esta colección de DataGridColumn

public ObservableCollection<DataGridColumn> ColumnCollection
{
    get;
    private set;
}

Luego podemos vincular BindableColumns a ColumnCollection de esta manera

<DataGrid Name="dataGrid"
          local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
          AutoGenerateColumns="False"
          ...>

La propiedad adjunta BindableColumns

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;
        ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (columns == null)
        {
            return;
        }
        foreach (DataGridColumn column in columns)
        {
            dataGrid.Columns.Add(column);
        }
        columns.CollectionChanged += (sender, e2) =>
        {
            NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
            if (ne.Action == NotifyCollectionChangedAction.Reset)
            {
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Move)
            {
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
            }
            else if (ne.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (DataGridColumn column in ne.OldItems)
                {
                    dataGrid.Columns.Remove(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Replace)
            {
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
            }
        };
    }
    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }
    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}
Fredrik Hedblad avatar Dec 07 '2010 18:12 Fredrik Hedblad

Continué mi investigación y no encontré ninguna forma razonable de hacerlo. La propiedad Columnas en DataGrid no es algo a lo que pueda vincularme; de ​​hecho, es de solo lectura.

Bryan sugirió que se podría hacer algo con AutoGenerateColumns, así que eché un vistazo. Utiliza una reflexión .Net simple para observar las propiedades de los objetos en ItemsSource y genera una columna para cada uno. Quizás podría generar un tipo sobre la marcha con una propiedad para cada columna, pero esto se está desviando mucho.

Dado que este problema se soluciona tan fácilmente en el código, seguiré con un método de extensión simple al que llamo cada vez que el contexto de datos se actualiza con nuevas columnas:

public static void GenerateColumns(this DataGrid dataGrid, IEnumerable<ColumnSchema> columns)
{
    dataGrid.Columns.Clear();

    int index = 0;
    foreach (var column in columns)
    {
        dataGrid.Columns.Add(new DataGridTextColumn
        {
            Header = column.Name,
            Binding = new Binding(string.Format("[{0}]", index++))
        });
    }
}

// E.g. myGrid.GenerateColumns(schema);
Generic Error avatar Dec 05 '2008 10:12 Generic Error

Encontré un artículo de blog de Deborah Kurata con un buen truco sobre cómo mostrar un número variable de columnas en un DataGrid:

Llenar un DataGrid con columnas dinámicas en una aplicación Silverlight usando MVVM

Básicamente, ella crea DataGridTemplateColumny coloca ItemsControldentro una que muestra varias columnas.

Lukas Cenovsky avatar Jan 29 '2011 11:01 Lukas Cenovsky