Wpf DataGrid:动态DataGridTemplateColumn的动态DataTemplate

Wpf DataGrid:动态DataGridTemplateColumn的动态DataTemplate,wpf,silverlight,datagrid,datatemplate,datagridtemplatecolumn,Wpf,Silverlight,Datagrid,Datatemplate,Datagridtemplatecolumn,我想在datagrid中显示数据,其中的数据是 public class Thing { public string Foo { get; set; } public string Bar { get; set; } public List<Candidate> Candidates { get; set; } } public class Candidate { public string FirstName { get; set; } p

我想在datagrid中显示数据,其中的数据是

public class Thing
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public List<Candidate> Candidates { get; set; }
}

public class Candidate
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    ...
}
当我计划在运行时更改它时,我希望为每个候选对象设置一个
DataTemplate
——用户可以选择在不同的列中显示关于候选对象的哪些信息(候选对象只是一个示例,我有不同的对象)。这意味着我还想在运行时更改列模板,尽管这可以通过一个大模板并折叠其部分来实现

我知道实现目标的两种方法(两种方法非常相似):

  • 使用
    AutoGeneratingColumn
    事件并创建候选列
  • 手动添加列
  • 在这两种情况下,我都需要使用
    XamlReader
    从字符串加载
    DataTemplate
    。在此之前,我必须编辑字符串以将绑定更改为所需候选

    有没有更好的方法来创建具有未知数量DataGridTemplateColumn的DataGrid?

    注:此问题基于


    Edit:由于我需要同时支持WPF和Silverlight,我创建了自己的
    DataGrid
    组件,该组件具有
    DependencyProperty
    用于绑定列集合。当集合更改时,我会更新列。

    例如,我们创建2个数据模板和一个ContentControl:

    <DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
    <DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>
    
    <ContentControl Content="{Binding Path=GridModel}" />
    
    
    
    现在,如果将GridModel属性(例如type对象)设置为Variata或VariantB,它将切换DataTemplate

    Variata&B示例实现:

    public class VariantA
    {
        public ObservableCollection<ViewModel1> DataList { get; set; }
    }
    
    public class VariantB
    {
        public ObservableCollection<ViewModel2> DataList { get; set; }
    }
    
    公共类变量
    {
    公共ObservableCollection数据列表{get;set;}
    }
    公共类
    {
    公共ObservableCollection数据列表{get;set;}
    }
    
    希望这有帮助。

    我不知道这是否是一种“更好”的方式,因为这仍然很难看,但我个人喜欢这样:

    • 在xaml中创建模板
    • 使用多绑定,将当前绑定+对列的绑定结合起来,以获得“正确”的dataContext(即:单元格而不是行)
    • 在此绑定上使用转换器获取所需属性的值,如果要检索多个属性,可以选择添加参数
    e、 g.:(对不起,我没有调整我的代码以适合您的项目,但您应该可以从那里自己做)

    这是我的数据模板:

    <DataTemplate x:Key="TreeCellTemplate">
        <Grid>
            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
                <TextBlock.Text>
                    <MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
                        <Binding />
                        <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </Grid>
    </DataTemplate>
    
    这保存了MVVM模型,我更喜欢这种方式,因为我真的不喜欢使用xaml解析器来创建“动态”数据模板,但从我的角度来看,这仍然是一种丑陋的攻击

    我希望微软的人能给我们一种方法,让我们把单元格而不是行作为数据上下文,以便能够动态生成模板列

    希望这有帮助


    编辑:在您的情况下,转换器实际上应该简单得多(如果我没有弄错的话,您可以直接返回单元格的实例,并且您不需要任何参数),但我保留了更复杂的版本,以防万一,其他人也有类似的问题

    我一直在研究一个类似的问题,只发现了一些有用的模式。整个“动态列”问题在silverlight中是一个有趣的问题

    昨天我在Travis Pettijohn的网站上搜索到了这个页面

    之前,我一直在使用“索引转换器”模式,该模式的工作原理非常好。。。只要使用
    DataGridTextColumn
    。一切都可以在代码隐藏中完成,我在运行时应用样式也没有问题。但是,我现在的要求是应用一些“单元格级别”格式—更改单元格的背景等—这意味着需要
    DataGridTemplateColumn

    对我来说,
    DataGridTemplateColumn
    的最大问题是无法在代码中设置绑定。我知道我们可以通过解析xaml来构建它,但和其他人一样,这似乎是一个巨大的黑客攻击,无法维护到第n个

    特拉维斯(上面的第一个链接)描述的模式完全不同。在“运行时”(即页面加载时),在网格中创建所需的列。这意味着迭代您的集合,并为每个项添加一列,其中包含适当的标题等。然后为
    RowLoaded
    事件实现一个处理程序,当加载每一行时,只需将每个单元格的DataContext设置为父项的适当属性/属性索引

    private void MyGrid_RowLoaded(object sender, EventArgs e)
    {
        var grid = sender as DataGrid;
        var myItem = grid.SelectedItem as MyClass;
        foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
        { 
            var column = grid.Columns[i]; 
            var cell = column.GetCellContent(e.Row)
            cell.DataContext = myItem.ColumnObjects[i];
        }
    }
    
    private void MyGrid\u行已加载(对象发送方,事件参数e)
    {
    var grid=发送方作为数据网格;
    var myItem=grid.SelectedItem作为MyClass;
    foreach(int i=0;i
    这使我不再需要使用索引转换器。在设置
    cell.DataContext
    时,您可能可以使用
    绑定
    ,但对我来说,让模板直接绑定到底层对象更容易


    我现在计划拥有多个模板(每个模板都可以绑定到我的单元格对象上的相同属性),并在页面加载时在它们之间切换。非常整洁的解决方案。

    我明白你的意思,不幸的是我需要列模板,而不是整个网格模板。虽然我不能使用它,但这是一个有趣的想法,因为Silverlight不支持
    多绑定。
    。arf,很抱歉,我不熟悉Silverlight,所以我不知道。我可以确认这在WPF中非常有效,我今天再次使用了它,并开始对这个方法感到非常满意,尽管它很难看。你有没有得到这个问题的解决方案或答案?没有。正如我写的那样,我最终得到了自定义
    DataGrid
    控件。但是我正在考虑开发一个基于
    Grid
    的自定义控件,因为
    DataGrid
    对于我的任务来说非常繁重。这个
    rowload
    看起来是一个非常好的解决方案。虽然我不认为这是一个错误
       public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
       {
          public RowColumnToCellConverter() { }
    
          public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
          {
             XwpfRow row = values[0] as XwpfRow;
             XwpfTreeColumn column = values[1] as XwpfTreeColumn;
    
             if (row == null || column == null) return DependencyProperty.UnsetValue;
    
             TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
             switch ((string)parameter)
             {
                case "Text": return treeCell.Text;
                case "Expanded": return treeCell.Expanded;
                case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
                case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);
    
                default:
                   throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
             }
          }
    
          public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
          {
             throw new NotSupportedException();
          }
    
          public override object ProvideValue(IServiceProvider serviceProvider)
          {
             return new RowColumnToCellConverter();
          }
       }
    
    private void MyGrid_RowLoaded(object sender, EventArgs e)
    {
        var grid = sender as DataGrid;
        var myItem = grid.SelectedItem as MyClass;
        foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
        { 
            var column = grid.Columns[i]; 
            var cell = column.GetCellContent(e.Row)
            cell.DataContext = myItem.ColumnObjects[i];
        }
    }