Browse Source

Merge branch 'master' into fixes/project-status-wording

pull/2670/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
bf10c6ba65
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  2. 23
      src/Avalonia.Controls/Button.cs
  3. 2
      src/Avalonia.Controls/ComboBox.cs
  4. 36
      src/Avalonia.Controls/MenuItem.cs
  5. 1
      src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs
  6. 8
      src/Avalonia.Input/FocusManager.cs
  7. 6
      src/Avalonia.Input/IInputElement.cs
  8. 83
      src/Avalonia.Input/InputElement.cs
  9. 2
      src/Avalonia.Input/InputExtensions.cs
  10. 4
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  11. 44
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  12. 107
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  13. 101
      tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs

3
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using ReactiveUI; using ReactiveUI;
@ -11,7 +12,7 @@ namespace ControlCatalog.ViewModels
public MenuPageViewModel() public MenuPageViewModel()
{ {
OpenCommand = ReactiveCommand.CreateFromTask(Open); OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save); SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false));
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent); OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
MenuItems = new[] MenuItems = new[]

23
src/Avalonia.Controls/Button.cs

@ -33,8 +33,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Button : ContentControl public class Button : ContentControl
{ {
private ICommand _command;
/// <summary> /// <summary>
/// Defines the <see cref="ClickMode"/> property. /// Defines the <see cref="ClickMode"/> property.
/// </summary> /// </summary>
@ -75,6 +73,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsPressedProperty = public static readonly StyledProperty<bool> IsPressedProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsPressed)); AvaloniaProperty.Register<Button, bool>(nameof(IsPressed));
private ICommand _command;
private bool _commandCanExecute = true;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Button"/> class. /// Initializes static members of the <see cref="Button"/> class.
/// </summary> /// </summary>
@ -147,6 +148,8 @@ namespace Avalonia.Controls
private set { SetValue(IsPressedProperty, value); } private set { SetValue(IsPressedProperty, value); }
} }
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
@ -292,7 +295,11 @@ namespace Avalonia.Controls
{ {
if (status?.ErrorType == BindingErrorType.Error) if (status?.ErrorType == BindingErrorType.Error)
{ {
IsEnabled = false; if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
} }
} }
} }
@ -351,9 +358,13 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e) private void CanExecuteChanged(object sender, EventArgs e)
{ {
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to var canExecute = Command == null || Command.CanExecute(CommandParameter);
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter); if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/ComboBox.cs

@ -302,7 +302,7 @@ namespace Avalonia.Controls
} }
} }
private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible;
private void UpdateSelectionBoxItem(object item) private void UpdateSelectionBoxItem(object item)
{ {

36
src/Avalonia.Controls/MenuItem.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -20,8 +21,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
{ {
private ICommand _command;
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
@ -91,9 +90,8 @@ namespace Avalonia.Controls
private static readonly ITemplate<IPanel> DefaultPanel = private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel()); new FuncTemplate<IPanel>(() => new StackPanel());
/// <summary> private ICommand _command;
/// The submenu popup. private bool _commandCanExecute = true;
/// </summary>
private Popup _popup; private Popup _popup;
/// <summary> /// <summary>
@ -231,6 +229,8 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement; IMenuElement IMenuItem.Parent => Parent as IMenuElement;
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/> /// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
@ -394,6 +394,22 @@ namespace Avalonia.Controls
} }
} }
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
{
base.UpdateDataValidation(property, status);
if (property == CommandProperty)
{
if (status?.ErrorType == BindingErrorType.Error)
{
if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
}
}
}
/// <summary> /// <summary>
/// Closes all submenus of the menu item. /// Closes all submenus of the menu item.
/// </summary> /// </summary>
@ -437,9 +453,13 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e) private void CanExecuteChanged(object sender, EventArgs e)
{ {
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to var canExecute = Command == null || Command.CanExecute(CommandParameter);
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter); if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
} }
/// <summary> /// <summary>

1
src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs

@ -56,6 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
if (IsEnabled.GetValueOrDefault() && !_isRegistered) if (IsEnabled.GetValueOrDefault() && !_isRegistered)
{ {
// FIXME: This leaks event handlers.
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true);
_isRegistered = true; _isRegistered = true;
} }

