Create Datatemplate in Codebehind

How to create DataTemplate with ItemsControl from code behind WPF

You could use the XamlReader.Parse method to create an elements from a XAML string dynamically:

const string Xaml = "<ItemsControl ItemsSource=\"{Binding ListOfSubObjects}\">" +
" <ItemsControl.ItemsPanel>" +
" <ItemsPanelTemplate>" +
" <StackPanel Orientation=\"Vertical\"></StackPanel>" +
" </ItemsPanelTemplate>" +
" </ItemsControl.ItemsPanel>" +
" <ItemsControl.ItemTemplate>" +
" <DataTemplate>" +
" <TextBlock Text=\"{Binding SubObjectName}\"/>" +
" </DataTemplate>" +
" </ItemsControl.ItemTemplate>" +
" </ItemsControl>";

ParserContext parserContext = new ParserContext();
parserContext.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
parserContext.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");

ItemsControl itemsControl = XamlReader.Parse(Xaml, parserContext) as ItemsControl;

How to create instance of control from DataTemplate in code behind

Call LoadContent() and cast the result:

var template = resourceDictionary["Button"] as DataTemplate;
var control = template.LoadContent() as Button;

<DataTemplate x:Key="Button">
<Button Content="btn" />
</DataTemplate>

Creating DataTemplate from code behind

The missing lines are:

var ellipseVisBinding = new Binding("isAdmin");
ellipseVisBinding.Converter = new BooleanToVisibilityConverter();

fef.SetBinding(Ellipse.VisibilityProperty, ellipseVisBinding);

(I note that you've excluded the DockPanel in the template from your code version so I've removed that as well)

wpf applying datatemplates in code-behind

Your control can expose its own property/properties, which you declare in your code-behind.
If you need a single DataTemplate, then you can expose a property of type DataTemplate. When a user declares your control type in XAML, she can provide the template:

<ns:YourControl>
<ns:YourControl.DataTemplate>
<DataTemplate>

</DataTemplate>
</ns:YourControl.DataTemplate>
</ns:YourControl>

In your own control, you consume this by binding to the DataTemplate property. Be sure to reference the control itself in your Binding, rather than the DataContext. You'll probably want a default DataTemplate or throw a useful Exception in the event that the user does not specify a DataTemplate.

You can give the user some additional flexibility if you expose a property of type DataTemplateSelector and then apply that to your items, if the data types are disparate or the user is likely to want different templates under different circumstances.

Example

MyControl.xaml

<UserControl x:Class="MyNamespace.MyControl"
x:Name="ThisControl">
<ItemsControl ItemTemplate="{Binding ItemTemplate, ElementName=ThisControl}" />
</UserControl>

MyControl.xaml.cs

public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}

public static readonly DependencyProperty ItemTemplateProperty
= DependencyProperty.Register("ItemTemplate", typeof (DataTemplate),
typeof (MyControl), new PropertyMetadata(default(DataTemplate)));

public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}

// Other dependency properties (ItemsSource, SelectedItem, etc.)
}

The consumer:

<Grid>
<ns:MyControl ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ns:MyControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2"
BorderBrush="Black">
<TextBlock Foreground="DarkGray"
Text="{Binding Name}"
Margin="4" />
</Border>
</DataTemplate>
</ns:MyControl.ItemTemplate>
</ns:MyControl>
</Grid>

Update

Okay, here is a working example of populating the Grid and using the DataTemplate.

MyControl exposes a property, ItemsSource, which allows the consumer to bind to a collection in her view-model. MyControl also exposes a property, ItemTemplate, which allows a consumer to specify how to display those items (again, you could also allow the user to specify a DataTemplateSelector).

In the code-behind, when the source collection changes, we

  1. create a ColumnDefinition for each item,
  2. wrap each item inside another class that exposes Row and Column properties, and
  3. add each wrapped item to a private collection, which is what we actually bind to in our control.

First, the XAML:

<UserControl x:Class="WpfApplication1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="ThisControl"
d:DesignHeight="300" d:DesignWidth="300">
<ItemsControl x:Name="ItemsControl"
ItemsSource="{Binding BindableItems, ElementName=ThisControl, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Row" Value="{Binding Row}" />
<Setter Property="Grid.Column" Value="{Binding Column}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"
ContentTemplate="{Binding ItemTemplate, ElementName=ThisControl}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</UserControl>

And the code-behind:

using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}

public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof (IEnumerable), typeof (MyControl),
new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged));

public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}

// This is the DataTemplate that the consumer of your control specifies
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate", typeof (DataTemplate), typeof (MyControl), new PropertyMetadata(default(DataTemplate)));

public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}

// This is declared private, because it is only to be consumed by this control
private static readonly DependencyProperty BindableItemsProperty = DependencyProperty.Register(
"BindableItems", typeof (ObservableCollection<object>), typeof (MyControl), new PropertyMetadata(new ObservableCollection<object>()));

private ObservableCollection<object> BindableItems
{
get { return (ObservableCollection<object>) GetValue(BindableItemsProperty); }
set { SetValue(BindableItemsProperty, value); }
}

private static void OnItemsSourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var myControl = dependencyObject as MyControl;
if (myControl == null)
{
return;
}

// Get reference to the Grid using reflection. You could also walk the tree.
var grid = (Grid) typeof (ItemsControl).InvokeMember("ItemsHost",
BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance,
null, myControl.ItemsControl, null);

var columns = grid.ColumnDefinitions;
columns.Clear();
myControl.BindableItems.Clear();

var items = args.NewValue as IEnumerable;
if (items != null)
{
var columnIndex = 0;
foreach (var item in items)
{
columns.Add(new ColumnDefinition{ Width = GridLength.Auto });
var container = new MyItem
{
Row = columnIndex,
Column = columnIndex++,
Content = item
};
myControl.BindableItems.Add(container);
}
}
}
}

public class MyItem
{
public object Content { get; set; }
public int Row { get; set; }
public int Column { get; set; }
}
}


Related Topics



Leave a reply



Submit