【问题标题】:WPF nested datagrid for custom data structure用于自定义数据结构的 WPF 嵌套数据网格
【发布时间】:2017-03-06 12:06:16
【问题描述】:

我有一个自定义数据结构,我需要一个 WPF 组件来代表我的数据结构。组件应该看起来像图片。组件应该是动态的,因此结构中的 ColumnSet 数量可以从 1 到 x。并且每个 columnSet 可以有不同的列数。

public class CustomStructure{
   public List<ColumnSet> ColumnSets{get; set;}
}   

public class ColumnSet{
    public string Name { get; set; }
    public List<Column> ColumnSets{get;}
}

public class Column{
     public string Name { get; set; }
     public List<int> Data{get;}
}

第一行代表 ColumnSet 类的 Name 属性。第二行是 Column 类的 Name 属性。其他行是来自 Column 类的数据。

Wanted WPF component design (image)
我的解决方案

我定义了两个组件。首先表示外表,称为FuzzyTableControl。它有 X 列,只有一行。 第二个代表 FuzzyTableControl 第一行中每一列的 FuzzyInnerTableControl。

FuzzyTableControl.xaml

 <UserControl:Class="FuzzyTableControl">       
   <UserControl.DataContext>
       <viewModel:FuzzyTableViewModel/>
   </UserControl.DataContext>

<DataGrid AutoGenerateColumns="True" IsReadOnly="True"
        CanUserAddRows="False"
        CanUserDeleteRows="False"
        ItemsSource="{Binding DataTable}">
// here is problem 1
    <DataGrid.Columns>
      <DataGridTemplateColumn Header="{Binding}">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <view:FuzzyInnerTableControl/>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>

FuzzyTableViewModel.cs

public class FuzzyTableViewModel : BaseViewModel
    {
        public FuzzyTable Table { get; set; }

        public DataTable DataTable { get; set; }


        public FuzzyTableViewModel()
        {
            Table = FuzzyTable.Generate(3, 2);
            DataTable = new DataTable();

            foreach (var attribute in Table.Attributes)
            {
                DataTable.Columns.Add(new DataColumn(attribute.Name));
            }
            var row = new List<object>();
            foreach (var attribute in Table.Attributes)
                row.Add(attribute);

            DataTable.Rows.Add(row.ToArray());
        }
    }

FuzzyInnerTableControl.xaml 这个很好用

 <UserControl:Class="FuzzyInnerTableControl">
    <UserControl.DataContext>
        <viewModel:FuzzyInnerTableViewModel/>
    </UserControl.DataContext>    
    <DataGrid ItemsSource="{Binding DataTable}"/>
</UserControl>

FuzzyInnerTableViewModel.cs

public class FuzzyInnerTableViewModel : BaseViewModel
    {
        public DataTable DataTable { get; }

        public ColumnSetDouble ColumnSetDouble { get; set; }

        public FuzzyInnerTableViewModel()
        {
            // test
            var table = FuzzyTable.Generate(3, 2);
            ColumnSetDouble = table.ClassAttribute;
            //end test

            DataTable = new DataTable();

            foreach (var attribute in ColumnSetDouble.Columns)
                DataTable.Columns.Add(new DataColumn(attribute.Name));

            for (int rowId = 0; rowId < ColumnSetDouble.Columns[0].Data.Count; rowId++)
            {
                var row = new List<object>();
                foreach (var column in ColumnSetDouble.Columns)
                {
                    row.Add(column.Data[rowId]);
                }

                DataTable.Rows.Add(row.ToArray());
            }

        }

    }

我不知道如何为 FuzzyTableControl 的每一列定义单元格模板。此解决方案创建新列,但我需要从 viewModel 动态加载列。

【问题讨论】:

  • DataGrid 通常由行集合表示,其中每一行都有一些列属性。在尝试显示之前,您可能希望将数据转换为基于行的结构。
  • 如果您想知道如何显示多个表格,您应该已经知道如何显示单个表格(一个ColumnSet)并将其包含在您的问题中。否则,您应该首先弄清楚如何显示单个表格,而不必担心许多表格。这个问题的范围很广。
  • 我刚刚为问题添加了更多细节。
  • 您能否更改您的代码类以使您在 XAML 中使用的名称与代码中的名称匹配?无法真正在代码中的所有 ColumnSets 和 xaml 中的 {Binding DataTable}FuzzyInnerTableControl 等之间进行转换。
  • 我在后面添加了代码。抱歉,我之前忘记了。