8
src/Avalonia.Input/FocusManager.cs

@ -23,7 +23,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class. /// Initializes a new instance of the <see cref="FocusManager"/> class.
/// </summary> /// </summary>
public FocusManager() static FocusManager()
{ {
InputElement.PointerPressedEvent.AddClassHandler( InputElement.PointerPressedEvent.AddClassHandler(
typeof(IInputElement), typeof(IInputElement),
@ -146,7 +146,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible;
/// <summary> /// <summary>
/// Gets the focus scope ancestors of the specified control, traversing popups. /// Gets the focus scope ancestors of the specified control, traversing popups.
@ -174,7 +174,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="sender">The event sender.</param> /// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void OnPreviewPointerPressed(object sender, RoutedEventArgs e) private static void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
{ {
var ev = (PointerPressedEventArgs)e; var ev = (PointerPressedEventArgs)e;
@ -191,7 +191,7 @@ namespace Avalonia.Input
if (element != null) if (element != null)
{ {
Focus(element, NavigationMethod.Pointer, ev.InputModifiers); Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers);
} }
} }
} }

6
src/Avalonia.Input/IInputElement.cs

@ -83,14 +83,14 @@ namespace Avalonia.Input
Cursor Cursor { get; } Cursor Cursor { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the control is effectively enabled for user interaction. /// Gets a value indicating whether this control and all its parents are enabled.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual /// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual
/// controls. The <see cref="IsEnabledCore"/> property takes into account the /// controls. The <see cref="IsEffectivelyEnabled"/> property takes into account the
/// <see cref="IsEnabled"/> value of this control and its parent controls. /// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks> /// </remarks>
bool IsEnabledCore { get; } bool IsEffectivelyEnabled { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the control is focused. /// Gets a value indicating whether the control is focused.

83
src/Avalonia.Input/InputElement.cs

@ -28,10 +28,12 @@ namespace Avalonia.Input
AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabled), true); AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabled), true);
/// <summary> /// <summary>
/// Defines the <see cref="IsEnabledCore"/> property. /// Defines the <see cref="IsEffectivelyEnabled"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsEnabledCoreProperty = public static readonly DirectProperty<InputElement, bool> IsEffectivelyEnabledProperty =
AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabledCore), true); AvaloniaProperty.RegisterDirect<InputElement, bool>(
nameof(IsEffectivelyEnabled),
o => o.IsEffectivelyEnabled);
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
@ -155,6 +157,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent; public static readonly RoutedEvent<RoutedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent;
private bool _isEffectivelyEnabled = true;
private bool _isFocused; private bool _isFocused;
private bool _isPointerOver; private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers; private GestureRecognizerCollection _gestureRecognizers;
@ -179,7 +182,7 @@ namespace Avalonia.Input
PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost); PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged); PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
PseudoClass<InputElement, bool>(IsEnabledCoreProperty, x => !x, ":disabled"); PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled");
PseudoClass<InputElement>(IsFocusedProperty, ":focus"); PseudoClass<InputElement>(IsFocusedProperty, ":focus");
PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover"); PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover");
} }
@ -365,31 +368,25 @@ namespace Avalonia.Input
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); } internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
} }
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the control is effectively enabled for user interaction. public bool IsEffectivelyEnabled
/// </summary> {
/// <remarks> get => _isEffectivelyEnabled;
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
/// controls. The <see cref="IsEnabledCore"/> property takes into account the }
/// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks> public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
bool IInputElement.IsEnabledCore => IsEnabledCore;
/// <summary> /// <summary>
/// Gets a value indicating whether the control is effectively enabled for user interaction. /// Allows a derived class to override the enabled state of the control.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual /// Derived controls may wish to disable the enabled state of the control without overwriting the
/// controls. The <see cref="IsEnabledCore"/> property takes into account the /// user-supplied <see cref="IsEnabled"/> setting. This can be done by overriding this property
/// <see cref="IsEnabled"/> value of this control and its parent controls. /// to return the overridden enabled state. If the value returned from <see cref="IsEnabledCore"/>
/// should change, then the derived control should call <see cref="UpdateIsEffectivelyEnabled()"/>.
/// </remarks> /// </remarks>
protected bool IsEnabledCore protected virtual bool IsEnabledCore => IsEnabled;
{
get { return GetValue(IsEnabledCoreProperty); }
set { SetValue(IsEnabledCoreProperty, value); }
}
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
public GestureRecognizerCollection GestureRecognizers public GestureRecognizerCollection GestureRecognizers
=> _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
@ -417,7 +414,7 @@ namespace Avalonia.Input
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTreeCore(e); base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore(); UpdateIsEffectivelyEnabled();
} }
/// <summary> /// <summary>
@ -525,9 +522,18 @@ namespace Avalonia.Input
{ {
} }
/// <summary>
/// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent
/// control's enabled state and <see cref="IsEnabledCore"/>.
/// </summary>
protected void UpdateIsEffectivelyEnabled()
{
UpdateIsEffectivelyEnabled(this.GetVisualParent<InputElement>());
}
private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
{ {
((InputElement)e.Sender).UpdateIsEnabledCore(); ((InputElement)e.Sender).UpdateIsEffectivelyEnabled();
} }
/// <summary> /// <summary>
@ -551,32 +557,17 @@ namespace Avalonia.Input
} }
/// <summary> /// <summary>
/// Updates the <see cref="IsEnabledCore"/> property value. /// Updates the <see cref="IsEffectivelyEnabled"/> property based on the parent's
/// </summary> /// <see cref="IsEffectivelyEnabled"/>.
private void UpdateIsEnabledCore()
{
UpdateIsEnabledCore(this.GetVisualParent<InputElement>());
}
/// <summary>
/// Updates the <see cref="IsEnabledCore"/> property based on the parent's
/// <see cref="IsEnabledCore"/>.
/// </summary> /// </summary>
/// <param name="parent">The parent control.</param> /// <param name="parent">The parent control.</param>
private void UpdateIsEnabledCore(InputElement parent) private void UpdateIsEffectivelyEnabled(InputElement parent)
{ {
if (parent != null) IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true);
{
IsEnabledCore = IsEnabled && parent.IsEnabledCore;
}
else
{
IsEnabledCore = IsEnabled;
}
foreach (var child in this.GetVisualChildren().OfType<InputElement>()) foreach (var child in this.GetVisualChildren().OfType<InputElement>())
{ {
child.UpdateIsEnabledCore(this); child.UpdateIsEffectivelyEnabled(this);
} }
} }
} }

