2008-11-10 13 views
51

J'ai un tableau bidimensionnel d'objets et je souhaite fondamentalement indexer chacun d'eux sur une cellule dans une grille WPF. Actuellement, j'ai ce travail, mais je fais la plupart de la procédure. Je crée le nombre correct de définitions de lignes et de colonnes, puis je parcourt les cellules et crée les contrôles et configure les liaisons correctes pour chacun d'eux. Au minimum, j'aimerais pouvoir utiliser un modèle pour spécifier les contrôles et les liaisons dans xaml. Idéalement, je voudrais me débarrasser du code de procédure et tout faire avec la liaison de données, mais je ne suis pas sûr que ce soit possible.Comment remplir une grille WPF basée sur un tableau bidimensionnel

Voici le code que je suis actuellement en utilisant:

public void BindGrid() 
{ 
    m_Grid.Children.Clear(); 
    m_Grid.ColumnDefinitions.Clear(); 
    m_Grid.RowDefinitions.Clear(); 

    for (int x = 0; x < MefGrid.Width; x++) 
    { 
     m_Grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), }); 
    } 

    for (int y = 0; y < MefGrid.Height; y++) 
    { 
     m_Grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), }); 
    } 

    for (int x = 0; x < MefGrid.Width; x++) 
    { 
     for (int y = 0; y < MefGrid.Height; y++) 
     { 
      Cell cell = (Cell)MefGrid[x, y];      

      SolidColorBrush brush = new SolidColorBrush(); 

      var binding = new Binding("On"); 
      binding.Converter = new BoolColorConverter(); 
      binding.Mode = BindingMode.OneWay; 

      BindingOperations.SetBinding(brush, SolidColorBrush.ColorProperty, binding); 

      var rect = new Rectangle(); 
      rect.DataContext = cell; 
      rect.Fill = brush; 
      rect.SetValue(Grid.RowProperty, y); 
      rect.SetValue(Grid.ColumnProperty, x); 
      m_Grid.Children.Add(rect); 
     } 
    } 

} 

Répondre

61

Le but de la grille est pas réelle databinding, il est juste un panneau. Je suis la liste sur la meilleure façon d'accomplir la visualisation d'une liste de deux dimensions

<Window.Resources> 
    <DataTemplate x:Key="DataTemplate_Level2"> 
      <Button Content="{Binding}" Height="40" Width="50" Margin="4,4,4,4"/> 
    </DataTemplate> 

    <DataTemplate x:Key="DataTemplate_Level1"> 
     <ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}"> 
      <ItemsControl.ItemsPanel> 
       <ItemsPanelTemplate> 
        <StackPanel Orientation="Horizontal"/> 
       </ItemsPanelTemplate> 
      </ItemsControl.ItemsPanel> 
     </ItemsControl> 
    </DataTemplate> 

</Window.Resources> 
<Grid> 
    <ItemsControl x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}"/> 
</Grid> 

Et dans le code derrière les ItemsSource de LST avec une structure de données TwoDimentional.

public Window1() 
    { 
     List<List<int>> lsts = new List<List<int>>(); 

     for (int i = 0; i < 5; i++) 
     { 
      lsts.Add(new List<int>()); 

      for (int j = 0; j < 5; j++) 
      { 
       lsts[i].Add(i * 10 + j); 
      } 
     } 

     InitializeComponent(); 

     lst.ItemsSource = lsts; 
    } 

Ceci vous donne l'écran suivant en sortie. Vous pouvez modifier le DataTemplate_Level2 pour ajouter des données plus spécifiques de votre objet.

alt text

+1

Je ne dois pas nécessairement d'utiliser une grille pour la liaison de données, mais je ne veux pas avoir à créer une liste de listes pour la source. Je veux utiliser un objet qui a un indexeur qui prend deux paramètres, x et y. –

+3

La liaison WPF ne peut pas recongniser un tableau, il doit s'agir d'une collection Enumerable. Donc, mieux créer une liste de liste et de la databind. –

+1

Est-il possible d'utiliser le nouveau DataGrid WPF pour y parvenir? –

41

