DataGrid:用于动态DataGridTemplateColumn的动态DataTemplate

时间:2011-01-10 22:43:06

标签: 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; }
    public string LastName { get; set; }
    ...
}

候选人名单中的候选人数量在运行时会有所不同。

所需的网格布局如下所示

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

我希望每个 Candidate 都有一个DataTemplate,因为我计划在运行时更改它 - 用户可以选择在不同列中显示候选人的哪些信息(候选人只是一个例如,我有不同的对象)。这意味着我还想在运行时更改列模板,尽管这可以通过一个大模板实现并折叠其部分。

我知道如何实现目标的两种方式(两者非常相似):

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

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

    注意:此问题基于dynamic datatemplate with valueconverter

    编辑:由于我需要同时支持WPF和Silverlight,因此我创建了自己的DataGrid组件,其中DependencyProperty用于绑定一组列。当集合发生变化时,我会更新列。

3 个答案:

答案 0 :(得分:2)

例如,我们创建2个DataTemplates和一个ContentControl:

<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&amp; B示例实施:

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

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

希望这有帮助。

答案 1 :(得分:0)

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

  • 在xaml中制作模板
  • 使用一个multibind,它接受当前绑定+对列的绑定,以获取“正确的”dataContext(即:单元格而不是行)
  • 在此绑定上使用转换器来获取您喜欢的属性的值,如果您要检索许多属性,则可以选择添加参数。

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

这是我的dataTemplate:

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

我希望MS的工作人员能够为我们提供一种方法来获取单元格而不是行作为dataContexts,以便能够动态生成模板化的列...

希望这会有所帮助

编辑:在你的情况下,转换器实际应该更简单(你可以直接返回单元格的实例,如果我没有弄错,你不需要任何参数),但是我离开了更复杂的版本,以防其他人有类似的问题

答案 2 :(得分:0)

我一直在寻找类似的问题,并且只发现了一些有用的模式。整个“动态专栏”问题在silverlight中是一个有趣的问题。

昨天我在搜索期间在Travis Pettijohn的网站上找到了这个页面Silverlight DataGrid with Dynamic Columns

以前我一直在使用Colin Eberhardt概述的'索引转换器'模式,只要您使用DataGridTextColumn,效果非常好。一切都可以在后面的代码中完成,我在运行时应用样式没有问题。但是我现在要求应用一些“单元格级”格式 - 更改单元格的背景等 - 这意味着需要DataGridTemplateColumn

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

Travis概述的模式(上面的第一个链接)完全不同。在“运行时”(即页面加载时间),在网格中创建所需的列。这意味着遍历您的集合,并为每个项添加一个具有相应标头的列等。然后为RowLoaded事件实现一个处理程序,并且当每行加载时,只需为每个单元格设置DataContext < / em>到父级的相应属性/属性索引。

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

这使我无需使用索引转换器。您可以在设置Binding时使用cell.DataContext,但对我而言,模板更容易直接绑定到基础对象。

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