Как заполнить сетку WPF на основе двумерного массива
У меня есть 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);
}
}
}
Ответы
Ответ 1
Цель 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>
И в коде позади установлен ItemSource 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]()
Ответ 2
Вот элемент управления под названием DataGrid2D
, который может быть заполнен на основе 2D или
1D (или все, что реализует интерфейс IList
). Он подклассы DataGrid
и добавляет свойство под названием ItemsSource2D
, которое используется для привязки к двумерным или 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]()
OLD POST
Вот реализация, которая может привязать 2D-массив к datagrid 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, и изменения, которые мы делаем, должны быть отражены в массиве. Для этого я использовал класс Eric Lippert 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;
}
Этого было бы достаточно для привязки, но Path in 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);
И результат будет выглядеть следующим образом:
![alt text]()
Любые изменения, сделанные в DataGrid
, будут отражены в m_intArray.
Ответ 3
Я написал небольшую библиотеку прикрепленных свойств для DataGrid
.
Вот источник
Пример, где Data2D int[,]
:
<DataGrid HeadersVisibility="None"
dataGrid2D:Source2D.ItemsSource2D="{Binding Data2D}" />
Оказывает:
![enter image description here]()
Ответ 4
Вы можете проверить эту ссылку: http://www.thinkbottomup.com.au/site/blog/Game_of_Life_in_XAML_WPF_using_embedded_Python
Если вы используете Список в списке, вы можете использовать myList [x] [y] для доступа к ячейке.
Ответ 5
Вот еще одно решение, основанное на 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;
}