diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 1a9347e317..f27bb5fac6 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
+using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications
@@ -14,7 +15,7 @@ namespace Avalonia.Controls.Notifications
///
/// An that displays notifications in a .
///
- public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
+ public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{
private IList _items;
@@ -153,5 +154,7 @@ namespace Avalonia.Controls.Notifications
adornerLayer?.Children.Add(this);
}
+
+ public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index ebe5e0a93e..9a2f0310d7 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -138,10 +138,7 @@ namespace Avalonia.Controls.Primitives
}
}
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
private class AdornedElementInfo
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 487a5e91e4..5150033a53 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -21,11 +21,8 @@ namespace Avalonia.Controls.Primitives
return null;
}
-
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
protected override Size ArrangeOverride(Size finalSize)
{
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index a5bbcec186..69da211aa4 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@@ -240,17 +241,14 @@ namespace Avalonia.Controls.Primitives
public override void BeginInit()
{
base.BeginInit();
- ++_updateCount;
- _updateSelectedIndex = int.MinValue;
+
+ InternalBeginInit();
}
///
public override void EndInit()
{
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
base.EndInit();
}
@@ -437,7 +435,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
- ++_updateCount;
+
+ InternalBeginInit();
}
///
@@ -445,10 +444,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDataContextEndUpdate();
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
}
protected override void OnKeyDown(KeyEventArgs e)
@@ -1118,6 +1114,26 @@ namespace Avalonia.Controls.Primitives
}
}
+ private void InternalBeginInit()
+ {
+ if (_updateCount == 0)
+ {
+ _updateSelectedIndex = int.MinValue;
+ }
+
+ ++_updateCount;
+ }
+
+ private void InternalEndInit()
+ {
+ Debug.Assert(_updateCount > 0);
+
+ if (--_updateCount == 0)
+ {
+ UpdateFinished();
+ }
+ }
+
private class Selection : IEnumerable
{
private readonly List _list = new List();
diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs
index e7cd0697a0..4b3e8e2110 100644
--- a/src/Avalonia.Controls/Primitives/ToggleButton.cs
+++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs
@@ -7,8 +7,14 @@ using Avalonia.Data;
namespace Avalonia.Controls.Primitives
{
+ ///
+ /// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
+ ///
public class ToggleButton : Button
{
+ ///
+ /// Defines the property.
+ ///
public static readonly DirectProperty IsCheckedProperty =
AvaloniaProperty.RegisterDirect(
nameof(IsChecked),
@@ -17,9 +23,30 @@ namespace Avalonia.Controls.Primitives
unsetValue: null,
defaultBindingMode: BindingMode.TwoWay);
+ ///
+ /// Defines the property.
+ ///
public static readonly StyledProperty IsThreeStateProperty =
AvaloniaProperty.Register(nameof(IsThreeState));
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent CheckedEvent =
+ RoutedEvent.Register(nameof(Checked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent UncheckedEvent =
+ RoutedEvent.Register(nameof(Unchecked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent IndeterminateEvent =
+ RoutedEvent.Register(nameof(Indeterminate), RoutingStrategies.Bubble);
+
private bool? _isChecked = false;
static ToggleButton()
@@ -27,14 +54,49 @@ namespace Avalonia.Controls.Primitives
PseudoClass(IsCheckedProperty, c => c == true, ":checked");
PseudoClass(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate");
+
+ IsCheckedProperty.Changed.AddClassHandler((x, e) => x.OnIsCheckedChanged(e));
+ }
+
+ ///
+ /// Raised when a is checked.
+ ///
+ public event EventHandler Checked
+ {
+ add => AddHandler(CheckedEvent, value);
+ remove => RemoveHandler(CheckedEvent, value);
+ }
+
+ ///
+ /// Raised when a is unchecked.
+ ///
+ public event EventHandler Unchecked
+ {
+ add => AddHandler(UncheckedEvent, value);
+ remove => RemoveHandler(UncheckedEvent, value);
+ }
+
+ ///
+ /// Raised when a is neither checked nor unchecked.
+ ///
+ public event EventHandler Indeterminate
+ {
+ add => AddHandler(IndeterminateEvent, value);
+ remove => RemoveHandler(IndeterminateEvent, value);
}
+ ///
+ /// Gets or sets whether the is checked.
+ ///
public bool? IsChecked
{
- get { return _isChecked; }
- set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
+ get => _isChecked;
+ set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
}
+ ///
+ /// Gets or sets a value that indicates whether the control supports three states.
+ ///
public bool IsThreeState
{
get => GetValue(IsThreeStateProperty);
@@ -47,18 +109,78 @@ namespace Avalonia.Controls.Primitives
base.OnClick();
}
+ ///
+ /// Toggles the property.
+ ///
protected virtual void Toggle()
{
if (IsChecked.HasValue)
+ {
if (IsChecked.Value)
+ {
if (IsThreeState)
+ {
IsChecked = null;
+ }
else
+ {
IsChecked = false;
+ }
+ }
else
+ {
IsChecked = true;
+ }
+ }
else
+ {
IsChecked = false;
+ }
+ }
+
+ ///
+ /// Called when becomes true.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnChecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes false.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnUnchecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes null.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnIndeterminate(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var newValue = (bool?)e.NewValue;
+
+ switch (newValue)
+ {
+ case true:
+ OnChecked(new RoutedEventArgs(CheckedEvent));
+ break;
+ case false:
+ OnUnchecked(new RoutedEventArgs(UncheckedEvent));
+ break;
+ default:
+ OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
+ break;
+ }
}
}
}
diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
index 7199053b08..4c15de0312 100644
--- a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
+++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
@@ -1,3 +1,7 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.VisualTree;
+
namespace Avalonia.Rendering
{
///
@@ -9,4 +13,13 @@ namespace Avalonia.Rendering
{
bool HitTest(Point point);
}
+
+ public static class CustomSimpleHitTestExtensions
+ {
+ public static bool HitTestCustom(this IVisual visual, Point point)
+ => (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true;
+
+ public static bool HitTestCustom(this IEnumerable children, Point point)
+ => children.Any(ctrl => ctrl.HitTestCustom(point));
+ }
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index e5bbebdec2..8c16dd0f70 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -129,6 +129,23 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(-1, target.SelectedIndex);
}
+ [Fact]
+ public void SelectedIndex_Should_Be_Minus_1_Without_Initialize()
+ {
+ var items = new[]
+ {
+ new Item(),
+ new Item(),
+ };
+
+ var target = new ListBox();
+ target.Items = items;
+ target.Template = Template();
+ target.DataContext = new object();
+
+ Assert.Equal(-1, target.SelectedIndex);
+ }
+
[Fact]
public void SelectedIndex_Should_Be_0_After_Initialize_With_AlwaysSelected()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
index 4f4ab47b0a..9acd42aba6 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
@@ -1,5 +1,4 @@
using Avalonia.Data;
-using Avalonia.Markup.Data;
using Avalonia.UnitTests;
using Xunit;
@@ -63,6 +62,54 @@ namespace Avalonia.Controls.Primitives.UnitTests
Assert.Null(threeStateButton.IsChecked);
}
+ [Fact]
+ public void ToggleButton_Events_Are_Raised_On_Is_Checked_Changes()
+ {
+ var threeStateButton = new ToggleButton();
+
+ bool checkedRaised = false;
+ threeStateButton.Checked += (_, __) => checkedRaised = true;
+
+ threeStateButton.IsChecked = true;
+ Assert.True(checkedRaised);
+
+ bool uncheckedRaised = false;
+ threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
+
+ threeStateButton.IsChecked = false;
+ Assert.True(uncheckedRaised);
+
+ bool indeterminateRaised = false;
+ threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
+
+ threeStateButton.IsChecked = null;
+ Assert.True(indeterminateRaised);
+ }
+
+ [Fact]
+ public void ToggleButton_Events_Are_Raised_When_Toggling()
+ {
+ var threeStateButton = new TestToggleButton { IsThreeState = true };
+
+ bool checkedRaised = false;
+ threeStateButton.Checked += (_, __) => checkedRaised = true;
+
+ threeStateButton.Toggle();
+ Assert.True(checkedRaised);
+
+ bool indeterminateRaised = false;
+ threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
+
+ threeStateButton.Toggle();
+ Assert.True(indeterminateRaised);
+
+ bool uncheckedRaised = false;
+ threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
+
+ threeStateButton.Toggle();
+ Assert.True(uncheckedRaised);
+ }
+
private class Class1 : NotifyingBase
{
private bool _foo;
@@ -80,5 +127,10 @@ namespace Avalonia.Controls.Primitives.UnitTests
set { nullableFoo = value; RaisePropertyChanged(); }
}
}
+
+ private class TestToggleButton : ToggleButton
+ {
+ public new void Toggle() => base.Toggle();
+ }
}
}