diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 5ed0abf25d..cc9e6b7444 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -7,6 +7,7 @@ using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@@ -160,6 +161,40 @@ namespace Avalonia.Controls
}
}
+ ///
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+
+ if (IsDefault)
+ {
+ if (e.Root is IInputElement inputElement)
+ {
+ StopListeningForDefault(inputElement);
+ }
+ }
+ }
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+
+ if (Command != null)
+ {
+ Command.CanExecuteChanged += CanExecuteChanged;
+ }
+ }
+
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromLogicalTree(e);
+
+ if (Command != null)
+ {
+ Command.CanExecuteChanged -= CanExecuteChanged;
+ }
+ }
+
///
protected override void OnKeyDown(KeyEventArgs e)
{
@@ -195,20 +230,6 @@ namespace Avalonia.Controls
}
}
- ///
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromVisualTree(e);
-
- if (IsDefault)
- {
- if (e.Root is IInputElement inputElement)
- {
- StopListeningForDefault(inputElement);
- }
- }
- }
-
///
/// Invokes the event.
///
@@ -281,17 +302,17 @@ namespace Avalonia.Controls
{
if (e.Sender is Button button)
{
- var oldCommand = e.OldValue as ICommand;
- var newCommand = e.NewValue as ICommand;
-
- if (oldCommand != null)
- {
- oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
- }
-
- if (newCommand != null)
+ if (((ILogical)button).IsAttachedToLogicalTree)
{
- newCommand.CanExecuteChanged += button.CanExecuteChanged;
+ if (e.OldValue is ICommand oldCommand)
+ {
+ oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
+ }
+
+ if (e.NewValue is ICommand newCommand)
+ {
+ newCommand.CanExecuteChanged += button.CanExecuteChanged;
+ }
}
button.CanExecuteChanged(button, EventArgs.Empty);
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 64daa133a3..d8473dc613 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -286,6 +286,26 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+
+ if (Command != null)
+ {
+ Command.CanExecuteChanged += CanExecuteChanged;
+ }
+ }
+
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromLogicalTree(e);
+
+ if (Command != null)
+ {
+ Command.CanExecuteChanged -= CanExecuteChanged;
+ }
+ }
+
///
/// Called when the is clicked.
///
@@ -399,14 +419,17 @@ namespace Avalonia.Controls
{
if (e.Sender is MenuItem menuItem)
{
- if (e.OldValue is ICommand oldCommand)
+ if (((ILogical)menuItem).IsAttachedToLogicalTree)
{
- oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
- }
+ if (e.OldValue is ICommand oldCommand)
+ {
+ oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
+ }
- if (e.NewValue is ICommand newCommand)
- {
- newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
+ if (e.NewValue is ICommand newCommand)
+ {
+ newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
+ }
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 91b37596b7..9a751d4953 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -5,6 +5,7 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@@ -21,6 +22,7 @@ namespace Avalonia.Controls.UnitTests
{
Command = command,
};
+ var root = new TestRoot { Child = target };
Assert.False(target.IsEnabled);
command.IsEnabled = true;
@@ -215,6 +217,39 @@ namespace Avalonia.Controls.UnitTests
Assert.True(clicked);
}
+ [Fact]
+ public void Button_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
+ {
+ var command = new TestCommand(true);
+ var target = new Button
+ {
+ Command = command,
+ };
+
+ Assert.Equal(0, command.SubscriptionCount);
+ }
+
+ [Fact]
+ public void Button_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
+ {
+ var command = new TestCommand(true);
+ var target = new Button { Command = command };
+ var root = new TestRoot { Child = target };
+
+ Assert.Equal(1, command.SubscriptionCount);
+ }
+
+ [Fact]
+ public void Button_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
+ {
+ var command = new TestCommand(true);
+ var target = new Button { Command = command };
+ var root = new TestRoot { Child = target };
+
+ root.Child = null;
+ Assert.Equal(0, command.SubscriptionCount);
+ }
+
private class TestButton : Button, IRenderRoot
{
public TestButton()
@@ -298,6 +333,7 @@ namespace Avalonia.Controls.UnitTests
private class TestCommand : ICommand
{
+ private EventHandler _canExecuteChanged;
private bool _enabled;
public TestCommand(bool enabled)
@@ -313,12 +349,18 @@ namespace Avalonia.Controls.UnitTests
if (_enabled != value)
{
_enabled = value;
- CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+ _canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
- public event EventHandler CanExecuteChanged;
+ public int SubscriptionCount { get; private set; }
+
+ public event EventHandler CanExecuteChanged
+ {
+ add { _canExecuteChanged += value; ++SubscriptionCount; }
+ remove { _canExecuteChanged -= value; --SubscriptionCount; }
+ }
public bool CanExecute(object parameter) => _enabled;
diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
index e7352af23e..32d154249c 100644
--- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
+using System.Windows.Input;
+using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -22,5 +24,57 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable);
}
+
+ [Fact]
+ public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
+ {
+ var command = new TestCommand();
+ var target = new MenuItem
+ {
+ Command = command,
+ };
+
+ Assert.Equal(0, command.SubscriptionCount);
+ }
+
+ [Fact]
+ public void MenuItem_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
+ {
+ var command = new TestCommand();
+ var target = new MenuItem { Command = command };
+ var root = new TestRoot { Child = target };
+
+ Assert.Equal(1, command.SubscriptionCount);
+ }
+
+ [Fact]
+ public void MenuItem_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
+ {
+ var command = new TestCommand();
+ var target = new MenuItem { Command = command };
+ var root = new TestRoot { Child = target };
+
+ root.Child = null;
+ Assert.Equal(0, command.SubscriptionCount);
+ }
+
+ private class TestCommand : ICommand
+ {
+ private EventHandler _canExecuteChanged;
+
+ public int SubscriptionCount { get; private set; }
+
+ public event EventHandler CanExecuteChanged
+ {
+ add { _canExecuteChanged += value; ++SubscriptionCount; }
+ remove { _canExecuteChanged -= value; --SubscriptionCount; }
+ }
+
+ public bool CanExecute(object parameter) => true;
+
+ public void Execute(object parameter)
+ {
+ }
+ }
}
}