Browse Source

Fix CalendarDatePicker becoming unusable when closed programmatically (#20756)

* Fix CalendarDatePicker becoming unusable when closed programmatically

* Add unit test for CalendarDatePicker
pull/20776/head
Evan 4 weeks ago
committed by GitHub
parent
commit
7c2f1869bc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 13
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  2. 75
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs

13
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -299,6 +299,19 @@ namespace Avalonia.Controls.Primitives
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
// Reset mouse button tracking state. When the calendar popup closes
// (e.g. due to a programmatic window change during date selection),
// the PointerReleased event never fires, leaving these flags stuck.
// See https://github.com/AvaloniaUI/Avalonia/issues/18418
_isMouseLeftButtonDown = false;
_isMouseLeftButtonDownYearView = false;
}
private void SetDayTitles()
{
for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)

75
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@ -1,8 +1,9 @@
using Xunit;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.UnitTests;
namespace Avalonia.Controls.UnitTests
@ -367,5 +368,77 @@ namespace Avalonia.Controls.UnitTests
Assert.True(calendar.SelectedDates.Contains(earlierDate));
Assert.True(calendar.SelectedDates.Contains(laterDate));
}
[Fact]
public void CalendarItem_Should_Reset_Mouse_Down_Flag_On_Detach_From_Visual_Tree()
{
var calendar = new Calendar();
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
var calendarItem = new CalendarItem();
calendarItem.Owner = calendar;
// Attach CalendarItem to a visual tree
var root = new TestRoot(calendarItem);
// Create a day button and simulate mouse left button down,
// which sets the internal _isMouseLeftButtonDown flag to true.
var date1 = new DateTime(2024, 1, 15);
var dayButton1 = new CalendarDayButton { DataContext = date1 };
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
var props = new PointerPointProperties(RawInputModifiers.LeftMouseButton,
PointerUpdateKind.LeftButtonPressed);
var pressArgs = new PointerPressedEventArgs(dayButton1, pointer, root,
default, 0, props, KeyModifiers.None);
calendarItem.Cell_MouseLeftButtonDown(dayButton1, pressArgs);
// date1 should now be selected
Assert.Equal(1, calendar.SelectedDates.Count);
Assert.Equal(date1, calendar.SelectedDates[0]);
// Detach CalendarItem from visual tree (simulates popup closing
// during date selection without a PointerReleased event).
root.Child = null;
// Create a different day button and simulate mouse enter.
// Before the fix, _isMouseLeftButtonDown would still be true,
// causing hover to auto-select dates.
var date2 = new DateTime(2024, 1, 20);
var dayButton2 = new CalendarDayButton { DataContext = date2 };
calendarItem.Cell_MouseEntered(dayButton2, null!);
// The selected date should NOT have changed to date2,
// because the mouse-down flag was reset when detaching.
Assert.Equal(1, calendar.SelectedDates.Count);
Assert.Equal(date1, calendar.SelectedDates[0]);
}
[Fact]
public void CalendarItem_Should_Reset_YearView_Mouse_Down_Flag_On_Detach_From_Visual_Tree()
{
var calendar = new Calendar();
var calendarItem = new CalendarItem();
calendarItem.Owner = calendar;
// Attach CalendarItem to a visual tree
var root = new TestRoot(calendarItem);
// Use reflection to set the _isMouseLeftButtonDownYearView flag,
// since Month_CalendarButtonMouseDown is private.
var field = typeof(CalendarItem).GetField("_isMouseLeftButtonDownYearView",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.NotNull(field);
field!.SetValue(calendarItem, true);
Assert.True((bool)field.GetValue(calendarItem)!);
// Detach CalendarItem from visual tree
root.Child = null;
// Verify the flag was reset
Assert.False((bool)field.GetValue(calendarItem)!);
}
}
}

Loading…
Cancel
Save