Skip to main content

Lifecycle events

Avalonia controls raise several events during their creation, attachment to the visual tree, and removal. Understanding the order and purpose of these events is important for initializing controls, loading data, and cleaning up resources.

Lifecycle event order

Control creation

When a control is created and added to the visual tree, events fire in the following order:

OrderEvent / MethodDefined OnDescription
1InitializedStyledElementAll property values from XAML have been set. The control is not yet part of the visual tree.
2AttachedToVisualTreeVisualThe control has been added to a rooted visual tree. Layout has not yet occurred.
3LoadedControlThe control is fully attached and ready for interaction. This fires after the visual tree attachment is complete.

Control removal

When a control is removed:

OrderEvent / MethodDefined OnDescription
1UnloadedControlThe control is about to be removed from the visual tree.
2DetachedFromVisualTreeVisualThe control has been removed from the visual tree.

Layout application

In addition to the events described above, layout controls can also participate in altering the visual tree through the following methods.

During the initial run (i.e., when the control is first attached to the visual tree), these events occur in the stated sequence between the AttachedToVisualTree and Loaded events detailed above. However, MeasureOverride and ArrangeOverride can run multiple times during a control's lifetime, as they are triggered whenever the layout is updated, e.g., when the window size is adjusted.

OrderEvent / MethodDefined OnDescription
1ApplyTemplateControlApplies the control template and creates the required templated visual parts.
2MeasureOverrideControlCalled during the measure pass of layout. Determines the desired size of a control.
3ArrangeOverrideControlCalled during the arrange pass of layout. Assigns the final size of a control.

Initialized

The Initialized event fires when the XAML loader has finished setting all properties defined in markup. At this point, the control's property values are set but the control may not yet be part of a visual tree.

public class MyControl : Control
{
protected override void OnInitialized()
{
base.OnInitialized();
// Properties from XAML are set
// Visual tree may not be available yet
}
}

Or subscribe externally:

myControl.Initialized += (sender, e) =>
{
// Control is initialized
};

When to use: Set up internal state that depends on XAML-defined property values but does not require the visual tree.

AttachedToVisualTree / DetachedFromVisualTree

These events fire when a control is added to or removed from a rooted visual tree (a tree that has a TopLevel at its root).

public class MyControl : Control
{
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
// e.RootVisual is the root of the visual tree
// Start listening to external services, timers, etc.
}

protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
// Clean up external subscriptions, timers, etc.
}
}

The VisualTreeAttachmentEventArgs provides:

PropertyTypeDescription
RootVisualVisualThe root visual of the tree the control was attached to.
AttachmentPointVisualThe visual that the control was directly attached to or detached from.
PresentationSourceIPresentationSourceThe presentation source hosting the visual tree.

When to use: Subscribe to or unsubscribe from external services, platform APIs, or events that should only be active while the control is visible.

Loaded / Unloaded

The Loaded event fires after a control is attached to the visual tree and all related initialization is complete. The Unloaded event fires when the control is removed.

public class MyControl : Control
{
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
// Control is fully ready
// Layout has occurred, bindings are active
}

protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
// Clean up
}
}

Or subscribe via XAML/code:

myControl.Loaded += (sender, e) =>
{
// Control is loaded and ready
};

When to use: Perform actions that require the control to be fully set up with an active visual tree, such as starting animations, measuring layout, or fetching data.

Loaded vs AttachedToVisualTree

Both events indicate the control is part of the visual tree. The key difference:

  • AttachedToVisualTree fires immediately when the control enters the tree. It is a plain CLR event on Visual.
  • Loaded fires after the attachment is fully complete. It is a RoutedEvent on Control.

For most scenarios, Loaded is the right choice. Use AttachedToVisualTree when you need access to the Root reference or when working with non-Control visuals.

ApplyTemplate

This method applies the control template of a control.

public class MyControl : Control
{
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
}
}

MeasureOverride / ArrangeOverride

These override methods are called by the layout system whenever a control needs to undergo the two-pass layout process to size and position it within the layout.

public class MyControl : Control
{
protected override Size MeasureOverride(Size availableSize)
{
// Calculates the desired size
return base.MeasureOverride(availableSize);
}

protected override Size ArrangeOverride(Size finalSize)
{
// Allocates the final size
return base.ArrangeOverride(finalSize);
}
}

DataContextChanged

The DataContextChanged event fires whenever the DataContext property changes on a StyledElement:

myControl.DataContextChanged += (sender, e) =>
{
var newContext = ((Control)sender!).DataContext;
// React to the new data context
};

This event fires when:

  • The DataContext is set directly on the control.
  • The inherited DataContext changes because a parent's DataContext changed.
  • The control moves to a different part of the visual tree with a different inherited DataContext.
warning

Do not mutate the logical tree from DataContextChanged or OnPropertyChanged!

DataContext is an inherited property, so changes trigger a walk down the logical tree to propagate new values to descendants. If LogicalChildren are modified while the walk is in progress, binding errors can result.

For more information, see Mutating the logical tree.

Typical initialization patterns

Loading data in a view

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

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);

if (DataContext is CustomerViewModel vm)
{
vm.LoadCustomersCommand.Execute(null);
}
}
}

Managing subscriptions

public class StatusMonitor : Control
{
private IDisposable? _subscription;

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_subscription = StatusService.StatusChanged.Subscribe(OnStatusChanged);
}

protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_subscription?.Dispose();
_subscription = null;
base.OnDetachedFromVisualTree(e);
}

private void OnStatusChanged(string status)
{
// Update the control
}
}

See also