Как заполнить сетку 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 ответов:
цель сетки не для реальной привязки данных, это просто панель. Я перечисляю самый простой способ выполнить визуализацию двумерного списка
<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 массив (или все, что реализует ). Это подклассы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 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);
и выход будет выглядеть так
любые изменения, внесенные в
DataGrid
будет отражено в m_intArray.
Я написал небольшую библиотеку вложенных свойств для
DataGrid
. вот источникобразец, где Data2D
int[,]
:<DataGrid HeadersVisibility="None" dataGrid2D:Source2D.ItemsSource2D="{Binding Data2D}" />
оказывает:
вы можете проверить эту ссылку: 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; }