标签: c# wpf datagrid


【解决方案1】:

要为所有自动生成的单元格定义模板,您可以尝试使用DataGrid.CellStyle

<DataGrid AutoGenerateColumns="True" IsReadOnly="True"
    CanUserAddRows="False"
    CanUserDeleteRows="False"
    ItemsSource="{Binding DataTable}">
    <DataGrid.CellStyle>
        <Style TargetType="DataGridCell">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <view:FuzzyInnerTableControl/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

如果我有更好的想法,我会编辑。


现在你问为什么&lt;view:FuzzyInnerTableControl DataContext="{Binding}"/&gt; 不起作用。所以我想你已经准备好了你的外部数据表,所以它确实包含FuzzyInnerTableViewModel,这并不完全是微不足道的:

// when creating a column, ensure to set the DataType, so your content won't be reduced to some text
DataTable.Columns.Add(new DataColumn(attribute.Name, typeof(FuzzyInnerTableViewModel)));

// I have no idea what attribute is, but it better be a FuzzyInnerTableViewModel for your use case!
row.Add(attribute);

首先DataGrid会尝试自动生成相对无用的DataGridTextColumn,需要改一下。处理AutoGeneratingColumn 事件以更改列类型。 DataGridTemplateColumn 实际上不会为您提供单元格内容并且如果您(像我一样)尝试通过CellStyle 设置DataGridCell.Content,它会忽略您并改为在本地设置内容.因此,让我们暂时将单元格值推送到DataGridCell.Tag

这里是FuzzyTableControlDataGrid_AutoGeneratingColumn 方法连接到xaml 中的DataGrid.AutoGeneratingColumn 事件。

public partial class FuzzyTableControl : UserControl
{
    public FuzzyTableControl()
    {
        InitializeComponent();
    }

    private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        e.Column = new DataGridTemplateColumn
        {
            CellStyle = new Style(typeof(DataGridCell))
            {
                Setters =
                {
                    new Setter(DataGridCell.TagProperty, new Binding(e.PropertyName)),
                }
            },
            Header = e.Column.Header,
            HeaderStringFormat = e.Column.HeaderStringFormat,
            HeaderStyle = e.Column.HeaderStyle,
            HeaderTemplate = e.Column.HeaderTemplate,
            HeaderTemplateSelector = e.Column.HeaderTemplateSelector,
            // transfer whatever properties you feel worthy in your scenario
        };
    }
}

现在,至少所需的数据在 Tag 的某个地方徘徊,但猜猜看 - DataGrid.CellStyle 刚刚被替换为主要关注动态绑定某些数据的样式。因此,让我们将 DataTemplate 移动到资源中,并将绑定添加到某个父 DataGridCell.Tag,而它的热...

<UserControl.Resources>
    <DataTemplate x:Key="FuzzyInnerTableTemplate">
        <view:FuzzyInnerTableControl
            DataContext="{Binding Tag,RelativeSource={RelativeSource AncestorType=DataGridCell}}"/>
    </DataTemplate>
</UserControl.Resources>

并将模板与 DataGridTemplateColumn 列生成中的所有其他内容连接起来

            // ...
            // transfer whatever properties you feel worthy in your scenario
            CellTemplate = Resources["FuzzyInnerTableTemplate"] as DataTemplate

FuzzyInnerTableControl 中删除UserControl.DataContext,否则绑定会惨遭失败,而您仍然会获得默认值。如果您需要组合默认值和外部提供的值,请寻找其他方法。


困惑?好吧,我也是,所以这里是我的测试代码的摘要,包括一些编造的行和列,因为我没有你的全部 Column.DataTable.Attributes 东西可用:

FuzzyTable 的 XAML(控件、视图模型)

