如何基于二维数组填充 WPF 网格
Posted
技术标签:
【中文标题】如何基于二维数组填充 WPF 网格【英文标题】:How to populate a WPF grid based on a 2-dimensional array 【发布时间】:2010-09-21 13:35:29 【问题描述】:我有一个二维对象数组,我基本上想将每个对象数据绑定到 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>
并在后面的代码中设置 lst 的 ItemsSource 为 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 以添加对象的更多具体数据。
【讨论】:
我不一定需要为数据绑定使用网格,但我不想为源创建列表列表。我想使用一个具有索引器的对象,该索引器采用两个参数 x 和 y。 WPF 绑定无法识别数组,它必须是一个可枚举的集合。所以最好创建一个 List 列表并对其进行数据绑定。 是否可以使用新的 WPF DataGrid 来实现这一点? 使用参考图像显示正确结果的奖励积分。一张图片值 2^10 个字。 快速提问,在这个例子中你真的需要使用 DynamicResource,还是 Static 也可以?【参考方案2】:您可能想查看此链接:http://www.thinkbottomup.com.au/site/blog/Game_of_Life_in_XAML_WPF_using_embedded_Python
如果您在列表中使用列表,则可以使用 myList[x][y] 访问单元格。
【讨论】:
列表中有一个列表,通常是 mylist[y][x]【参考方案3】:这是一个名为 DataGrid2D
的控件,可以基于 2D 或
一维数组(或任何实现IList
接口的东西)。它继承 DataGrid
并添加了一个名为 ItemsSource2D
的属性,用于绑定 2D 或 1D 源。库可以下载here,源码可以下载here。
要使用它,只需添加对 DataGrid2DLibrary.dll 的引用,添加此命名空间
xmlns:dg2d="clr-namespace:DataGrid2DLibrary;assembly=DataGrid2DLibrary"
然后创建一个 DataGrid2D 并将其绑定到您的 IList、2D 数组或 1D 数组,像这样
<dg2d:DataGrid2D Name="dataGrid2D"
ItemsSource2D="Binding Int2DList"/>
旧帖 这是一个可以将二维数组绑定到 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);
然后我们想将此二维数组绑定到 WPF DataGrid,我们所做的更改将反映在数组中。为此,我使用了来自 this 线程的 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);
然后我使用上面的 Ref 类创建了一个静态辅助类,该方法可以采用二维数组并返回 DataView。
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);
输出会是这样的
在DataGrid
中所做的任何更改都将反映在 m_intArray 中。
【讨论】:
很好的例子,感谢分享!您在 DataGrid2D.cs 中有一个问题:“找出这个问题的更好方法?”。我想,你可以写:bool multiDimensionalArray = type.IsArray && type.GetArrayRank() == 2;
和上面两行的 if 语句:if (e.NewValue is IList && (!type.IsArray || type.GetArrayRank() <= 2))
这似乎有效,在快速测试示例时找不到问题。
@Slauma:很高兴您喜欢它并感谢您的反馈!我希望有人最终会给我一个更新:)我会尝试一下并更新库!再次感谢!
我只是在测试和玩一下;)似乎还有另一个小故障:我认为,在ItemsSource2DPropertyChanged
EventHandler 中,您需要考虑e.NewValue
是@987654342 的情况@。当有人将 ItemsSource2D
设置为 null 时,它会崩溃。我只是不小心遇到了这种情况。我只是在整个 EventHandler 周围放置了一个if (e.NewValue != null)
。它不再崩溃,但我不确定这是否足够。也许dataGrid2D.ItemsSource
也必须设置为null
??
@Slauma: 将ItemsSource2D
设置为 null 时绝对不会崩溃 :) 这之前是由 if 语句处理的,但由于一次优化尝试,我不小心把它弄坏了。我上传了一个新的实现您的第一条评论中的建议并修复 null
错误的版本。再次感谢!
嗨,Meleak,是否有可能获得您的 lib DataGrid2D 的源代码?谢谢弗雷德【参考方案4】:
这是基于Meleak 的答案的另一种解决方案,但不需要在每个绑定的DataGrid
后面的代码中使用AutoGeneratingColumn
事件处理程序:
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;
【讨论】:
【参考方案5】:我为DataGrid
编写了一个小型附加属性库。
Here is the source
示例,其中 Data2D 为 int[,]
:
<DataGrid HeadersVisibility="None"
dataGrid2D:Source2D.ItemsSource2D="Binding Data2D" />
渲染:
【讨论】:
很惊讶没有cmets。做这样简单的事情我找不到其他任何东西。花几天时间尝试将二维数组绑定到网格视图!在 winforms 中花不到 10 分钟就太难了以上是关于如何基于二维数组填充 WPF 网格的主要内容,如果未能解决你的问题,请参考以下文章