This document describes the currently implemented item and template system in MewUI.
Templates are used to convert items into reusable FrameworkElement instances. The core flow is:
- Build a view once for a container.
- Bind data into the view when it is realized.
- Reset tracked resources when the view is recycled.
This is the mechanism used by item controls such as ListBox, ComboBox, TreeView, and GridView.
MewUI item controls are driven by an ItemsView abstraction. In practice:
Items(...)creates or wraps anItemsView.- The control asks
ItemsViewfor item count, text, and selection. - Templates create and bind views for visible items.
ItemsView is the data side, and templates are the view side. They are designed to be used together.
IDataTemplate defines the contract for building and binding views.
public interface IDataTemplate
{
FrameworkElement Build(TemplateContext context);
void Bind(FrameworkElement view, object? item, int index, TemplateContext context);
}IDataTemplate<TItem> provides type-safe binding.
public interface IDataTemplate<in TItem> : IDataTemplate
{
void Bind(FrameworkElement view, TItem item, int index, TemplateContext context);
}DelegateTemplate<TItem> is the standard implementation. It is also where the default-template behavior is best understood: a simple build creates a TextBlock, and bind assigns text (from GetText or ToString()), using TemplateContext for fast lookup when needed.
var template = new DelegateTemplate<Person>(
build: ctx =>
{
// Default-template shape: a single TextBlock.
// Use TemplateContext when you want named access and reuse.
return new TextBlock().Register(ctx, "Text");
},
bind: (view, item, index, ctx) =>
{
ctx.Get<TextBlock>("Text").Text = item.Name;
});You can also return the view directly when you do not need TemplateContext:
var template = new DelegateTemplate<Person>(
build: _ => new TextBlock(),
bind: (view, item, index, _) => ((TextBlock)view).Text = item.Name);TemplateContext is used for:
- Registering named elements for fast lookup.
public sealed class TemplateContext : IDisposable
{
public void Register<T>(string name, T element) where T : UIElement;
public T Get<T>(string name) where T : UIElement;
// Internal lifecycle management (not part of the public contract).
}Common usage:
ctx.Get<TextBlock>("Name").Text = item.Name;Buildis called once when a container is created.Bindis called when a container is realized for an item.- Context cleanup is handled internally before each
Bindand during recycle.
This allows containers to be reused safely without leaking subscriptions or state.
TemplatedItemsHost is the internal helper used by item controls.
Responsibilities:
- Create containers using
IDataTemplate.Build. - Bind items using
IDataTemplate.Bind. - Reset
TemplateContextwhen reusing containers. - Delegate actual virtualization and layout to
VirtualizedItemsPresenter.
This is the common path used by ListBox, ComboBox, TreeView, and GridView.
new ListBox()
.Items(people, p => p.Name)
.ItemTemplate(template);If you do not set ItemTemplate, the default template is used (TextBlock + GetText/ToString()).
// Default template usage
new ListBox().Items(people, p => p.Name);The second argument of Items(...) is a text selector. It tells the default template
what string to display for each item (used by TextBlock).
new ComboBox()
.Items(people, p => p.Name)
.ItemTemplate(template);// Default template usage
new ComboBox().Items(people, p => p.Name);The second argument of Items(...) is a text selector used by the default template.
new TreeView()
.Items(treeItems)
.ItemTemplate(template);// Default template usage
new TreeView().Items(treeItems);You can also pass a hierarchical data source directly:
new TreeView().Items(
roots,
childrenSelector: n => n.Children,
textSelector: n => n.Name,
keySelector: n => n.Id);GridView columns use templates for cells.
var grid = new GridView();
grid.Columns(
new GridViewColumn<Person>()
.Header("Name")
.Width(160)
.Bind(
build: ctx => new TextBlock().Register(ctx, "Text"),
bind: (TextBlock t, Person p, int _, TemplateContext __) => t.Text = p.Name));If no template is provided, item controls behave like the example above: a TextBlock is created and populated using GetText or ToString().
This keeps the behavior consistent while allowing users to override with templates when needed.
- Use
TemplateContext.RegisterandGetfor named elements. - Use
TemplateContext.Trackto register subscriptions and unmanaged resources. - Avoid creating heavy objects during
Bind; reuse inBuild. - Always assume
Bindcan be called repeatedly on the same container.
When your template builds a single control and you do not need named lookup or tracked disposables, you can use overloads that ignore TemplateContext in the user code. The context is still created internally, but you do not need to use it.
// Build a single view and bind only the item (no context usage).
listBox.ItemTemplate(
build: _ => new TextBlock(),
bind: (TextBlock view, Person item) => view.Text = item.Name);This keeps the API simple for common cases while preserving the same template pipeline.