diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 821aef775889..dc836a08716a 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -394,13 +394,23 @@ protected override void OnPointerPressed(PointerPressedEventArgs e)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
- IsPressed = true;
- e.Handled = true;
-
- if (ClickMode == ClickMode.Press)
+ if (_isFlyoutOpen && IsEffectivelyEnabled)
{
+ // When a flyout is open with OverlayDismissEventPassThrough enabled and the button is pressed,
+ // close the flyout, but do not transition to a pressed state
+ e.Handled = true;
OnClick();
}
+ else
+ {
+ IsPressed = true;
+ e.Handled = true;
+
+ if (ClickMode == ClickMode.Press)
+ {
+ OnClick();
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
index b570b2c4ff00..ec54533f4e14 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
@@ -629,10 +629,22 @@ private void TextBox_TextChanged()
private void DropDownButton_PointerPressed(object? sender, PointerPressedEventArgs e)
{
- _ignoreButtonClick = _isPopupClosing;
+ if (_isFlyoutOpen && (_dropDownButton?.IsEffectivelyEnabled == true) && e.GetCurrentPoint(_dropDownButton).Properties.IsLeftButtonPressed)
+ {
+ // When a flyout is open with OverlayDismissEventPassThrough enabled and the drop-down button
+ // is pressed, close the flyout
+ _ignoreButtonClick = true;
- _isPressed = true;
- UpdatePseudoClasses();
+ e.Handled = true;
+ TogglePopUp();
+ }
+ else
+ {
+ _ignoreButtonClick = _isPopupClosing;
+
+ _isPressed = true;
+ UpdatePseudoClasses();
+ }
}
private void DropDownButton_PointerReleased(object? sender, PointerReleasedEventArgs e)
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index f1cc84f7a4b1..dbdbf4b536e1 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -298,7 +298,18 @@ protected override void OnPointerPressed(PointerPressedEventArgs e)
return;
}
}
- PseudoClasses.Set(pcPressed, true);
+
+ if (IsDropDownOpen)
+ {
+ // When a drop-down is open with OverlayDismissEventPassThrough enabled and the control
+ // is pressed, close the drop-down
+ SetCurrentValue(IsDropDownOpenProperty, false);
+ e.Handled = true;
+ }
+ else
+ {
+ PseudoClasses.Set(pcPressed, true);
+ }
}
///
@@ -314,7 +325,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e)
e.Handled = true;
}
}
- else
+ else if (PseudoClasses.Contains(pcPressed))
{
SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
e.Handled = true;
@@ -375,11 +386,6 @@ private void PopupClosed(object? sender, EventArgs e)
{
_subscriptionsOnOpen.Clear();
- if (CanFocus(this))
- {
- Focus();
- }
-
DropDownClosed?.Invoke(this, EventArgs.Empty);
}
diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
index 99c9f065ad3a..b91543556a52 100644
--- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
@@ -41,6 +41,12 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
public static readonly StyledProperty ShowModeProperty =
AvaloniaProperty.Register(nameof(ShowMode));
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty OverlayDismissEventPassThroughProperty =
+ Popup.OverlayDismissEventPassThroughProperty.AddOwner();
+
///
/// Defines the property
///
@@ -115,6 +121,22 @@ public FlyoutShowMode ShowMode
set => SetValue(ShowModeProperty, value);
}
+ ///
+ /// Gets or sets a value indicating whether the event that closes the flyout is passed
+ /// through to the parent window.
+ ///
+ ///
+ /// Clicks outside the popup cause the popup to close. When
+ /// is set to false, these clicks will be
+ /// handled by the popup and not be registered by the parent window. When set to true,
+ /// the events will be passed through to the parent window.
+ ///
+ public bool OverlayDismissEventPassThrough
+ {
+ get => GetValue(OverlayDismissEventPassThroughProperty);
+ set => SetValue(OverlayDismissEventPassThroughProperty, value);
+ }
+
///
/// Gets or sets an element that should receive pointer input events even when underneath
/// the flyout's overlay.
@@ -247,6 +269,7 @@ protected virtual bool ShowAtCore(Control placementTarget, bool showAtPointer =
Popup.Child = CreatePresenter();
}
+ Popup.OverlayDismissEventPassThrough = OverlayDismissEventPassThrough;
Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement;
if (CancelOpening())
@@ -365,8 +388,6 @@ private Popup CreatePopup()
{
WindowManagerAddShadowHint = false,
IsLightDismissEnabled = true,
- //Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss.
- OverlayDismissEventPassThrough = false
};
popup.Opened += OnPopupOpened;
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index a2bf07db0e69..7285281d89cd 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -745,12 +745,16 @@ private void PointerPressedDismissOverlay(object? sender, PointerPressedEventArg
{
if (IsLightDismissEnabled && e.Source is Visual v && !IsChildOrThis(v))
{
- CloseCore();
-
if (OverlayDismissEventPassThrough)
{
PassThroughEvent(e);
}
+
+ // Ensure the popup is closed if it was not closed by a pass-through event handler
+ if (IsOpen)
+ {
+ CloseCore();
+ }
}
}
diff --git a/src/Avalonia.Controls/SplitButton/SplitButton.cs b/src/Avalonia.Controls/SplitButton/SplitButton.cs
index dc8dc56fcf40..c51c77bee2a1 100644
--- a/src/Avalonia.Controls/SplitButton/SplitButton.cs
+++ b/src/Avalonia.Controls/SplitButton/SplitButton.cs
@@ -226,6 +226,7 @@ private void UnregisterEvents()
if (_secondaryButton != null)
{
_secondaryButton.Click -= SecondaryButton_Click;
+ _secondaryButton.RemoveHandler(PointerPressedEvent, SecondaryButton_PreviewPointerPressed);
}
}
@@ -248,6 +249,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
if (_secondaryButton != null)
{
_secondaryButton.Click += SecondaryButton_Click;
+ _secondaryButton.AddHandler(PointerPressedEvent, SecondaryButton_PreviewPointerPressed, RoutingStrategies.Tunnel);
}
RegisterFlyoutEvents(Flyout);
@@ -422,7 +424,14 @@ protected virtual void OnClickSecondary(RoutedEventArgs? e)
// Note: It is not currently required to check enabled status; however, this is a failsafe
if (IsEffectivelyEnabled)
{
- OpenFlyout();
+ if (_isFlyoutOpen)
+ {
+ CloseFlyout();
+ }
+ else
+ {
+ OpenFlyout();
+ }
}
}
@@ -443,7 +452,7 @@ protected virtual void OnFlyoutClosed()
}
///
- /// Event handler for when the internal primary button part is pressed.
+ /// Event handler for when the internal primary button part is clicked.
///
private void PrimaryButton_Click(object? sender, RoutedEventArgs e)
{
@@ -453,7 +462,7 @@ private void PrimaryButton_Click(object? sender, RoutedEventArgs e)
}
///
- /// Event handler for when the internal secondary button part is pressed.
+ /// Event handler for when the internal secondary button part is clicked.
///
private void SecondaryButton_Click(object? sender, RoutedEventArgs e)
{
@@ -461,6 +470,23 @@ private void SecondaryButton_Click(object? sender, RoutedEventArgs e)
e.Handled = true;
OnClickSecondary(e);
}
+
+ ///
+ /// Event handler for when the internal secondary button part is pressed.
+ ///
+ private void SecondaryButton_PreviewPointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (_isFlyoutOpen && _secondaryButton?.IsEffectivelyEnabled == true)
+ {
+ if (e.GetCurrentPoint(_secondaryButton).Properties.IsLeftButtonPressed)
+ {
+ // When a flyout is open with OverlayDismissEventPassThrough enabled and the secondary button
+ // is pressed, close the flyout
+ e.Handled = true;
+ OnClickSecondary(e);
+ }
+ }
+ }
///
/// Called when the property changes.
diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
index 162e3a6f8e75..65243fa445e9 100644
--- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
@@ -53,7 +53,7 @@ public void Clicking_On_Control_PseudoClass()
Assert.True(target.Classes.Contains(ComboBox.pcDropdownOpen));
_helper.Down(target);
- Assert.True(target.Classes.Contains(ComboBox.pcPressed));
+ Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
_helper.Up(target);
Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
index e3f598327410..dad8c3b78e7b 100644
--- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
@@ -221,7 +221,91 @@ public void Light_Dismiss_Closes_Flyout()
Assert.False(f.IsOpen);
}
}
+
+ [Fact]
+ public void Light_Dismiss_No_Event_Pass_Through_To_Button()
+ {
+ using (CreateServicesWithFocus())
+ {
+ var window = PreparedWindow();
+ window.Width = 100;
+ window.Height = 100;
+
+ bool buttonClicked = false;
+ var button = new Button()
+ {
+ ClickMode = ClickMode.Press
+ };
+ button.Click += (s, e) =>
+ {
+ buttonClicked = true;
+ };
+ window.Content = button;
+
+ window.Show();
+
+ var f = new Flyout();
+ f.OverlayDismissEventPassThrough = false; // Focus of test
+ f.Content = new Border { Width = 10, Height = 10 };
+ f.ShowAt(window);
+
+ var hitTester = new Mock();
+ window.HitTesterOverride = hitTester.Object;
+ hitTester.Setup(x =>
+ x.HitTestFirst(new Point(90, 90), window, It.IsAny>()))
+ .Returns(button);
+
+ var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
+ var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
+ overlay.RaiseEvent(e);
+
+ Assert.False(f.IsOpen);
+ Assert.False(buttonClicked); // Button is NOT clicked
+ }
+ }
+ [Fact]
+ public void Light_Dismiss_Event_Pass_Through_To_Button()
+ {
+ using (CreateServicesWithFocus())
+ {
+ var window = PreparedWindow();
+ window.Width = 100;
+ window.Height = 100;
+
+ bool buttonClicked = false;
+ var button = new Button()
+ {
+ ClickMode = ClickMode.Press
+ };
+ button.Click += (s, e) =>
+ {
+ buttonClicked = true;
+ };
+ window.Content = button;
+
+ window.Show();
+
+ var f = new Flyout();
+ f.OverlayDismissEventPassThrough = true; // Focus of test
+ f.Content = new Border { Width = 10, Height = 10 };
+ f.ShowAt(window);
+
+ var hitTester = new Mock();
+ window.HitTesterOverride = hitTester.Object;
+ hitTester.Setup(x =>
+ x.HitTestFirst(new Point(90, 90), window, It.IsAny>()))
+ .Returns(button);
+
+ var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
+ var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
+ overlay.RaiseEvent(e);
+
+ Assert.False(f.IsOpen);
+ Assert.True(buttonClicked); // Button is clicked
+ }
+ }
+
[Fact]
public void Flyout_Has_Uncancellable_Close_Before_Showing_On_A_Different_Target()
{