2008-11-26 6 views
112

Mi aplicación WPF genera conjuntos de datos que pueden tener un número diferente de columnas cada vez. Se incluye en el resultado una descripción de cada columna que se utilizará para aplicar el formato. Una versión simplificada de la salida podría ser algo como:¿Cómo puedo vincular un WGD DataGrid a un número variable de columnas?

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

Esta clase se establece como el DataContext en un WPF DataGrid pero en realidad crear 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 manera de reemplazar este código con enlaces de datos en el archivo XAML en su lugar?

Respuesta

113

Aquí es una solución para las columnas de unión en el DataGrid. Dado que la propiedad Columns es ReadOnly, como todos notaron, hice una propiedad adjunta llamada BindableColumns que actualiza las columnas en el DataGrid cada vez que la colección cambia a través del evento CollectionChanged.

Si tenemos esta colección de

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

BindableColumns Entonces podemos unir de DataGridColumn a la ColumnCollection como esto

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

El Attached Propiedad BindableColumns

+3

Esto es la respuesta correcta, Bravo! –

+0

Acepto, me gusta más esta solución. – Jaime

+1

buena solución para el patrón MVVM – WPFKK

1

Puede hacer esto con AutoGenerateColumns y un DataTemplate. No estoy seguro si funcionaría sin mucho trabajo, tendrías que jugar con eso. Honestamente, si ya tienes una solución de trabajo, yo no haría el cambio por ahora a menos que haya una gran razón. El control DataGrid se está volviendo muy bueno, pero aún necesita algo de trabajo (y todavía me queda mucho por aprender) para poder hacer tareas dinámicas como esta fácilmente.

+0

Mi razón es que viene de ASP.Net Soy nuevo en lo que se puede hacer con el enlace de datos decente y estoy no estoy seguro de dónde están sus límites. Jugaré con AutoGenerateColumns, gracias. –

17

He continuado mi investigación y no he encontrado ninguna forma razonable de hacerlo. La propiedad Columns en DataGrid no es algo contra 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 simple reflexión .Net para observar las propiedades de los objetos en ItemsSource y genera una columna para cada uno. Tal vez podría generar un tipo sobre la marcha con una propiedad para cada columna, pero esto se está desviando.

Dado que este problema es tan fácil sovled en el código Voy a seguir con un simple método de extensión que llamo siempre 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); 
+0

¿Qué hace 'index'? –

+1

¡La solución más votada y aceptada no es la mejor! Dos años después, la respuesta sería: http://msmvps.com/blogs/deborahk/archive/2011/01/23/populating-a-datagrid-with-dynamic-columns-in-a-silverlight-application-using- mvvm.aspx – Mikhail

+4

No, no lo haría. No es el enlace proporcionado de todos modos, porque el resultado de esa solución es completamente diferente. – 321X

2

Usted puede crear un control de usuario con la definición de la red y defina controles 'secundarios' con definiciones de columna variadas en xaml. El padre necesita una propiedad de dependencia para las columnas y un método para la carga de las columnas:

Padres:


public ObservableCollection<DataGridColumn> gridColumns 
{ 
    get 
    { 
    return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty); 
    } 
    set 
    { 
    SetValue(ColumnsProperty, value); 
    } 
} 
public static readonly DependencyProperty ColumnsProperty = 
    DependencyProperty.Register("gridColumns", 
    typeof(ObservableCollection<DataGridColumn>), 
    typeof(parentControl), 
    new PropertyMetadata(new ObservableCollection<DataGridColumn>())); 

public void LoadGrid() 
{ 
    if (gridColumns.Count > 0) 
    myGrid.Columns.Clear(); 

    foreach (DataGridColumn c in gridColumns) 
    { 
    myGrid.Columns.Add(c); 
    } 
} 

Xaml Niño:


<local:parentControl x:Name="deGrid">   
    <local:parentControl.gridColumns> 
    <toolkit:DataGridTextColumn Width="Auto" Header="1" Binding="{Binding Path=.}" /> 
    <toolkit:DataGridTextColumn Width="Auto" Header="2" Binding="{Binding Path=.}" /> 
    </local:parentControl.gridColumns> 
</local:parentControl> 

Y, finalmente, , la parte difícil es encontrar dónde llamar a 'LoadGrid '.
estoy luchando con esto, pero tengo cosas que trabajar llamando después InitalizeComponent en mi constructor de la ventana (childGrid es x: nombre en window.xaml):

childGrid.deGrid.LoadGrid(); 

Related blog entry

9

Me han encontrado una artículo de blog de Deborah Kurata con un buen truco de cómo mostrar varia ble número de columnas de una cuadrícula de datos:

Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM

Básicamente, se crea un DataGridTemplateColumn y pone ItemsControl interior que muestra varias columnas.

+1

¡Funciona perfectamente tanto para Silverlight como para WPF! – Mikhail

+1

¡No es el mismo resultado que la versión programada! – 321X

+0

@ 321X: ¿Podría explicar cuáles son las diferencias observadas (y también especificar a qué se refiere con * versión programada *, ya que todas las soluciones están programadas), por favor? –

5

me las arreglé para que sea posible añadir dinámicamente una columna usando sólo una línea de código como este:

MyItemsCollection.AddPropertyDescriptor(
    new DynamicPropertyDescriptor<User, int>("Age", x => x.Age)); 

