diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index bf177d64cd..9bc7ba9e2f 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -1630,7 +1630,7 @@ namespace Avalonia.Controls
///
/// The source object.
/// The event data.
- private void DropDownPopup_Closed(object sender, EventArgs e)
+ private void DropDownPopup_Closed(object sender, PopupClosedEventArgs e)
{
// Force the drop down dependency property to be false.
if (IsDropDownOpen)
@@ -1638,6 +1638,11 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
}
+ if (e.CloseEvent is PointerEventArgs pointerEvent)
+ {
+ pointerEvent.Handled = true;
+ }
+
// Fire the DropDownClosed event
if (_popupHasOpened)
{
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs
index 07e42c64e4..b4e4ad1452 100644
--- a/src/Avalonia.Controls/Calendar/DatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/DatePicker.cs
@@ -895,12 +895,17 @@ namespace Avalonia.Controls
_ignoreButtonClick = false;
}
}
- private void PopUp_Closed(object sender, EventArgs e)
+ private void PopUp_Closed(object sender, PopupClosedEventArgs e)
{
IsDropDownOpen = false;
if(!_isPopupClosing)
{
+ if (e.CloseEvent is PointerEventArgs pointerEvent)
+ {
+ pointerEvent.Handled = true;
+ }
+
_isPopupClosing = true;
Threading.Dispatcher.UIThread.InvokeAsync(() => _isPopupClosing = false);
}
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index 4b7d931d80..1daa6a5630 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -242,11 +242,16 @@ namespace Avalonia.Controls
}
}
- private void PopupClosed(object sender, EventArgs e)
+ private void PopupClosed(object sender, PopupClosedEventArgs e)
{
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
+ if (e.CloseEvent is PointerEventArgs pointerEvent)
+ {
+ pointerEvent.Handled = true;
+ }
+
if (CanFocus(this))
{
Focus();
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 932800868c..66f2153b6c 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -95,7 +95,7 @@ namespace Avalonia.Controls.Primitives
///
/// Raised when the popup closes.
///
- public event EventHandler? Closed;
+ public event EventHandler? Closed;
///
/// Raised when the popup opens.
@@ -270,7 +270,7 @@ namespace Avalonia.Controls.Primitives
if (parentPopupRoot?.Parent is Popup popup)
{
- DeferCleanup(SubscribeToEventHandler(popup, ParentClosed,
+ DeferCleanup(SubscribeToEventHandler>(popup, ParentClosed,
(x, handler) => x.Closed += handler,
(x, handler) => x.Closed -= handler));
}
@@ -306,28 +306,7 @@ namespace Avalonia.Controls.Primitives
///
/// Closes the popup.
///
- public void Close()
- {
- if (_openState is null)
- {
- using (BeginIgnoringIsOpen())
- {
- IsOpen = false;
- }
-
- return;
- }
-
- _openState.Dispose();
- _openState = null;
-
- using (BeginIgnoringIsOpen())
- {
- IsOpen = false;
- }
-
- Closed?.Invoke(this, EventArgs.Empty);
- }
+ public void Close() => CloseCore(null);
///
/// Measures the control.
@@ -389,21 +368,44 @@ namespace Avalonia.Controls.Primitives
}
}
+ private void CloseCore(EventArgs? closeEvent)
+ {
+ if (_openState is null)
+ {
+ using (BeginIgnoringIsOpen())
+ {
+ IsOpen = false;
+ }
+
+ return;
+ }
+
+ _openState.Dispose();
+ _openState = null;
+
+ using (BeginIgnoringIsOpen())
+ {
+ IsOpen = false;
+ }
+
+ Closed?.Invoke(this, new PopupClosedEventArgs(closeEvent));
+ }
+
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawPointerEventArgs;
if (!StaysOpen && mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
- Close();
+ CloseCore(e);
}
}
private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
{
- if (!StaysOpen && !IsChildOrThis((IVisual)e.Source))
+ if (!StaysOpen && e.Source is IVisual v && !IsChildOrThis(v))
{
- Close();
+ CloseCore(e);
}
}
diff --git a/src/Avalonia.Controls/Primitives/PopupClosedEventArgs.cs b/src/Avalonia.Controls/Primitives/PopupClosedEventArgs.cs
new file mode 100644
index 0000000000..c51543438c
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/PopupClosedEventArgs.cs
@@ -0,0 +1,33 @@
+using System;
+using Avalonia.Interactivity;
+
+#nullable enable
+
+namespace Avalonia.Controls.Primitives
+{
+ ///
+ /// Holds data for the event.
+ ///
+ public class PopupClosedEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public PopupClosedEventArgs(EventArgs? closeEvent)
+ {
+ CloseEvent = closeEvent;
+ }
+
+ ///
+ /// Gets the event that closed the popup, if any.
+ ///
+ ///
+ /// If is false, then this property will hold details of the
+ /// interaction that caused the popup to close if the close was caused by e.g. a pointer press
+ /// outside the popup. It can be used to mark the event as handled if the event should not
+ /// be propagated.
+ ///
+ public EventArgs? CloseEvent { get; }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 4321d566df..a479317f3d 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -358,21 +358,42 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Open();
- var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
- var e = new PointerPressedEventArgs(
- window,
- pointer,
- window,
- default,
- 0,
- new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
- KeyModifiers.None);
+ var e = CreatePointerPressedEventArgs(window);
window.RaiseEvent(e);
Assert.False(e.Handled);
}
}
+ [Fact]
+ public void Should_Pass_Closing_Click_To_Closed_Event()
+ {
+ using (CreateServices())
+ {
+ var window = PreparedWindow();
+ var target = new Popup()
+ {
+ PlacementTarget = window,
+ StaysOpen = false,
+ };
+
+ target.Open();
+
+ var press = CreatePointerPressedEventArgs(window);
+ var raised = 0;
+
+ target.Closed += (s, e) =>
+ {
+ Assert.Same(press, e.CloseEvent);
+ ++raised;
+ };
+
+ window.RaiseEvent(press);
+
+ Assert.Equal(1, raised);
+ }
+ }
+
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
@@ -385,6 +406,19 @@ namespace Avalonia.Controls.UnitTests.Primitives
})));
}
+ private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source)
+ {
+ var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
+ return new PointerPressedEventArgs(
+ source,
+ pointer,
+ source,
+ default,
+ 0,
+ new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
+ KeyModifiers.None);
+ }
+
private Window PreparedWindow(object content = null)
{
var w = new Window { Content = content };