Remplir un Datagrid avec des colonnes dynamiques

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

Le tablelayout est de la forme:

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

Les 4 premières colonnes sont statiques, les autres de la dynamique. 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 les uns à côté des autres dans le tableau.

en ce moment j'ai une boîte aux lettres Listbox whitch contient les noms des colonnes générées dynamiquement et une méthode qui permet de filtrer les colonnes statiques colonne. Je peux aussi 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 un datagrid.

17
demandé sur Rahul Tripathi 2013-08-27 00:03:52

3 réponses

Il y a au moins trois façons de le faire:

  1. Modifier les colonnes du DataGrid manuellement à partir du code-derrière
  2. utiliser un DataTable comme source Itemss*
  3. utilisez un CustomTypeDescriptor

    * recommandé pour la simplicité


1ère approche: utilisez code-behind pour générer les colonnes de DataGrid à l'exécution. C'est simple à mettre en œuvre, mais peut-être se sent un peu hackish, surtout si vous utilisez MVVM. Vous avez 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>

quand vous avez vos" noms " prêts, alors 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 voulez créer une classe wrapper, qui devrait contenir la classe originale, plus un dictionnaire pour contenir les propriétés personnalisées. Disons que votre classe de rangée principale est "utilisateur", alors vous voulez une classe d'emballage quelque chose comme ceci:

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

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

peupler 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, le liant 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: un DataTable. Cela rend l'utilisation de ce type d'infrastructure sous le capot, mais est plus facile à utiliser. Il suffit de lier le DataGrid de ItemsSourceDataTable.DefaultView propriété:

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

alors vous pouvez définir les colonnes cependant, vous comme, 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: utilisez l'extensibilité du système de type .Net. Plus précisément, utilisez un CustomTypeDescriptor. Cela vous permet de créer un type personnalisé à l'exécution, ce qui à son tour vous permet de dire au DataGrid que votre type a les propriétés "Name1", "Name2",... "NameN", ou ce que vous voulez. Voir ici pour un exemple simple de cette approche.

24
répondu McGarnagle 2017-05-23 12:02:05

2ème approche: utilisez un DataTable. Cela rend l'utilisation de ce type d'infrastructure sous le capot, mais est plus facile à utiliser. Il suffit de lier la source des éléments de DataGrid à un DataTable.Propriété DefaultView:

cela a presque fonctionné mais au lieu de se lier au DataTable.Propriété DefaultView j'ai créé une propriété de type DataView et liée à celle-ci.

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

cela permet à la reliure d'être bidirectionnelle, DataTable.DefaultView ne peut pas être un liant à double sens. Dans le modèle View

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

avec cette configuration Je ne pouvais pas seulement définir les colonnes de façon dynamique lorsque le modèle de vue est initialisé, mais je pouvais mettre à jour et changer la table de données de façon dynamique à tout moment. En utilisant l'approche définie par McGarnagle ci-dessus, le schéma de vue n'était pas rafraîchissant lorsque le DataTable a été mis à jour avec une nouvelle source de données.

4
répondu FC Joe H 2015-07-24 00:01:58

si vous n'êtes pas obligé de montrer cela dans un seul grand DataGrid (table) alors vous pourriez avoir un DataGridid,image,nom,Description et quand un des enregistrements est sélectionné sur ce DataGrid alors vous montrez/rafraîchissez une liste avec le nom des images qui sont liées à cet enregistrement sélectionné

0
répondu Mauricio Gracia Gutierrez 2013-08-26 20:30:15