You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带行列标签、可编辑的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 04:48:34