【问题标题】:DataGrid: dynamic DataTemplate for dynamic DataGridTemplateColumnDataGrid:动态 DataTemplate 用于动态 DataGridTemplateColumn
【发布时间】:2011-06-06 20:16:33
【问题描述】:

我想在数据网格中显示数据,其中数据是一个集合

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。在此之前,我必须编辑字符串以将绑定更改为想要的 Candidate

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

注意:本题基于dynamic datatemplate with valueconverter

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

【问题讨论】:

  • 你有没有得到这个问题的解决方案或答案?
  • 并非如此。正如我所写,我最终得到了自定义 DataGrid 控件。但我正在考虑开发一个仅基于 Grid 的自定义控件,因为 DataGrid 对我的任务来说非常繁重。

标签: wpf silverlight datagrid datatemplate datagridtemplatecolumn


【解决方案1】:

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

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

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

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

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]绑定替换为实际索引。
  • @LukasCenovsky 我没有发布动态列代码,但您可以在链接中查看。正如我所提到的,这很容易 - foreach (var item in columnsToAdd){ grid.Columns.Add(new DataGridTemplateColumn(){ Header = item.HeaderProperty }; }
  • @LukasCenovsky 添加您想要使用的DataTemplates 作为Resources 并在代码中引用它们。我稍后会尝试编辑我的答案...
  • 动态我的意思是你已经显示了网格并且你想稍后添加/删除列。此解决方案意味着删除所有列并再次添加它们,不是吗?还是我错了?
  • @LukasCenovsky 这将是最简单的方法,是的。但我认为重建整个网格不会有什么大不了的吧?
【解决方案2】:

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

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

例如:(对不起,我没有调整我的代码以适应你的项目,但你应该可以从那里自己做)

这是我的数据模板:

<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,以便能够动态生成模板列...

希望对你有帮助

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

【讨论】:

  • 有趣的想法虽然我不能使用它,因为 Silverlight 不支持MultiBinding
  • arf,很抱歉,我不熟悉 Silverlight,所以我不知道。我可以确认这在 WPF 中运行良好,但我今天再次使用它并开始对这种方法感到非常满意,尽管它很丑。
【解决方案3】:

例如,我们创建 2 个 DataTemplate 和一个 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 & B 示例实现:

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

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

希望这会有所帮助。

【讨论】:

  • 我明白你的意思很遗憾我需要列模板 - 而不是整个网格模板。
猜你喜欢
  • 2019-05-09
  • 2012-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 2011-01-09
相关资源
最近更新 更多