Voici un contrôle appelé DataGrid2D qui peut être rempli en fonction 2D ou
tableau 1D (ou quoi que ce soit qui implémente l'interface IList). Il sous-classe DataGrid et ajoute une propriété appelée ItemsSource2D qui est utilisée pour lier les sources 2D ou 1D. La bibliothèque peut être téléchargée here et le code source peut être téléchargé here.

Pour l'utiliser il suffit d'ajouter une référence à DataGrid2DLibrary.dll, ajoutez cet espace de noms

xmlns:dg2d="clr-namespace:DataGrid2DLibrary;assembly=DataGrid2DLibrary" 

puis créer un DataGrid2D et le lier à votre IList, tableau 2D ou tableau 1D comme celui-ci

<dg2d:DataGrid2D Name="dataGrid2D" 
       ItemsSource2D="{Binding Int2DList}"/> 

enter image description here


POST VIEUX
Voici une implémentation qui peut lier un tableau 2D à la grille de données WPF.

Disons que nous avons ce tableau 2D

private int[,] m_intArray = new int[5, 5]; 
... 
for (int i = 0; i < 5; i++) 
{ 
    for (int j = 0; j < 5; j++) 
    { 
     m_intArray[i,j] = (i * 10 + j); 
    } 
} 

Et nous voulons lier ce tableau 2D à la DataGrid WPF et les changements que nous faisons est reflétée dans le tableau. Pour ce faire j'ai utilisé la classe Ref d'Eric Lippert du fil this.

public class Ref<T> 
{ 
    private readonly Func<T> getter; 
    private readonly Action<T> setter; 
    public Ref(Func<T> getter, Action<T> setter) 
    { 
     this.getter = getter; 
     this.setter = setter; 
    } 
    public T Value { get { return getter(); } set { setter(value); } } 
} 

Alors je fait une classe d'aide statique avec une méthode qui pourrait prendre un tableau 2D et retourner un DataView en utilisant la classe Ref ci-dessus.

public static DataView GetBindable2DArray<T>(T[,] array) 
{ 
    DataTable dataTable = new DataTable(); 
    for (int i = 0; i < array.GetLength(1); i++) 
    { 
     dataTable.Columns.Add(i.ToString(), typeof(Ref<T>)); 
    } 
    for (int i = 0; i < array.GetLength(0); i++) 
    { 
     DataRow dataRow = dataTable.NewRow(); 
     dataTable.Rows.Add(dataRow); 
    } 
    DataView dataView = new DataView(dataTable); 
    for (int i = 0; i < array.GetLength(0); i++) 
    { 
     for (int j = 0; j < array.GetLength(1); j++) 
     { 
      int a = i; 
      int b = j; 
      Ref<T> refT = new Ref<T>(() => array[a, b], z => { array[a, b] = z; }); 
      dataView[i][j] = refT; 
     } 
    } 
    return dataView; 
} 

Ce serait presque suffisant pour se lier à, mais le chemin de la liaison pointera vers le Ref objet au lieu de l'Ref.Value dont nous avons besoin si nous devons changer quand les colonnes sont générées.

<DataGrid Name="c_dataGrid" 
      RowHeaderWidth="0" 
      ColumnHeaderHeight="0" 
      AutoGenerateColumns="True" 
      AutoGeneratingColumn="c_dataGrid_AutoGeneratingColumn"/> 

private void c_dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) 
{ 
    DataGridTextColumn column = e.Column as DataGridTextColumn; 
    Binding binding = column.Binding as Binding; 
    binding.Path = new PropertyPath(binding.Path.Path + ".Value"); 
} 

Et après cela, nous pouvons utiliser

c_dataGrid.ItemsSource = BindingHelper.GetBindable2DArray<int>(m_intArray); 

Et la sortie ressemblera à ceci

alt text

Toute modification apportée dans le DataGrid seront reflétés dans le m_intArray.

+0

Bon exemple, merci de le partager! Vous avez posé une question dans DataGrid2D.cs: "Meilleure façon de le découvrir?". Je pense, vous pouvez écrire: 'bool multiDimensionalArray = type.IsArray && type.GetArrayRank() == 2;' et l'instruction if deux lignes ci-dessus: 'if (e.NewValue est IList && (! Type.IsArray || type .GetArrayRank() <= 2)) 'Cela semble fonctionner, n'a pas pu trouver de problème lors du test rapide des exemples. – Slauma

+0

@Slauma: Je suis content que vous l'ayez aimé et merci pour vos commentaires! J'espérais que quelqu'un finirait par me donner une mise à jour sur ça :) Je vais essayer et mettre à jour la lib! Merci encore! –

+0

Je ne fais que tester et jouer un peu;) Il semble y avoir un autre petit problème: je pense que dans le gestionnaire d'événements 'ItemsSource2DPropertyChanged' vous devez considérer le cas où' e.NewValue' est 'null'.Lorsque quelqu'un définit le 'ItemsSource2D' à null, il se bloque. Je viens d'avoir cette situation accidentellement. J'ai simplement placé un 'if (e.NewValue! = Null)' autour de l'ensemble EventHandler. Ça ne plante plus, mais je ne suis pas sûr que ce soit suffisant. Peut-être aussi 'dataGrid2D.ItemsSource' doit être défini sur' null' ?? – Slauma

0

Voici une autre solution basée sur la réponse de Meleak mais sans qu'il soit nécessaire pour un gestionnaire d'événements AutoGeneratingColumn dans le code derrière de chaque binded DataGrid:

public static DataView GetBindable2DArray<T>(T[,] array) 
{ 
    var table = new DataTable(); 
    for (var i = 0; i < array.GetLength(1); i++) 
    { 
     table.Columns.Add(i+1, typeof(bool)) 
        .ExtendedProperties.Add("idx", i); // Save original column index 
    } 
    for (var i = 0; i < array.GetLength(0); i++) 
    { 
     table.Rows.Add(table.NewRow()); 
    } 

    var view = new DataView(table); 
    for (var ri = 0; ri < array.GetLength(0); ri++) 
    { 
     for (var ci = 0; ci < array.GetLength(1); ci++) 
     { 
      view[ri][ci] = array[ri, ci]; 
     } 
    } 

    // Avoids writing an 'AutogeneratingColumn' handler 
    table.ColumnChanged += (s, e) => 
    { 
     var ci = (int)e.Column.ExtendedProperties["idx"]; // Retrieve original column index 
     var ri = e.Row.Table.Rows.IndexOf(e.Row); // Retrieve row index 

     array[ri, ci] = (T)view[ri][ci]; 
    }; 

    return view; 
} 
3

j'ai écrit une petite bibliothèque de propriétés attachées pour le DataGrid. Here is the source

Sample, où Data2D est int[,]:

<DataGrid HeadersVisibility="None" 
      dataGrid2D:Source2D.ItemsSource2D="{Binding Data2D}" /> 

Renders: enter image description here

+0

Surpris il n'y a aucun commentaire à ceci. Rien d'autre que je peux trouver pour faire quelque chose de simple comme ça. Passez des journées à essayer de lier un tableau 2D à un gridview! Tellement difficile quand il faut moins de 10 minutes en winforms – rolls

Questions connexes