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

У меня есть двумерный массив объектов, и я в основном хочу привязать каждый из них к ячейке в сетке 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 ответов

Решение

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

<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, чтобы добавить более конкретные данные вашего объекта.

альтернативный текст

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

Чтобы использовать его просто добавьте ссылку на DataGrid2DLibrary.dll, добавьте это пространство имен

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

а затем создайте DataGrid2D и привяжите его к вашему IList, 2D-массиву или 1D-массиву следующим образом

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


СТАРЫЙ ПОСТ
Вот реализация, которая может связать 2D-массив с сеткой данных WPF.

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

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

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

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

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

альтернативный текст

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

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

Пример, где Data2D int[,]:

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

Оказывает:

Вот еще одно решение, основанное на ответе Мелика, но не требующее 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;
}

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

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

Другие вопросы по тегам