WPF嵌套数据网格以用于自定义数据结构

时间:2017-03-06 12:06:17

标签: c# wpf datagrid

我有一个自定义数据结构,我需要一个代表我的数据结构的WPF组件。组件应该看起来像在图片上。组件应该是动态的,因此结构中的ColumnSets数量可以是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的每一列定义单元格模板。此解决方案创建NEW列,但我需要从viewModel动态加载列。

1 个答案:

答案 0 :(得分:0)

要为所有自动生成的单元格定义模板,您可以尝试使用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>

如果我有更好的主意,我会编辑。

现在你问为什么<view:FuzzyInnerTableControl DataContext="{Binding}"/>无效。所以我想你已经准备好了你的外部DataTable,所以它真的包含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实际上不会为您提供单元格内容,如果您(像我一样)尝试通过DataGridCell.Content设置CellStyle,它会好友不理你并在本地设置内容。因此,暂时将单元格值推送到DataGridCell.Tag

以下FuzzyTableControl DataGrid_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,同时又热... {/ p>

<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

UserControl.DataContext移除FuzzyInnerTableControl,否则绑定将失败并且您仍然会获得默认值。如果您需要组合默认值和外部提供的值,请查找其他方法。

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

用于FuzzyTable的XAML(Control,ViewModel)

<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>

FuzzyTable的代码

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());
        }
    }
}