2
src/Avalonia.Input/InputExtensions.cs

@ -45,7 +45,7 @@ namespace Avalonia.Input
return element != null && return element != null &&
element.IsVisible && element.IsVisible &&
element.IsHitTestVisible && element.IsHitTestVisible &&
element.IsEnabledCore && element.IsEffectivelyEnabled &&
element.IsAttachedToVisualTree; element.IsAttachedToVisualTree;
} }
} }

4
src/Avalonia.Input/Navigation/FocusExtensions.cs

@ -13,13 +13,13 @@ namespace Avalonia.Input.Navigation
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible;
/// <summary> /// <summary>
/// Checks if descendants of the specified element can be focused. /// Checks if descendants of the specified element can be focused.
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if descendants of the element can be focused.</returns> /// <returns>True if descendants of the element can be focused.</returns>
public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible; public static bool CanFocusDescendants(this IInputElement e) => e.IsEffectivelyEnabled && e.IsVisible;
} }
} }

44
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -26,11 +26,26 @@ namespace Avalonia.Controls.UnitTests
}; };
var root = new TestRoot { Child = target }; var root = new TestRoot { Child = target };
Assert.False(target.IsEnabled); Assert.False(target.IsEffectivelyEnabled);
command.IsEnabled = true; command.IsEnabled = true;
Assert.True(target.IsEnabled); Assert.True(target.IsEffectivelyEnabled);
command.IsEnabled = false; command.IsEnabled = false;
Assert.False(target.IsEnabled); Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void Button_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False()
{
var command = new TestCommand(true);
var target = new Button
{
IsEnabled = false,
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(((IInputElement)target).IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -41,7 +56,8 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -59,8 +75,12 @@ namespace Avalonia.Controls.UnitTests
}; };
Assert.True(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.DataContext = null; target.DataContext = null;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -77,9 +97,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel; target.DataContext = viewModel;
Assert.True(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -96,9 +120,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel; target.DataContext = viewModel;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]

107
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -2,6 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -25,6 +27,103 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable); Assert.False(target.Focusable);
} }
[Fact]
public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False()
{
var command = new TestCommand(true);
var target = new MenuItem
{
IsEnabled = false,
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(((IInputElement)target).IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Bound_Command_Doesnt_Exist()
{
var target = new MenuItem
{
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Bound_Command_Is_Removed()
{
var viewModel = new
{
Command = new TestCommand(true),
};
var target = new MenuItem
{
DataContext = viewModel,
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.DataContext = null;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Enabled_When_Bound_Command_Is_Added()
{
var viewModel = new
{
Command = new TestCommand(true),
};
var target = new MenuItem
{
DataContext = new object(),
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Disabled_Bound_Command_Is_Added()
{
var viewModel = new
{
Command = new TestCommand(false),
};
var target = new MenuItem
{
DataContext = new object(),
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact] [Fact]
public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree() public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{ {
@ -60,8 +159,14 @@ namespace Avalonia.Controls.UnitTests
private class TestCommand : ICommand private class TestCommand : ICommand
{ {
private bool _enabled;
private EventHandler _canExecuteChanged; private EventHandler _canExecuteChanged;
public TestCommand(bool enabled = true)
{
_enabled = enabled;
}
public int SubscriptionCount { get; private set; } public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged public event EventHandler CanExecuteChanged
@ -70,7 +175,7 @@ namespace Avalonia.Controls.UnitTests
remove { _canExecuteChanged -= value; --SubscriptionCount; } remove { _canExecuteChanged -= value; --SubscriptionCount; }
} }
public bool CanExecute(object parameter) => true; public bool CanExecute(object parameter) => _enabled;
public void Execute(object parameter) public void Execute(object parameter)
{ {

101
tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs

@ -0,0 +1,101 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Xunit;
namespace Avalonia.Input.UnitTests
{
public class InputElement_Enabled
{
[Fact]
public void IsEffectivelyEnabled_Follows_IsEnabled()
{
var target = new Decorator();
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.IsEnabled = false;
Assert.False(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void IsEffectivelyEnabled_Follows_Ancestor_IsEnabled()
{
Decorator child;
Decorator grandchild;
var target = new Decorator
{
Child = child = new Decorator
{
Child = grandchild = new Decorator(),
}
};
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.True(child.IsEffectivelyEnabled);
Assert.True(grandchild.IsEnabled);
Assert.True(grandchild.IsEffectivelyEnabled);
target.IsEnabled = false;
Assert.False(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.False(child.IsEffectivelyEnabled);
Assert.True(grandchild.IsEnabled);
Assert.False(grandchild.IsEffectivelyEnabled);
}
[Fact]
public void Disabled_Pseudoclass_Follows_IsEffectivelyEnabled()
{
Decorator child;
var target = new Decorator
{
Child = child = new Decorator()
};
Assert.DoesNotContain(":disabled", child.Classes);
target.IsEnabled = false;
Assert.Contains(":disabled", child.Classes);
}
[Fact]
public void IsEffectivelyEnabled_Respects_IsEnabledCore()
{
Decorator child;
var target = new TestControl
{
Child = child = new Decorator()
};
target.ShouldEnable = false;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.False(child.IsEffectivelyEnabled);
}
private class TestControl : Decorator
{
private bool _shouldEnable;
public bool ShouldEnable
{
get => _shouldEnable;
set { _shouldEnable = value; UpdateIsEffectivelyEnabled(); }
}
protected override bool IsEnabledCore => IsEnabled && _shouldEnable;
}
}
}
Loading…
Cancel
Save