Как заполнить сетку WPF на основе двумерного массива - PullRequest
55 голосов
/ 10 ноября 2008

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

66 голосов
/ 10 ноября 2008

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

<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 со структурой данных 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;
    }

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

alt text

42 голосов
/ 23 октября 2010

Вот элемент управления, называемый 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}"/>

enter image description here


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

Скажем, у нас есть этот двумерный массив

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

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

alt text

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

3 голосов
/ 28 июня 2015

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

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

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

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

0 голосов
/ 30 ноября 2011

Вот еще одно решение, основанное на ответе 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;
}
0 голосов
/ 28 октября 2009

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

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

...