web-dev-qa-db-fra.com

Remplir une grille de données avec des colonnes dynamiques

J'ai un Datagrid qui doit être rempli dynamiquement.

La mise en page est comme:

id | image | name | Description | Name-1 | Name-N

Les 4 premières colonnes sont statiques, les autres sont dynamiques. L'utilisateur doit pouvoir ajouter autant d'utilisateurs qu'il le souhaite.

J'essaie de comparer les données de plusieurs utilisateurs en les mettant côte à côte dans le tableau.

En ce moment, j'ai une Listbox qui contient les noms des colonnes générées dynamiques et une méthode qui utilise les colonnes statiques. Je peux également charger les données pour chaque utilisateur. maintenant, je dois les fusionner en une seule grande table.

Le problème principal est maintenant: Comment mettre les "Userdata" et le contenu statique dans une grille de données.

23
Wr4thon

Il existe au moins trois façons de procéder:

  1. Modifier manuellement les colonnes du DataGrid à partir de code-behind
  2. Utilisez un DataTable comme ItemsSource *
  3. Utilisez un CustomTypeDescriptor

    * recommandé pour plus de simplicité


1ère approche: utilisez le code-behind pour générer les colonnes de DataGrid lors de l'exécution. C'est simple à implémenter, mais peut-être un peu hack, surtout si vous utilisez MVVM. Vous auriez donc votre DataGrid avec des colonnes fixes:

<DataGrid x:Name="grid">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding id}" Header="id" />
        <DataGridTextColumn Binding="{Binding image}" Header="image" />
    </DataGrid.Columns>
</DataGrid>

Lorsque vous avez vos "Noms" prêts, modifiez la grille en ajoutant/supprimant des colonnes, par exemple:

// add new columns to the data grid
void AddColumns(string[] newColumnNames)
{
    foreach (string name in newColumnNames)
    {
        grid.Columns.Add(new DataGridTextColumn { 
            // bind to a dictionary property
            Binding = new Binding("Custom[" + name + "]"), 
            Header = name 
        });
    }
}

Vous souhaiterez créer une classe wrapper, qui devrait contenir la classe d'origine, plus un dictionnaire pour contenir les propriétés personnalisées. Disons que votre classe de ligne principale est "Utilisateur", alors vous voudriez une classe wrapper quelque chose comme ceci:

public class CustomUser : User
{
    public Dictionary<string, object> Custom { get; set; }

    public CustomUser() : base()
    {
        Custom = new Dictionary<string, object>();
    }
}

Remplissez le ItemsSource avec une collection de cette nouvelle classe "CustomUser":

void PopulateRows(User[] users, Dictionary<string, object>[] customProps)
{
    var customUsers = users.Select((user, index) => new CustomUser {
        Custom = customProps[index];
    });
    grid.ItemsSource = customUsers;
}

Donc, attachez-les ensemble, par exemple:

var newColumnNames = new string[] { "Name1", "Name2" };
var users = new User[] { new User { id="First User" } };
var newProps = new Dictionary<string, object>[] {
    new Dictionary<string, object> { 
        "Name1", "First Name of First User",
        "Name2", "Second Name of First User",
    },
};
AddColumns(newColumnNames);
PopulateRows(users, newProps);

2ème approche: utilisez un DataTable . Cela utilise l'infrastructure de type personnalisé sous le capot, mais est plus facile à utiliser. Liez simplement le ItemsSource du DataGrid à un DataTable.DefaultView propriété:

<DataGrid ItemsSource="{Binding Data.DefaultView}" AutoGenerateColumns="True" />

Ensuite, vous pouvez définir les colonnes comme vous le souhaitez, par exemple:

Data = new DataTable();

// create "fixed" columns
Data.Columns.Add("id");
Data.Columns.Add("image");

// create custom columns
Data.Columns.Add("Name1");
Data.Columns.Add("Name2");

// add one row as an object array
Data.Rows.Add(new object[] { 123, "image.png", "Foo", "Bar" });

3ème approche: utiliser l'extensibilité du système de type de .Net. Plus précisément, utilisez un CustomTypeDescriptor . Cela vous permet de créer un type personnalisé lors de l'exécution; qui à son tour vous permet de dire au DataGrid que votre type a les propriétés "Name1", "Name2", ... "NameN", ou tout ce que vous voulez. Voir ici pour un exemple simple de cette approche.

35
McGarnagle

2ème approche: utilisez un DataTable. Cela utilise l'infrastructure de type personnalisé sous le capot, mais est plus facile à utiliser. Liez simplement le ItemsSource du DataGrid à une propriété DataTable.DefaultView:

Cela a presque fonctionné, mais au lieu de lier à la propriété de propriété DataTable.DefaultView, j'ai créé une propriété de type DataView et lié à cela.

<DataGrid ItemsSource="{Binding DataView, Mode=TwoWay}" AutoGenerateColumns="True" />

Cela permet à la liaison d'être bidirectionnelle, la liaison à DataTable.DefaultView ne peut pas être une liaison à deux voies. Dans le modèle d'affichage

    public DataView DataView
    {
        get { return _dataView; }
        set
        {
            _dataView = value;
            OnPropertyChanged("DataView");
        }
    }

Avec cette configuration, je pouvais non seulement définir dynamiquement les colonnes lors de l'initialisation du modèle de vue, mais également mettre à jour et modifier dynamiquement la table de données à tout moment. En utilisant l'approche définie par McGarnagle ci-dessus, le schéma de la vue n'était pas actualisé lorsque le DataTable a été mis à jour avec une nouvelle source de données.

5
FC Joe H

J'utilise actuellement une autre approche, je ne sais pas s'il est juste de le faire comme ça, mais cela fonctionne. J'ai fait un petit échantillon.

Gardez à l'esprit que pour que cela fonctionne, chaque entrée dans le Datagrid doit avoir les mêmes colonnes dynamiques, ce qui le rend un peu moins flexible. Mais si vous avez des entrées avec différentes quantités de colonnes dans chaque entrée, un Datagrid est probablement la mauvaise approche de toute façon.

Ce sont mes cours

 public class Person
    {
        public ObservableCollection<Activity> Hobbys { get; set; }
        public string Name { get; set; }
    }
 public class Activity
    {
        public string Name { get; set; }
    }

Et voici Code Derrière:

public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            ObservableCollection<Activity> hobbys = new ObservableCollection<Activity>();
            hobbys.Add(new Activity() { Name = "Soccer" });
            hobbys.Add(new Activity() { Name = "Basketball" });

            Community = new ObservableCollection<Person>();
            Community.Add(new Person() { Name = "James", Hobbys = hobbys });
            Community.Add(new Person() { Name = "Carl", Hobbys = hobbys });
            Community.Add(new Person() { Name = "Homer", Hobbys = hobbys });

            MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Name", Binding = new Binding("Name") });    //Static Column
            int x = 0;
            foreach (Activity act in Community[0].Hobbys)  //Create the dynamic columns
            {
                MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Activity", Binding = new Binding("Hobbys["+x+"].Name") });
                x++;
            }

        }

Et en .XAML simplement:

  <DataGrid Name="MyGrid" ItemsSource="{Binding Community}" AutoGenerateColumns="False"/>
1
Bedi

Si vous n'êtes pas obligé de montrer cela dans un seul grand DataGrid (table), vous pouvez avoir un DataGrid avec id, image, nom, description et lorsque l'un des enregistrements est sélectionné sur ce DataGrid, puis vous affichez/actualisez un ListBox avec le nom des images liées à cet enregistrement sélectionné