tags:

views:

111

answers:

2

I am writing a WPF program in C# in which I have a ListView for which the columns will be populated at runtime. I would like to use a custom DataTemplate for the GridViewColumn objects in the ListView.

In the examples I have seen where the number of columns is fixed in advance, a custom DataTemplate is often created using something like the XAML below.

<DataTemplate x:Key="someKey">
    <TextBlock Text="{Binding Path=FirstName}" />
</DataTemplate>

This DataTemplate could also later be assigned to GridViewColumn.CellTemplate in the code-behind by calling FindResource("someKey"). However, this alone is of no use to me, because in this example the Path element is fixed to FirstName. Really I need something where I can set the Path in code.

It is my impression that something along these lines may be possible if XamlReader is used, but I'm not sure how in practice I would do this. Any solutions are greatly appreciated.

A: 

Maybe GridViewColumn.CellTemplateSelector would help you? Or you could create a user control, bind it to something big enough (that's the same for each column). Then let this control figure out what exactly it should display based on what it has in DataContext...

Anvaka
A: 

It is easy to build what you need using two DataTemplates working in concert: The outer DataTemplate simply sets the DataContext for the inner DataTemplate, as follows:

<DataTemplate x:Key="DisplayTemplate">
  <Border ...>
    <TextBlock Text="{Binding}" ... />
  </Border>
</DataTemplate>

<DataTemplate x:Key="CellTemplate">
  <ContentPresenter Content="{Binding FirstName}"
                    ContentTemplate="{StaticResource DisplayTemplate}" />
</DataTemplate>

The only tricky thing is making it convenient to set this on a GridViewColumn. I would accomplish this with attached properties, allowing you to write:

<GridViewColumn
  my:GVCHelper.DisplayPath="FirstName"
  my:GVCHelper.Template="{StaticResource DisplayTemplate}" />

Or equivalently in code:

var col = new GridViewColumn();
GVCHelper.SetDisplayPath(col, "FirstName");
GVCHelper.SetTemplate(col, (DataTemplate)FindResource("DisplayTemplate"));

Either of these would cause the DataTemplate named "DisplayTemplate" to be used to display the FirstName in the column.

The helper class would be implemented as:

public class GVCHelper : DependencyObject
{
  public static string GetDisplayPath(DependencyObject obj) { return (string)obj.GetValue(DisplayPathProperty); }
  public static void SetDisplayPath(DependencyObject obj, string value) { obj.SetValue(DisplayPathProperty, value); }
  public static readonly DependencyProperty DisplayPathProperty = DependencyProperty.RegisterAttached("DisplayPath", typeof(string), typeof(GVCHelper), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) => Update(obj)
  });

  public static DataTemplate GetTemplate(DependencyObject obj) { return (DataTemplate)obj.GetValue(TemplateProperty); }
  public static void SetTemplate(DependencyObject obj, DataTemplate value) { obj.SetValue(TemplateProperty, value); }
  public static readonly DependencyProperty TemplateProperty = DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(GVCHelper), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) => Update(obj)
  });

  private static void Update(DependencyObject obj)
  {
    var path = GetDisplayPath(obj);
    var template = GetTemplate(obj);
    if(path!=null && template!=null)
    {
      var factory = new FrameworkElementFactory(typeof(ContentPresenter));
      factory.SetBinding(ContentPresenter.ContentProperty, new Binding(path));
      factory.SetValue(ContentPresenter.ContentTemplateProperty, template);
      obj.SetValue(GridViewColumn.CellTemplateProperty,
        new DataTemplate { VisualTree = factory };
    }
  }
}

How it works: Whenever the properties are both set, a new DataTemplate is constructed and the GridViewColumn.CellTemplate property is updated.

Ray Burns