Comment puis-je lier un DataGrid WPF à un nombre variable de colonnes?

mon application WPF génère des ensembles de données qui peuvent avoir un nombre de colonnes différent à chaque fois. La sortie comprend une description de chaque colonne qui sera utilisée pour appliquer le formatage. Une version simplifiée de la sortie pourrait être quelque chose comme:

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

cette classe est définie comme le texte de données sur un DataGrid WPF mais je crée en fait les colonnes de manière programmatique:

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

est-il possible de remplacer ce code avec des liaisons de données dans le fichier XAML à la place?

117
demandé sur fedab 2008-11-26 11:52:26

8 réponses

voici une solution de contournement pour lier les colonnes dans le DataGrid. Puisque la propriété colonnes est en lecture seule, comme tout le monde l'a remarqué, j'ai fait une propriété attachée appelée BindableColumns qui met à jour les colonnes dans le DataGrid à chaque fois que la collection change à travers L'événement CollectionChanged.

si nous avons cette Collection de Datagridcolumn's

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

alors nous pouvons lier BindableColumns à la collection de colonnes comme ceci

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

La Propriété Attachée 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);
    }
}
121
répondu Fredrik Hedblad 2010-12-07 18:01:44

j'ai poursuivi mes recherches et je n'ai trouvé aucun moyen raisonnable de le faire. La propriété Columns sur le DataGrid n'est pas quelque chose que je peux lier contre, en fait il est lu seulement.

Bryan suggéré quelque chose pourrait être fait avec AutoGenerateColumns donc j'ai eu un oeil. Il utilise de simples .Net réflexion pour regarder les propriétés des objets dans ItemsSource et génère une colonne pour chacun. Je pourrais peut-être générer un type à la volée avec une propriété pour chaque colonne mais cela devient chemin hors de la piste.

étant donné que ce problème est si facilement résolu en code, je m'en tiendrai à une méthode d'extension simple que j'appellerai chaque fois que le contexte de données sera mis à jour avec de nouvelles colonnes:

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);
18
répondu Generic Error 2011-09-19 18:25:29

j'ai trouvé un article de blog par Deborah Kurata avec un bon truc comment montrer le nombre variable de colonnes dans un DataGrid:

peupler un DataGrid avec des colonnes dynamiques dans une Application Silverlight en utilisant MVVM

essentiellement, elle crée un DataGridTemplateColumn et met ItemsControl à l'intérieur qui affiche plusieurs colonnes.

9
répondu Lukas Cenovsky 2014-12-03 13:38:21

j'ai réussi à rendre possible l'ajout dynamique d'une colonne en utilisant juste une ligne de code comme celle-ci:

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

en ce qui concerne la question, il ne s'agit pas d'une solution basée sur XAML (puisque comme mentionné il n'y a pas de façon raisonnable de le faire), ni d'une solution qui fonctionnerait directement avec DataGrid.Colonne. Il fonctionne en fait avec DataGrid bound ItemsSource, qui implémente ITypedList et fournit ainsi des méthodes personnalisées pour la récupération de PropertyDescriptor. En un seul endroit dans le code, vous pouvez définir des "lignes" et "colonnes de données" pour votre grille.

si vous auriez:

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

, vous pouvez utiliser par exemple:

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) 

et votre grille utilisant la liaison à MyItemsCollection serait remplie avec les colonnes correspondantes. Ces colonnes peuvent être modifiées (ajoutées ou existantes supprimées) à l'exécution dynamiquement et grid actualisera automatiquement sa collection de colonnes.

DynamicPropertyDescriptor mentionné ci-dessus est juste une mise à niveau à PropertyDescriptor régulier et fournit la définition de colonnes fortement dactylographiées avec quelques options supplémentaires. DynamicDataGridSource ne fonctionnerait autrement que très bien event avec PropertyDescriptor de base.

5
répondu doblak 2014-04-10 21:01:36

a fait une version de la réponse acceptée qui traite unsubscription.

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);
    }
}
3
répondu Mikhail Orlov 2016-03-09 13:43:09

vous pouvez créer un usercontrol avec la définition de grille et définir des commandes 'enfant' avec des définitions de colonnes variées dans xaml. Le parent a besoin d'une propriété de dépendance pour les colonnes et d'une méthode pour charger les colonnes:

Parent:


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

Enfant Xaml:


<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>

et enfin, la partie délicate est de trouver où appeler "LoadGrid".

J'ai du mal avec ça mais j'ai des choses à faire en appelant après InitalizeComponent dans mon constructeur de fenêtres (childGrid est x:name in window.xaml):

childGrid.deGrid.LoadGrid();

Liées à l'entrée de blog

2
répondu Andy 2010-11-22 19:57:20

vous pourriez être en mesure de le faire avec AutoGenerateColumns et un Dataemplate. Je ne suis pas positif si cela fonctionnerait sans beaucoup de travail, vous avez à jouer avec elle. Honnêtement, si vous avez déjà une solution, je ne ferais pas le changement à moins qu'il y ait une grande raison. Le contrôle de DataGrid est en train de devenir très bon, mais il a encore besoin de travail (et j'ai encore beaucoup d'apprentissage à faire) pour être capable de faire des tâches dynamiques comme celle-ci facilement.

1
répondu Bryan Anderson 2008-11-26 15:37:35

il y a un échantillon de la façon dont je fais programmatically:

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; }
}
0
répondu David Soler 2016-10-21 10:24:03