2008-11-10 6 views
51

Ich habe ein zweidimensionales Array von Objekten und ich möchte im Grunde jede einzelne zu einer Zelle in einem WPF-Gitter Databind. Zur Zeit habe ich diese Arbeit, aber ich mache das meiste prozedural. Ich erstelle die korrekte Anzahl von Zeilen- und Spaltendefinitionen, dann durchlaufe ich die Zellen und erstelle die Steuerelemente und richte die korrekten Bindungen für jedes ein.Wie man ein WPF-Gitter basierend auf einem 2-dimensionalen Array bevölkert

Zumindest möchte ich in der Lage sein, eine Vorlage zu verwenden, um die Steuerelemente und Bindungen in XAML anzugeben. Idealerweise möchte ich den prozeduralen Code loswerden und alles mit Datenbindung machen, aber ich bin mir nicht sicher, ob das möglich ist. Hier

ist der Code, den ich bin derzeit mit:

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

} 

Antwort

61

Der Zweck des Grid ist nicht für echte Datenbindung, es ist nur eine Platte. Mir Streichungen den einfachste Weg, um die Visualisierung einer zweidimensionalen Liste

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

Und in dem Code hinter stellen Sie die Itemssource von lst mit einer TwoDimentional Datenstruktur zu erreichen.

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

Dies gibt Ihnen den folgenden Bildschirm als Ausgabe. Sie können das DataTemplate_Level2 bearbeiten, um spezifischere Daten Ihres Objekts hinzuzufügen.

alt text

+1

ich nicht unbedingt ein Raster für die Datenbindung verwenden, aber ich will nicht zu haben, Erstellen Sie eine Liste mit Listen für die Quelle. Ich möchte ein Objekt mit einem Indexer verwenden, der zwei Parameter, x und y, enthält. –

+3

WPF-Bindung kann ein Array nicht erkennen, es muss eine Enumerable-Auflistung sein. Also erstellen Sie eine List of List und erstellen Sie sie. –

+1

Ist es möglich, das neue WPF DataGrid zu verwenden, um dies zu erreichen? –

41

Hier ist eine Steuer DataGrid2D genannt, die basierend auf einer 2D bestückt werden können oder
1D-Array (oder irgendetwas, das die IList Schnittstelle implementiert). Es Unterklassen DataGrid und fügt eine Eigenschaft namens ItemsSource2D, die zum Binden gegen 2D-oder 1D-Quellen verwendet wird. Bibliothek kann here heruntergeladen werden und Quellcode kann here heruntergeladen werden.

es hinzufügen verwenden nur einen Verweis auf DataGrid2DLibrary.dll, fügen Sie diesen Namespace

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

und dann eine DataGrid2D erstellen und binden Sie es an Ihre IList, 2D-Array oder 1D-Array wie diese

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

enter image description here


ALTE POST
Hier ist eine Implementierung, die ein 2D-Array an das WPF-Datagrid binden kann.

Sagen wir diese 2D-Array haben

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

Und dann wollen wir binden, dieses 2D-Array an die WPF Datagrid und die Änderungen, die wir machen werden in der Anordnung reflektiert werden. Dazu habe ich Eric Lipperts Ref-Klasse von this Thread verwendet.

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

Dann machte ich eine statische Hilfsklasse mit einem Verfahren, das einen 2D-Array nehmen und eine Dataview zurückzukehren über die Ref-Klasse.

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

Dies würde fast genug sein, um zu binden, aber der Pfad in dem Bindepunkt wird auf die Referenz anstelle der Ref.Value Objekt, das wir brauchen, so müssen wir dies ändern, wenn die Spalten erzeugt bekommen.

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

Und danach können wir

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

und der Ausgang wird wie folgt aussehen

alt text

Alle im DataGrid vorgenommenen Änderungen verwenden wird in der m_intArray widerspiegeln.

+0

Tolles Beispiel, vielen Dank für die Freigabe! Sie hatten eine Frage in DataGrid2D.cs: "Bessere Möglichkeit, das herauszufinden?". Ich denke, Sie können schreiben: 'bool multiDimensionalArray = type.IsArray && type.GetArrayRank() == 2;' und die if-Anweisung zwei Zeilen oben: 'if (e.NewValue ist IList && (! Type.IsArray || type .GetArrayRank() <= 2)) 'Das scheint zu funktionieren, konnte beim schnellen Test der Beispiele kein Problem finden. – Slauma

+0

@Slauma: Schön, dass es dir gefallen hat und danke für das Feedback! Ich hatte gehofft, dass jemand mir später ein Update geben würde :) Ich werde es ausprobieren und die lib aktualisieren! Danke noch einmal! –

+0

Ich teste und spiele ein bisschen herum;) Es scheint noch einen weiteren kleinen Fehler zu geben: Ich denke, im 'ItemsSource2DPropertyChanged' EventHandler müssen Sie den Fall berücksichtigen, dass' e.NewValue' 'null' ist.Wenn jemand 'ItemsSource2D' auf null setzt, stürzt es ab. Ich hatte gerade diese Situation zufällig. Ich habe einfach einen 'if (e.NewValue! = Null)' um den gesamten EventHandler gelegt. Es stürzt nicht mehr ab, aber ich bin mir nicht sicher, ob das ausreicht. Vielleicht muss auch 'dataGrid2D.ItemsSource' auf' null' gesetzt werden ?? – Slauma

0

ist hier eine andere Lösung basiert auf Meleak ‚s Antwort, aber ohne hinter jeder im Code für einen AutoGeneratingColumn Event-Handler zu erfordern DataGrid binded:

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

Ich schrieb eine kleine Bibliothek von angefügten Eigenschaften für die DataGrid. Here is the source

Probe, wo Data2D ist int[,]:

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

Renders: enter image description here

+0

Überrascht gibt es dazu keine Kommentare. Nichts anderes kann ich finden, um so etwas Einfaches zu tun. Verbringen Sie Tage damit, ein 2D-Array an eine Gridview zu binden! So schwierig, wenn es in Winforms weniger als 10 Minuten dauert – rolls

Verwandte Themen