Con respecto a la cuestión, esto no es una solución basada en XAML (ya que como se ha mencionado que hay no hay forma razonable de hacerlo), tampoco es una solución que funcionaría directamente con DataGrid.Columns. En realidad, funciona con DataSource enlazado ItemsSource, que implementa ITypedList y, como tal, proporciona métodos personalizados para la recuperación de PropertyDescriptor. En un lugar del código, puede definir "filas de datos" y "columnas de datos" para su cuadrícula.

Si desea tener:

IList<string> ColumnNames { get; set; } 
//dict.key is column name, dict.value is value 
Dictionary<string, string> Rows { get; set; } 

se puede utilizar por ejemplo:

var descriptors= new List<PropertyDescriptor>(); 
//retrieve column name from preprepared list or retrieve from one of the items in dictionary 
foreach(var columnName in ColumnNames) 
    descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName])) 
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors) 

y su rejilla usando unión a MyItemsCollection se rellena con columnas correspondientes. Esas columnas se pueden modificar (nuevas añadidas o eliminadas) en tiempo de ejecución de forma dinámica y la cuadrícula actualizará automáticamente su colección de columnas.

DynamicPropertyDescriptor mencionado anteriormente es solo una actualización de PropertyDescriptor regular y proporciona una definición de columnas fuertemente tipada con algunas opciones adicionales. DynamicDataGridSource de lo contrario funcionaría bien evento con PropertyDescriptor básico.

3

Se realizó una versión de la respuesta aceptada que maneja la cancelación de la suscripción.

public class DataGridColumnsBehavior 
{ 
    public static readonly DependencyProperty BindableColumnsProperty = 
     DependencyProperty.RegisterAttached("BindableColumns", 
              typeof(ObservableCollection<DataGridColumn>), 
              typeof(DataGridColumnsBehavior), 
              new UIPropertyMetadata(null, BindableColumnsPropertyChanged)); 

    /// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary> 
    private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers; 

    static DataGridColumnsBehavior() 
    { 
     _handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>(); 
    } 

    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 
    { 
     DataGrid dataGrid = source as DataGrid; 

     ObservableCollection<DataGridColumn> oldColumns = e.OldValue as ObservableCollection<DataGridColumn>; 
     if (oldColumns != null) 
     { 
      // Remove all columns. 
      dataGrid.Columns.Clear(); 

      // Unsubscribe from old collection. 
      NotifyCollectionChangedEventHandler h; 
      if (_handlers.TryGetValue(dataGrid, out h)) 
      { 
       oldColumns.CollectionChanged -= h; 
       _handlers.Remove(dataGrid); 
      } 
     } 

     ObservableCollection<DataGridColumn> newColumns = e.NewValue as ObservableCollection<DataGridColumn>; 
     dataGrid.Columns.Clear(); 
     if (newColumns != null) 
     { 
      // Add columns from this source. 
      foreach (DataGridColumn column in newColumns) 
       dataGrid.Columns.Add(column); 

      // Subscribe to future changes. 
      NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid); 
      _handlers[dataGrid] = h; 
      newColumns.CollectionChanged += h; 
     } 
    } 

    static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid) 
    { 
     switch (ne.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       dataGrid.Columns.Clear(); 
       foreach (DataGridColumn column in ne.NewItems) 
        dataGrid.Columns.Add(column); 
       break; 
      case NotifyCollectionChangedAction.Add: 
       foreach (DataGridColumn column in ne.NewItems) 
        dataGrid.Columns.Add(column); 
       break; 
      case NotifyCollectionChangedAction.Move: 
       dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex); 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       foreach (DataGridColumn column in ne.OldItems) 
        dataGrid.Columns.Remove(column); 
       break; 
      case NotifyCollectionChangedAction.Replace: 
       dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn; 
       break; 
     } 
    } 

    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); 
    } 
} 
0

Hay una muestra de la manera que lo hago mediante programación:

public partial class UserControlWithComboBoxColumnDataGrid : UserControl 
{ 
    private Dictionary<int, string> _Dictionary; 
    private ObservableCollection<MyItem> _MyItems; 
    public UserControlWithComboBoxColumnDataGrid() { 
     _Dictionary = new Dictionary<int, string>(); 
     _Dictionary.Add(1,"A"); 
     _Dictionary.Add(2,"B"); 
     _MyItems = new ObservableCollection<MyItem>(); 
     dataGridMyItems.AutoGeneratingColumn += DataGridMyItems_AutoGeneratingColumn; 
     dataGridMyItems.ItemsSource = _MyItems; 

    } 
private void DataGridMyItems_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) 
     { 
      var desc = e.PropertyDescriptor as PropertyDescriptor; 
      var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute; 
      if (att != null) 
      { 
       if (att.Name == "My Combobox Item") { 
        var comboBoxColumn = new DataGridComboBoxColumn { 
         DisplayMemberPath = "Value", 
         SelectedValuePath = "Key", 
         ItemsSource = _ApprovalTypes, 
         SelectedValueBinding = new Binding("Bazinga"), 
        }; 
        e.Column = comboBoxColumn; 
       } 

      } 
     } 

} 
public class MyItem { 
    public string Name{get;set;} 
    [ColumnName("My Combobox Item")] 
    public int Bazinga {get;set;} 
} 

    public class ColumnNameAttribute : Attribute 
    { 
     public string Name { get; set; } 
     public ColumnNameAttribute(string name) { Name = name; } 
} 
Cuestiones relacionadas