如何实现带行列标签、可编辑的N×N复选框网格/矩阵
实现方案
- 没有完全匹配的开箱即用第三方控件,直接用WPF原生
ItemsControl嵌套实现即可,不需要额外引入依赖。 - 不需要将数据转换为二维数组,直接使用原始
MatrixEntry实体集合即可,转二维数组反而会增加双向绑定的同步维护成本。 - 优先通过XAML+转换器+MVVM模式实现,不要将映射逻辑堆在代码后置,逻辑抽离到转换器中更易维护、复用。
具体实现步骤
1. 编写枚举Display特性读取转换器
用于自动读取RowType、ColumnType枚举上的Display标注文本,不需要硬编码坐标轴文本:
using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Windows.Data; public class EnumDisplayConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) return string.Empty; var field = value.GetType().GetField(value.ToString()); var displayAttr = field?.GetCustomAttribute<DisplayAttribute>(); return displayAttr?.Name ?? value.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); }
2. 编写矩阵条目查找转换器
用于根据当前行、列枚举值,匹配到对应的MatrixEntry实体,支持双向绑定直接更新Value属性:
using System.Collections.ObjectModel; using System.Globalization; using System.Windows.Data; public class MatrixEntryLookupConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Length < 3 || values[0] is not ObservableCollection<MatrixEntry> entries || values[1] is not RowType currentRow || values[2] is not ColumnType currentCol) return false; var matchEntry = entries.FirstOrDefault(e => e.Row == currentRow && e.Column == currentCol); // 不存在对应条目时自动补全,避免空绑定 if (matchEntry == null) { matchEntry = new MatrixEntry { Id = Guid.NewGuid(), Row = currentRow, Column = currentCol, Value = false }; entries.Add(matchEntry); } return matchEntry.Value; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { // 双向绑定时直接更新匹配到的条目Value,其余绑定项不做修改 return new[] { Binding.DoNothing, Binding.DoNothing, Binding.DoNothing, value is bool checkValue ? checkValue : Binding.DoNothing }; } }
3. ViewModel层准备数据源
直接暴露行列枚举集合、实体集合即可,不需要做二维结构转换:
using System.Collections.ObjectModel; using System.Collections.Generic; using System; public class MatrixViewModel { // 所有行枚举值,自动适配枚举新增/删除项 public List<RowType> AllRows { get; } = Enum.GetValues<RowType>().ToList(); // 所有列枚举值,自动适配枚举新增/删除项 public List<ColumnType> AllColumns { get; } = Enum.GetValues<ColumnType>().ToList(); // 矩阵条目集合,用ObservableCollection实现界面自动同步 public ObservableCollection<MatrixEntry> MatrixEntries { get; set; } = new(); }
4. XAML布局实现网格
用UniformGrid作为布局面板自动均分单元格,自动适配N×N尺寸,分别实现列头、行头、CheckBox网格三个区域:
<Window x:Class="MatrixDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MatrixDemo" Title="MatrixGrid" Height="450" Width="800"> <Window.Resources> <local:EnumDisplayConverter x:Key="EnumDisplayConverter"/> <local:MatrixEntryLookupConverter x:Key="MatrixEntryLookupConverter"/> </Window.Resources> <Grid Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <!-- 顶部列头行 --> <RowDefinition Height="*"/> <!-- 数据区域行 --> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <!-- 左侧行头列 --> <ColumnDefinition Width="*"/> <!-- 数据区域列 --> </Grid.ColumnDefinitions> <!-- 顶部列头:显示ColumnType的Display文本 --> <ItemsControl Grid.Row="0" Grid.Column="1" ItemsSource="{Binding AllColumns}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="1"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Converter={StaticResource EnumDisplayConverter}}" HorizontalAlignment="Center" Margin="12 6"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- 左侧行头:显示RowType的Display文本 --> <ItemsControl Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AllRows}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Columns="1"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Converter={StaticResource EnumDisplayConverter}}" VerticalAlignment="Center" Margin="6 12"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- 核心CheckBox网格区域 --> <ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding AllRows}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Columns="1"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <ItemsControl ItemsSource="{Binding DataContext.AllColumns, RelativeSource={RelativeSource AncestorType=ItemsControl, AncestorLevel=2}}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="1"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8"> <CheckBox.IsChecked> <MultiBinding Converter="{StaticResource MatrixEntryLookupConverter}" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> <Binding Path="DataContext.MatrixEntries" RelativeSource="{RelativeSource AncestorType=Window}"/> <Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ItemsControl, AncestorLevel=2}"/> <Binding Path="."/> </MultiBinding> </CheckBox.IsChecked> </CheckBox> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </Window>
实现效果说明
- 网格尺寸会自动跟随枚举值数量适配,修改
RowType、ColumnType枚举项时不需要调整布局代码 - 勾选/取消勾选CheckBox会直接更新对应
MatrixEntry的Value属性,不需要额外做数据同步 - 坐标轴文本自动读取枚举的Display特性值,和预期展示效果完全一致
- 所有逻辑和界面分离,代码后置仅保留默认的
InitializeComponent调用即可,不需要写额外业务逻辑
内容的提问来源于stack exchange,提问作者jimbo
相关产品推荐
相关产品推荐