<UserControl x:Class="WpfApplication2.FuzzyTableControl"
             ...
    >
    <UserControl.DataContext>
        <viewModel:FuzzyTableViewModel/>
    </UserControl.DataContext>
    <UserControl.Resources>
        <DataTemplate x:Key="FuzzyInnerTableTemplate">
            <view:FuzzyInnerTableControl
                DataContext="{Binding Tag,RelativeSource={RelativeSource AncestorType=DataGridCell}}"/>
        </DataTemplate>
    </UserControl.Resources>

    <DataGrid AutoGenerateColumns="True" IsReadOnly="True"
        CanUserAddRows="False"
        CanUserDeleteRows="False"
        ItemsSource="{Binding DataTable}"
        AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">
    </DataGrid>
</UserControl>

模糊表代码

public partial class FuzzyTableControl : UserControl
{
    public FuzzyTableControl()
    {
        InitializeComponent();
    }

    private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        e.Column = new DataGridTemplateColumn
        {
            CellStyle = new Style(typeof(DataGridCell))
            {
                Setters =
                {
                    new Setter(DataGridCell.TagProperty, new Binding(e.PropertyName)),
                }
            },
            Header = e.Column.Header,
            HeaderStringFormat = e.Column.HeaderStringFormat,
            HeaderStyle = e.Column.HeaderStyle,
            HeaderTemplate = e.Column.HeaderTemplate,
            HeaderTemplateSelector = e.Column.HeaderTemplateSelector,
            // transfer whatever properties you feel worthy in your scenario
            CellTemplate = Resources["FuzzyInnerTableTemplate"] as DataTemplate
        };
    }
}

public class FuzzyTableViewModel : BaseViewModel
{
    public DataTable DataTable { get; set; }


    public FuzzyTableViewModel()
    {
        DataTable = new DataTable();

        DataTable.Columns.Add(new DataColumn("O", typeof(FuzzyInnerTableViewModel)));
        DataTable.Columns.Add(new DataColumn("P", typeof(FuzzyInnerTableViewModel)));

        var c1 = new FuzzyInnerTableViewModel();
        var c2 = new FuzzyInnerTableViewModel();

        c1.DataTable.Rows[1][0] = "Replace";

        var row = new List<object>();
        row.Add(c1);
        row.Add(c2);

        DataTable.Rows.Add(row.ToArray());
    }
}

FuzzyInnerTable 的 XAML

<UserControl x:Class="WpfApplication2.FuzzyInnerTableControl"
             ...
             Loaded="UserControl_Loaded">
    <UserControl.Resources>
        <viewModel:FuzzyInnerTableViewModel x:Key="defaultVM"/>
    </UserControl.Resources>
    <DataGrid ItemsSource="{Binding DataTable}"/>
</UserControl>

FuzzyInnerTable 的代码

public partial class FuzzyInnerTableControl : UserControl
{
    public FuzzyInnerTableControl()
    {
        InitializeComponent();
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        if (DataContext == null)
        {
            DataContext = Resources["defaultVM"];
        }

    }
}

public class FuzzyInnerTableViewModel : BaseViewModel
{
    public DataTable DataTable { get; private set; }

    public FuzzyInnerTableViewModel()
    {
        DataTable = new DataTable();

        DataTable.Columns.Add(new DataColumn("A"));
        DataTable.Columns.Add(new DataColumn("B"));

        for (int rowId = 0; rowId < 3; rowId++)
        {
            var row = new List<object>();
            row.Add(rowId);
            row.Add(2 * rowId);

            DataTable.Rows.Add(row.ToArray());
        }
    }
}

【讨论】:

  • 谢谢,这正是我所需要的。还有一个问题。在行中,我创建了新的 FuzzyInnerTableViewModel,我想绑定它。但是这段代码不起作用
  • @Patrik “不起作用”是一个非常模糊的问题描述。是否抛出错误,是否将某些内容记录到调试器输出中,是否未显示(如果是,Snoop 或类似的 WPF 运行时调试器会告知可用数据上下文什么?)
  • 新数据未更新,仍显示默认数据。在数据上下文选项卡的窥探中,有默认的 ViewModel。
  • @Patrik 哇,它真的变得超级丑陋,因为 DataGrid 不会费心为任何有用的用途提供单元格内容。最重要的是,您设置 UserControl.DataContext 的方法会干扰绑定,因此我将不得不解决很多糟糕的细节,以实现应该很容易的事情......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
相关资源
最近更新 更多