Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f039289
Synchronize late behavior lifecycle
wieslawsoltes Jul 14, 2026
07041ed
Synchronize late TopLevel behavior lifecycle
wieslawsoltes Jul 14, 2026
222bf21
Attach behavior collections before lifecycle sync
wieslawsoltes Jul 14, 2026
2479520
Make lifecycle replay mutation safe
wieslawsoltes Jul 15, 2026
c72a9a7
Deduplicate loaded lifecycle notifications
wieslawsoltes Jul 15, 2026
234f302
Deduplicate tree attachment lifecycle callbacks
wieslawsoltes Jul 15, 2026
c4c3bcb
Deduplicate initialized lifecycle callbacks
wieslawsoltes Jul 15, 2026
186ef6a
Harden lifecycle replay against detach and unload
wieslawsoltes Jul 15, 2026
fad3547
Revalidate behavior snapshots during attach
wieslawsoltes Jul 15, 2026
e548b20
Defer lifecycle replay during collection attach
wieslawsoltes Jul 15, 2026
182c9df
Skip detached behaviors in lifecycle snapshots
wieslawsoltes Jul 15, 2026
0f36899
Replay late lifecycle events by phase
wieslawsoltes Jul 15, 2026
e39e8e5
Catch up queued lifecycle additions by phase
wieslawsoltes Jul 15, 2026
957f356
Catch up nested lifecycle additions by phase
wieslawsoltes Jul 15, 2026
41c7f88
Defer replay during lifecycle dispatch
wieslawsoltes Jul 15, 2026
909b507
Defer replay for pending host lifecycle events
wieslawsoltes Jul 15, 2026
ebc1c5b
Defer top-level replay through opened phases
wieslawsoltes Jul 15, 2026
cd54810
Merge origin/master and resolve conflicts
wieslawsoltes Aug 19, 2026
d5b6a91
Merge origin/master and resolve conflicts
wieslawsoltes Aug 19, 2026
557ad32
Merge remote-tracking branch 'origin/master' into HEAD
wieslawsoltes Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docfx/articles/interactivity/behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ In many cases, a behavior needs to perform actions not just when it is attached,

When you attach a behavior collection to a control, the `Interaction` class subscribes to relevant events on that control (like `AttachedToVisualTree`, `DetachedFromVisualTree`, `Loaded`, `Unloaded`, etc.). When these events fire, `Interaction` iterates through the behaviors in the collection and calls the corresponding lifecycle method on each behavior.

If a collection or an individual behavior is attached after the control is already initialized, rooted, or loaded, the current lifecycle state is synchronized immediately. In particular, `OnLoaded` runs once for that current loaded lifetime instead of waiting for the control to unload and load again.

* **`OnAttached`**: Called when the behavior is attached to an object. Use this method to subscribe to events or initialize the behavior.
* **`OnDetaching`**: Called when the behavior is detached from an object. Use this method to unsubscribe from events and clean up resources.

Expand Down
86 changes: 79 additions & 7 deletions src/Xaml.Behaviors.Interactivity/AvaloniaObject/Behavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace Avalonia.Xaml.Interactivity;
/// </summary>
public abstract class Behavior : AvaloniaObject, IBehavior, IBehaviorEventsHandler
{
private bool _isAttachedToLogicalTree;
private bool _isAttachedToVisualTree;
private bool _isInitializedNotified;
private bool _isLoaded;

/// <summary>
/// Identifies the <seealso cref="IsEnabled"/> avalonia property.
/// </summary>
Expand Down Expand Up @@ -64,6 +69,10 @@ public void Attach(AvaloniaObject? associatedObject)
public void Detach()
{
OnDetaching();
_isAttachedToLogicalTree = false;
_isAttachedToVisualTree = false;
_isInitializedNotified = false;
_isLoaded = false;
AssociatedObject = null;
}

Expand All @@ -87,19 +96,82 @@ protected virtual void OnDetaching()
{
}

void IBehaviorEventsHandler.AttachedToVisualTreeEventHandler() => OnAttachedToVisualTree();
void IBehaviorEventsHandler.AttachedToVisualTreeEventHandler()
{
if (_isAttachedToVisualTree)
{
return;
}

void IBehaviorEventsHandler.DetachedFromVisualTreeEventHandler() => OnDetachedFromVisualTree();
_isAttachedToVisualTree = true;
OnAttachedToVisualTree();
}

void IBehaviorEventsHandler.DetachedFromVisualTreeEventHandler()
{
if (!_isAttachedToVisualTree)
{
return;
}

void IBehaviorEventsHandler.AttachedToLogicalTreeEventHandler() => OnAttachedToLogicalTree();
_isAttachedToVisualTree = false;
OnDetachedFromVisualTree();
}

void IBehaviorEventsHandler.DetachedFromLogicalTreeEventHandler() => OnDetachedFromLogicalTree();
void IBehaviorEventsHandler.AttachedToLogicalTreeEventHandler()
{
if (_isAttachedToLogicalTree)
{
return;
}

void IBehaviorEventsHandler.LoadedEventHandler() => OnLoaded();
_isAttachedToLogicalTree = true;
OnAttachedToLogicalTree();
}

void IBehaviorEventsHandler.UnloadedEventHandler() => OnUnloaded();
void IBehaviorEventsHandler.DetachedFromLogicalTreeEventHandler()
{
if (!_isAttachedToLogicalTree)
{
return;
}

void IBehaviorEventsHandler.InitializedEventHandler() => OnInitializedEvent();
_isAttachedToLogicalTree = false;
OnDetachedFromLogicalTree();
}

void IBehaviorEventsHandler.LoadedEventHandler()
{
if (_isLoaded)
{
return;
}

_isLoaded = true;
OnLoaded();
}

void IBehaviorEventsHandler.UnloadedEventHandler()
{
if (!_isLoaded)
{
return;
}

_isLoaded = false;
OnUnloaded();
}

void IBehaviorEventsHandler.InitializedEventHandler()
{
if (_isInitializedNotified)
{
return;
}

_isInitializedNotified = true;
OnInitializedEvent();
}

void IBehaviorEventsHandler.DataContextChangedEventHandler() => OnDataContextChangedEvent();

Expand Down
Loading
Loading