2008-11-10 11 views
51

Mam dwuwymiarową tablicę obiektów i zasadniczo chcę databind każdego z nich do komórki w siatce WPF. Obecnie mam to działa, ale robię większość z nich proceduralnie. Tworzę poprawną liczbę definicji wierszy i kolumn, a następnie przechodzę przez komórki i tworzę kontrolki oraz ustalam poprawne powiązania dla każdego z nich.Jak wypełnić siatkę WPF opartą na dwuwymiarowej tablicy

Co najmniej chciałbym móc użyć szablonu do określenia kontroli i powiązań w Xaml. Idealnie chciałbym pozbyć się kodu proceduralnego i po prostu zrobić to wszystko z wiązaniem danych, ale nie jestem pewien, czy to możliwe.

Oto kod Obecnie używam:

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

} 

Odpowiedz

61

Celem siatki nie jest za prawdziwe wiązania z danymi, to tylko panel. Ja wymieniając w dół najprostszy sposób wykonać wizualizację listy dwuwymiarowej

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

I w kodzie za ustawić ItemsSource LST z TwoDimentional struktury danych.

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

To daje następujący ekran jako wynik. Możesz edytować DataTemplate_Level2, aby dodać bardziej szczegółowe dane obiektu.

alt text

+1

Nie koniecznie trzeba użyć siatki do wiązania z danymi, ale nie chcę mieć do utwórz listę list dla źródła. Chcę użyć obiektu, który ma indeksator, który pobiera dwa parametry, x i y. –

+3

Wiązanie wiązania WPF może recharongnize tablicy, musi to być kolekcja Enumerable. Więc lepiej stwórz listę list i określ ją. –

+1

Czy można to wykorzystać w nowym DataGrid WPF? –

41

Oto sterowania zwana DataGrid2D, który może być wypełniany w oparciu o 2D lub
1D tablicy (albo nic realizującym interfejs IList). Podklasuje ona pod numer DataGrid i dodaje właściwość o nazwie ItemsSource2D, która służy do wiązania ze źródłami 2D lub 1D. Bibliotekę można pobrać here, a kod źródłowy można pobrać here.

go użyć po prostu dodać odwołanie do DataGrid2DLibrary.dll, dodaj nazw

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

a następnie utworzyć DataGrid2D i powiązać go do IList, tablicy 2D lub 1D tablicy jak ten

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

enter image description here


OLD POST Oto implementacja, która może powiązać tablicę 2D z datagridem WPF.

że mamy ten 2D tablicę

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

A potem chcemy powiązać tę tablicę 2D do WPF DataGrid i zmian, jakie podejmujemy powinny być odzwierciedlone w tablicy. W tym celu użyłem klasy Ref Eric Lipperta z wątku 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); } } 
} 

Następnie stworzyłem statyczną klasę pomocniczą za pomocą metody, która może przyjąć tablicę 2D i zwrócić DataView za pomocą klasy Ref powyżej.

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

To prawie wystarczyć do związania ale droga w wiązaniu będzie wskazywać na ref object zamiast Ref.Value które musimy więc musimy to zmienić, kiedy Kolumny generowana.

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

A potem możemy użyć

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

a wyjście będzie wyglądać następująco

alt text

Wszelkie zmiany dokonane w DataGrid zostaną odzwierciedlone w m_intArray.

+0

Świetny przykład, dziękuję za podzielenie się nią! Masz pytanie w DataGrid2D.cs: "Lepszy sposób, aby to znaleźć?". Myślę, że możesz napisać: 'bool multiDimensionalArray = type.IsArray && type.GetArrayRank() == 2;' i instrukcja if dwie linie powyżej: 'if (e.NewValue is IList && (! Type.IsArray || type .GetArrayRank() <= 2)) "To wydaje się działać, nie można znaleźć problemu podczas szybkiego testowania przykładów. – Slauma

+0

@Slauma: Cieszę się, że Ci się podobało i dziękuję za informację zwrotną! Miałem nadzieję, że ktoś w końcu da mi aktualizację :) Wypróbuję i zaktualizuję bibliotekę! Dzięki jeszcze raz! –

+0

Po prostu testuję i odtwarzam;) Wydaje się, że jest jeszcze jedna drobna usterka: Myślę, że w "EventSource2DPropertyChanged" EventHandler trzeba wziąć pod uwagę przypadek, w którym 'e.NewValue' ma wartość' null'.Kiedy ktoś ustawia 'ItemsSource2D' na wartość null, to ulega awarii. Po prostu miałem tę sytuację przypadkowo. Po prostu umieściłem 'if (e.NewValue! = Null)' wokół całego EventHandler. Nie ulega już awarii, ale nie jestem pewien, czy to wystarczy. Być może również 'dataGrid2D.ItemsSource' musi być ustawione na' null' ?? – Slauma

0

Oto kolejny rozwiązanie oparte na odpowiedź Meleak „s ale nie wymagając dla obsługi AutoGeneratingColumn zdarzeń w kodzie za każdego zbindowanych 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

napisałem małą bibliotekę dołączonych właściwości dla DataGrid. Here is the source

próbki, gdzie Data2D jest int[,]:

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

Renders: enter image description here

+0

Zaskoczony, nie ma żadnych komentarzy do tego. Nic więcej nie mogę znaleźć dla zrobienia czegoś takiego prostego. Poświęć kilka dni na przymocowanie tablicy 2D do widoku siatki! Tak trudne, gdy zajmuje mniej niż 10 minut w winformach – rolls