DataGrid:动态DataTemplate用于动态DataGridTemplateColumn

3

我希望能够在数据网格中显示数据,其中数据是一个集合。

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; }
    ...
}

在候选人列表中,候选人数量会随着时间变化而不同。

所需的网格布局如下所示

Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N

我希望为每个“候选人”创建一个DataTemplate,因为我计划在运行时更改它 - 用户可以选择在不同列中显示有关候选人的哪些信息(候选人只是一个示例,我有不同的对象)。这意味着我还想在运行时更改列模板,尽管这可以通过一个大模板并折叠其部分来实现。
我知道两种实现我的目标的方法(都非常相似):
1. 使用AutoGeneratingColumn事件并创建“候选人”列 2. 手动添加列
在这两种情况下,我需要使用XamlReader从字符串加载DataTemplate。在此之前,我必须编辑字符串以将绑定更改为所需的“候选人”。 是否有更好的方法来创建具有未知数量的DataGridTemplateColumn的DataGrid? 注:此问题基于dynamic datatemplate with valueconverter 编辑:由于我需要支持WPF和Silverlight,因此我创建了自己的DataGrid组件,其中包含用于绑定列集合的DependencyProperty。当集合更改时,我更新列。

你是否曾经解决了这个问题或得到了答案? - Kirk Broadhurst
不是很对。正如我所写的,我最终使用了自定义的“DataGrid”控件。但是我正在考虑开发一个基于“Grid”的自定义控件,因为“DataGrid”对于我的任务来说相当沉重。 - Lukas Cenovsky
3个回答

2
例如,我们创建了两个数据模板和一个内容控件:

<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>

<ContentControl Content="{Binding Path=GridModel}" />

现在,如果您将GridModel属性(例如类型为对象)设置为VariantA或VariantB,则会切换DataTemplate。

VariantA和B的示例实现:

public class VariantA
{
    public ObservableCollection<ViewModel1> DataList { get; set; }
}

public class VariantB
{
    public ObservableCollection<ViewModel2> DataList { get; set; }
}

希望这可以帮到您。

我理解你的意思,不幸的是我需要列模板 - 而不是整个网格模板。 - Lukas Cenovsky

0

我不知道这是否是一种“更好”的方法,因为这仍然相当丑陋,但我个人喜欢这样做:

  • 在xaml中制作模板
  • 使用Multibind获取当前绑定+列的绑定以获取“正确”的数据上下文(即:单元格而不是行)
  • 在此绑定上使用转换器来获取您喜欢的属性的值,并在需要检索多个属性时可选添加参数。

例如:(抱歉,我没有调整我的代码以适应您的项目,但您应该能够从那里自己完成)

这是我的数据模板:

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

这是我的转换器:

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

这样可以保存MVVM模型,我更喜欢这种做法,因为我真的不喜欢使用xaml解析器来制作“动态”数据模板,但从我的角度来看,这仍然是一个丑陋的Hack。

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

希望这有所帮助。

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


有趣的想法,但我不能使用它,因为Silverlight不支持MultiBinding - Lukas Cenovsky
抱歉,我不熟悉Silverlight,所以不知道。但我可以确认在WPF中这个方法非常有效,今天我又用了一次,开始对它感到满意了,尽管它有点丑陋。 - David

0

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

昨天在我的搜索中,我发现了Travis Pettijohn网站上的Silverlight DataGrid with Dynamic Columns页面。

以前,我一直在使用Colin Eberhardt提出的“索引转换器”模式,这个模式非常好用...只要你使用DataGridTextColumn。所有的东西都可以在代码后台完成,而且我在运行时应用样式也没有遇到任何问题。然而,我的要求现在是应用一些“单元格级别”的格式 - 改变单元格的背景等 - 这意味着需要使用DataGridTemplateColumn

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

Travis(上面的第一个链接)概述的模式完全不同。在“运行时”(即页面加载时),创建您网格中所需的列。这意味着遍历您的集合,并为每个具有适当标题等的项目添加列。然后实现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];
    }
}

这使我不再需要使用索引转换器。当设置cell.DataContext时,您可能可以使用Binding,但对于我来说,让模板直接绑定到底层对象更容易。

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


这个 RowLoaded 看起来是一个非常好的解决方案。虽然当您需要动态添加和删除列时,我不认为它是一个解决方案。那么最好从字符串加载模板,并将 myItem.ColumnObjects[i] 绑定替换为实际索引。 - Lukas Cenovsky
@LukasCenovsky我还没有发布动态列的代码,但您可以在链接中查看。 如我所提到的那样,这是容易的部分 - foreach(var item in columnsToAdd){ grid.Columns.Add(new DataGridTemplateColumn(){Header = item.HeaderProperty};} - Kirk Broadhurst
@LukasCenovsky,这将是最简单的方法,是的。但我认为重建整个网格不会很麻烦,不是吗? - Kirk Broadhurst
我认为这取决于模板。当我创建数据网格时,遇到了性能问题 - 它需要几秒钟的时间,因此我不得不分批加载列以避免UI冻结太多(就像在旧的WinForm时代一样 :)。 - Lukas Cenovsky
@LukasCenovsky 如果可能的话,请确保使用.NET 4.0(与3.5相比)进行WPF渲染,效果会好得多。 - Kirk Broadhurst
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接