Search code examples
c#wpfresourcescontrolsdatatemplate

How to create a DataTemplate using C# and set child control's resources?


How would I go about creating some resources for a particular control in code?

This data template defines how a column of a listview should present its content. However, due to the peculiar nature of this view, I have to create the columns in code to assign the required properties correctly. That all works fine, with the single exception that I haven't found the proper way to assign the content control's internal data template to the content control's resource dictionary in code (This is required because it changes according to the type of the binding, imagine other templates defined in "..."). The only part I am missing is the "translation" of <ContentControl.Resources> from xaml.

<DataTemplate>
    <ContentControl Content="{Binding}" Focusable="False">
        <ContentControl.Resources>
            <DataTemplate DataType="{x:Type data:DataItem}">
                <StackPanel Orientation="Horizontal">
                    <TextBox Text="{Binding FirstProperty}"/>
                    <Label Content=" - "/>
                    <TextBox Text="{Binding SecondProperty}"/>
                </StackPanel>
            </DataTemplate>
            ...
        </ContentControl.Resources>
    </ContentControl>
</DataTemplate>

Here is the cs code I have so far:

private DataTemplate GenerateSomeTemplate()
{
    DataTemplate template = new DataTemplate(typeof(TextBlock));
    FrameworkElementFactory contentElement = new FrameworkElementFactory(typeof(ContentControl));
    template.VisualTree = contentElement;
    contentElement.SetBinding(ContentControl.ContentProperty, new Binding() { }); // Might be wrong
    contentElement.SetValue(ContentControl.FocusableProperty, false);

    var displayTemplate = new DataTemplate(typeof(DataItem));
    var layout = new FrameworkElementFactory(typeof(StackPanel));

    var textBoxFirst = new FrameworkElementFactory(typeof(TextBox));
    textBoxFirst.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("FirstProperty") });
    layout.AppendChild(textBoxFirst);

    var dashLabel = new FrameworkElementFactory(typeof(Label));
    dashLabel.SetValue(Label.ContentProperty, " - ");
    layout.AppendChild(dashLabel);

    var textBoxSecond = new FrameworkElementFactory(typeof(TextBox));
    textBoxFirst.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("SecondProperty") });
    layout.AppendChild(textBoxSecond);

    displayTemplate.VisualTree = layout;

    // contentElement.AddResource(displayTemplate); // I need something like this...

    return template;
}

Solution

  • FrameworkElementFactory:

    This class is a deprecated way to programmatically create templates, which are subclasses of FrameworkTemplate such as ControlTemplate or DataTemplate; not all of the template functionality is available when you create a template using this class. The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class.

    This means, you should use the XmlReader to activate the DataTemplate from a string:

    var dataTemplateString = 
      @"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" 
                      xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">    
          <ContentControl Content=""{Binding}"" Focusable=""False"">
            <ContentControl.Resources>
              <DataTemplate DataType=""{x:Type data:DataItem}"">
                <StackPanel Orientation=""Horizontal"">
                  <TextBox Text=""{Binding FirstProperty}""/>
                  <Label Content="" - ""/>
                  <TextBox Text=""{Binding SecondProperty}""/>
                </StackPanel>
              </DataTemplate>
            </ContentControl.Resources>
          </ContentControl>
        </DataTemplate>";
    
    var stringReader = new StringReader(dataTemplateString);
    XmlReader xmlReader = XmlReader.Create(stringReader);
    DataTemplate dataTemplate = (DataTemplate) XamlReader.Load(xmlReader);
    

    Alternatively you can serialize an existing DataTemplate using XamlWriter.Save() and then actually activate it using XamlReader.Load() (Serialization Limitations of XamlWriter.Save).

    There also exists an asynchronous implementation to load XAML objects at runtime: XamlReader.LoadAsync.