Как заполнить сетку WPF на основе 2-мерного массива


У меня есть 2-мерный массив объектов, и я в основном хочу привязать каждый из них к ячейке в сетке WPF. В настоящее время у меня это работает, но я делаю большую часть его процедурно. Я создаю правильное количество определений строк и столбцов, затем я перебираю ячейки и создаю элементы управления и настраиваю правильные привязки для каждого из них.

как минимум, я хотел бы иметь возможность использовать шаблон для указания элементов управления и Привязок в xaml. В идеале я хотел бы получить избавьтесь от процедурного кода и просто сделайте все это с привязкой данных, но я не уверен, что это возможно.

вот код, который я сейчас использую:

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

}
5 54

5 ответов:

цель сетки не для реальной привязки данных, это просто панель. Я перечисляю самый простой способ выполнить визуализацию двумерного списка

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

и в коде позади установите ItemsSource lst с двухмерной структурой данных.

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

это дает вам следующий экран в качестве вывода. Вы можете изменить DataTemplate_Level2, чтобы добавить более конкретные данные вашего объекта.

alt text

вот элемент управления под названием DataGrid2D, которые могут быть заполнены на основе 2D или
1D массив (или все, что реализует ). Это подклассы DataGrid и добавляет свойство с именем ItemsSource2D который используется для привязки к 2D или 1D источникам. Библиотеку можно скачать здесь и исходный код можно скачать здесь.

чтобы использовать его, просто добавьте ссылку на DataGrid2DLibrary.DLL-файл, добавить это пространство имен

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

а затем создать DataGrid2D и привязать его к IList, 2D массив или 1D массив, как это

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

enter image description here


СТАРЫЙ ПОСТ
Вот реализация, которая может привязать 2D массив к WPF datagrid.

скажем, у нас есть этот 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);
    }
}

а затем мы хотим привязать этот 2D массив к WPF DataGrid и внесенные изменения будут отражены в массиве. К для этого я использовал класс Ref Эрика Липперта из этой нить.

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

затем я сделал статический вспомогательный класс с помощью метода, который может принимать 2D-массив и возвращать DataView, используя класс Ref выше.

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

этого было бы почти достаточно для привязки, но путь в привязке будет указывать на объект Ref вместо Ref.Значение, которое нам нужно, поэтому мы должны изменить это, когда столбцы генерируются.

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

и после этого мы можно использовать

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

и выход будет выглядеть так

alt text

любые изменения, внесенные в DataGrid будет отражено в m_intArray.

Я написал небольшую библиотеку вложенных свойств для DataGrid. вот источник

образец, где Data2D int[,]:

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

оказывает: enter image description here

вы можете проверить эту ссылку: http://www.thinkbottomup.com.au/site/blog/Game_of_Life_in_XAML_WPF_using_embedded_Python

Если вы используете список в списке, вы можете использовать myList[x][y] для доступа к ячейке.

вот еще одно решение, основанное на Meleak ответ, но без необходимости для AutoGeneratingColumn обработчик событий в коде позади каждого привязанного 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;
}