Goal
Why this matters
FuncControlTemplate, FuncDataTemplate, and selectors to mirror the flexibility of XAML.Prerequisites
FuncControlTemplateFuncControlTemplate<T> (source: external/Avalonia/src/Avalonia.Controls/Templates/FuncControlTemplate.cs) produces a ControlTemplate that builds visuals from code. It takes a lambda that receives the templated parent and returns a Control/IControl tree.
public static ControlTemplate CreateCardTemplate()
{
return new FuncControlTemplate<ContentControl>((parent, scope) =>
{
var border = new Border
{
Background = Brushes.White,
CornerRadius = new CornerRadius(12),
Padding = new Thickness(16),
Child = new ContentPresenter
{
Name = "PART_ContentPresenter"
}
};
scope?.RegisterNamed("PART_ContentPresenter", border.Child);
return border;
});
}
Attach the template to a control:
var card = new ContentControl
{
Template = CreateCardTemplate(),
Content = new TextBlock { Text = "Dashboard" }
};
Notes from the source implementation:
INameScope scope) lets you register named parts exactly like <ControlTemplate> does in XAML. Use it to satisfy template part lookups in your control’s code-behind.TemplatedParentUse TemplateBinding helpers (TemplateBindingExtensions) to bind template visual properties to the templated control.
return new Border
{
Background = Brushes.White,
[!Border.BackgroundProperty] = parent.GetTemplateBinding(ContentControl.BackgroundProperty),
Child = new ContentPresenter()
};
The [!Property] indexer syntax is shorthand for creating a template binding (enabled by the Avalonia.Markup.Declarative helpers). If you prefer explicit code, use TemplateBindingExtensions.Bind:
var presenter = new ContentPresenter();
presenter.Bind(ContentPresenter.ContentProperty, parent.GetTemplateBinding(ContentControl.ContentProperty));
TemplateBindingExtensions.cs shows this helper returns a lightweight binding linked to the templated parent’s property value.
FuncDataTemplateFuncDataTemplate<T> (source: FuncDataTemplate.cs) creates visuals for data items. Often you assign it to ContentControl.ContentTemplate or ItemsControl.ItemTemplate.
var itemTemplate = new FuncDataTemplate<OrderItem>((item, _) =>
new Border
{
Margin = new Thickness(0, 0, 0, 12),
Child = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 12,
Children =
{
new TextBlock { Text = item.ProductName, FontWeight = FontWeight.SemiBold },
new TextBlock { Text = item.Quantity.ToString() }
}
}
}, recycle: true);
Pass recycle: true to participate in virtualization (controls are reused). Attach to an ItemsControl:
itemsControl.ItemTemplate = itemTemplate;
Because the template receives the data item, you can access its properties directly or create bindings relative to the template context.
var template = new FuncDataTemplate<Customer>((item, scope) =>
{
var balance = new TextBlock();
balance.Bind(TextBlock.TextProperty, new Binding("Balance")
{
StringFormat = "{0:C}"
});
return new StackPanel
{
Children =
{
new TextBlock { Text = item.Name },
balance
}
};
});
FuncDataTemplate sets the DataContext to the item automatically, so bindings with explicit paths work without additional setup.
FuncDataTemplate supports predicates for conditional templates. Use the overload that accepts a Func<object?, bool> predicate.
var positiveTemplate = new FuncDataTemplate<Transaction>((item, _) => CreateTransactionRow(item));
var negativeTemplate = new FuncDataTemplate<Transaction>((item, _) => CreateTransactionRow(item, isDebit: true));
var selector = new FuncDataTemplate<Transaction>((item, _) =>
(item.Amount >= 0 ? positiveTemplate.Build(item) : negativeTemplate.Build(item))!,
supportsRecycling: true);
For more complex selection logic, implement IDataTemplate manually or use DataTemplateSelector base classes from community packages.
FuncTreeDataTemplateFuncTreeDataTemplate<T> builds item templates for hierarchical data such as tree views. It receives the item and a recursion function.
var treeTemplate = new FuncTreeDataTemplate<DirectoryNode>((item, _) =>
new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new TextBlock { Text = item.Name }
}
},
x => x.Children,
true);
var treeView = new TreeView
{
Items = fileSystem.RootNodes,
ItemTemplate = treeTemplate
};
The third argument is supportsRecycling. The second argument is the accessor returning child items. This mirrors XAML’s <TreeDataTemplate ItemsSource="{Binding Children}">.
FuncTreeDataTemplate internally wires TreeDataTemplate with lambda-based factories, so you get the same virtualization behaviour as XAML templates.
InstancedBinding (source: external/Avalonia/src/Avalonia.Data/Core/InstancedBinding.cs) lets you precompute a binding for a known source. It’s powerful when a template needs to bind to an item-specific property or when you assemble UI from graphs.
var binding = new Binding("Metrics[\"Total\"]") { Mode = BindingMode.OneWay };
var instanced = InstancedBinding.OneWay(binding, metricsDictionary);
var text = new TextBlock();
text.Bind(text.TextProperty, instanced);
Because you supply the source (metricsDictionary), the binding bypasses DataContext. This is useful in templates where you juggle multiple sources (e.g., templated parent + external service).
Within templates you can reference named parts registered through scope.RegisterNamed. After applying the template, resolve them via TemplateAppliedEventArgs.
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
}
From code-first templates, ensure the name scope registration occurs inside the template lambda as shown earlier.
Because templates are just CLR objects, you can replace them dynamically to support different visual representations.
public void UseCompactTemplates(Window window)
{
window.Resources["CardTemplate"] = Templates.CompactCard;
window.Resources["ListItemTemplate"] = Templates.CompactListItem;
foreach (var presenter in window.GetVisualDescendants().OfType<ContentPresenter>())
{
presenter.UpdateChild(); // apply new template
}
}
ContentPresenter.UpdateChild() forces the presenter to re-evaluate its template. GetVisualDescendants comes from VisualTreeExtensions. Consider performance: only call on affected presenters.
Use IStyle triggers or the view-model to change templates automatically. Example using a binding:
contentControl.Bind(ContentControl.ContentTemplateProperty, new Binding("SelectedTemplate")
{
Mode = BindingMode.OneWay
});
The view-model exposes IDataTemplate SelectedTemplate, and your code-first view updates this property to switch visuals.
Wrap template logic in factories that accept data and return controls, useful for plugin systems.
public interface IWidgetFactory
{
bool CanHandle(string widgetType);
Control Create(IWidgetContext context);
}
public sealed class ChartWidgetFactory : IWidgetFactory
{
public bool CanHandle(string widgetType) => widgetType == "chart";
public Control Create(IWidgetContext context)
{
return new Border
{
Child = new ChartControl { DataContext = context.Data }
};
}
}
Register factories and pick one at runtime:
var widget = factories.First(f => f.CanHandle(config.Type)).Create(context);
panel.Children.Add(widget);
Factories can also emit data templates instead of controls. For virtualization, return a FuncDataTemplate that participates in recycling.
ItemsControl allows specifying the ItemsPanel with FuncTemplate<Panel?>. Build them from code to align virtualization mode with runtime options.
itemsControl.ItemsPanel = new FuncTemplate<Panel?>(() =>
new VirtualizingStackPanel
{
Orientation = Orientation.Vertical,
VirtualizationMode = ItemVirtualizationMode.Simple
});
FuncTemplate<T> lives in external/Avalonia/src/Avalonia.Controls/Templates/FuncTemplate.cs and returns a new panel per items presenter.
RecyclingElementFactoryAvalonia’s element factories provide direct control over virtualization (see external/Avalonia/src/Avalonia.Controls/Generators/). You can use RecyclingElementFactory and supply templates via IDataTemplate implementations defined in code.
var factory = new RecyclingElementFactory
{
RecycleKey = "Widget",
Template = new FuncDataTemplate<IWidgetViewModel>((item, _) => WidgetFactory.CreateControl(item))
};
var items = new ItemsRepeater { ItemTemplate = factory };
ItemsRepeater (in Avalonia.Controls) mirrors WinUI’s control. Providing a factory integrates with virtualization surfaces better than raw ItemsControl in performance-sensitive scenarios.
FuncDataTemplate.Build(item) to materialize the control tree in memory and assert shape/values.[Fact]
public void Order_item_template_renders_quantity()
{
var template = Templates.OrderItem;
var control = (Control)template.Build(new OrderItem { Quantity = 5 }, null)!;
control.GetVisualDescendants().OfType<TextBlock>().Should().Contain(t => t.Text == "5");
}
TemplateAppliedEventArgs.NameScope.Find in tests to guarantee required parts exist.FuncControlTemplate for a CardControl that registers named parts, uses template bindings for background/content, and applies to multiple instances with different content.IssueViewModel that render differently based on IsClosed. Swap templates dynamically by changing a property on the view-model.TreeView for file system data using FuncTreeDataTemplate, including icons and lazy loading. Ensure child collections load on demand.IDataTemplate factories keyed by type names. Resolve templates at runtime and verify virtualization with an ItemsRepeater in a headless test.By mastering code-based templates, indexers, and factories, you gain full control over Avalonia’s presentation layer without depending on XAML. Combine these techniques with the binding and layout patterns from earlier chapters to build highly dynamic, testable UI modules in pure C#.
What's next