Description
A System.Windows.Controls.Calendar embedded directly and persistently in a window (i.e. not used as DatePicker's transient popup content) leaves Mouse.Captured pointing at its internal CalendarItem after every single date-selection click, even after that click's own routed-event processing has fully completed. Because the capture was taken with CaptureMode.SubTree, this causes the next mouse click anywhere in the window — even on a completely unrelated control — to be silently redirected into the Calendar's visual subtree instead of reaching its real target. The real target's click handler never fires. There is no exception, no visual feedback, and no indication anything went wrong; the target control (e.g. a Button) simply looks enabled and does nothing.
I found this in a WPF (.NET 10, net10.0-windows) desktop app that shows a Calendar (SelectionMode="SingleDate") permanently in the main window with an interactive Button positioned below it. In production, once triggered, the stuck state can persist across many subsequent click attempts before something resets it. In an isolated minimal repro (see below), the defect is milder but 100% deterministic: exactly the one click immediately following every date selection is swallowed, and processing that swallowed click is what incidentally releases the stuck capture, after which things work normally again — until the next date is selected.
I believe this has gone unreported because Calendar is almost never used this way in the wild; as DatePicker's popup content it is torn down (the Popup closes) on every selection, which happens to mask the leak before a user ever gets a chance to click something else inside that same Calendar instance.
Reproduction Steps
Minimal standalone repro (net10.0-windows, default WPF theme, dotnet new wpf):
MainWindow.xaml:
<Window x:Class="CalendarCaptureRepro.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CalendarCaptureRepro" Height="500" Width="420">
<StackPanel Margin="10">
<Calendar x:Name="Cal" SelectionMode="SingleDate" DisplayDate="2026-08-01" />
<Button x:Name="Btn" Content="Click Me" Height="60" Margin="0,10,0,0" Click="Btn_Click" />
<TextBlock x:Name="StatusText" Margin="0,10,0,0" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private int _clickCount = 0;
public MainWindow()
{
InitializeComponent();
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
_clickCount++;
StatusText.Text = $"Button clicked {_clickCount} time(s).";
}
}
Steps:
- Run the app.
- Click any date in the
Calendar (any date — no special date is required; leading/trailing days from an adjacent month are not necessary to trigger this, contrary to our own initial hypothesis).
- Click the
Button.
- Observe:
StatusText does not update; Btn_Click does not fire. The Button still renders enabled/normal.
- Click the
Button again — it now works normally, and continues to work normally until another date is selected in the Calendar.
I have confirmed this with real, OS-level synthesized mouse input (user32.dll SendInput, not UIAutomation.InvokePattern — the latter does not reproduce this, since it bypasses the native mouse-capture code path entirely) that step 2→3 fails 100% of the time, across dozens of trials, including with an instrumented build that logs Mouse.Captured at the tunnel (preview) phase, the bubble phase (handler added at the Window with handledEventsToo: true, so it fires after CalendarItem.OnMouseUp in the bubble), and again one dispatcher tick later at DispatcherPriority.Input. All three readings agree: capture is still non-null immediately after the date-selection click's own event processing has finished.
Representative captured log (semicolon-delimited timestamps ours; Captured= shows Mouse.Captured at each point):
PREVIEW-DOWN OriginalSource=Path Name=Blackout DataContext=2026-08-01 Captured=null
PREVIEW-UP OriginalSource=Path Name=Blackout DataContext=2026-08-01 Captured=CalendarItem Name=PART_CalendarItem
BUBBLE-UP(@Window) OriginalSource=Path Name=Blackout DataContext=2026-08-01 Captured=CalendarItem Name=PART_CalendarItem
POST-DISPATCH(Input prio) Captured=CalendarItem Name=PART_CalendarItem
--- next click (Button), swallowed: ---
PREVIEW-DOWN OriginalSource=CalendarItem Name=PART_CalendarItem Captured=CalendarItem Name=PART_CalendarItem
PREVIEW-UP OriginalSource=CalendarItem Name=PART_CalendarItem Captured=CalendarItem Name=PART_CalendarItem
BUBBLE-UP(@Window) OriginalSource=CalendarItem Name=PART_CalendarItem Captured=null
POST-DISPATCH(Input prio) Captured=null
--- click after that works normally: ---
PREVIEW-DOWN OriginalSource=TextBlock Captured=null
PREVIEW-UP OriginalSource=Button Name=Btn Captured=Button Name=Btn
*** BUTTON CLICK FIRED ***
Note the second block: the click that lands on the Button is completely re-targeted — OriginalSource for both its down and up is CalendarItem, not the Button or anything the user physically clicked — and it is this redirected click's processing that happens to finally clear Mouse.Captured.
Field-observed variant (from our production app): with a richer visual tree — the Calendar and an "Exclude/Include Selected Date" Button both live inside a GroupBox inside the main window — the same defect instead persists across many subsequent clicks rather than self-clearing on the very next one, matching the debugger trace below. I wasn't able to fully automate that more severe variant in the time available, but the mechanism and the capture object involved (CalendarItem, CaptureMode.SubTree) are identical, and the minimal repro above demonstrates the same underlying release failure with 100% reliability, so I'm confident it's the same defect surfacing with different self-healing timing depending on the surrounding layout complexity.
Expected behavior
Mouse.Captured should return to null (or to whatever legitimately claims it) by the time the MouseUp that ends a Calendar date-selection gesture has finished being processed. A click on an unrelated control elsewhere in the window should always reach that control.
Actual behavior
Mouse.Captured remains set to the internal CalendarItem (captured with CaptureMode.SubTree for drag-range-selection support) after the date-selection click's own event processing completes. Because of CaptureMode.SubTree's documented hit-testing fallback ("if the hit-tested point is outside the captured subtree, the captured element itself is used" — confirmed directly in MouseDevice.cs, see below), every subsequent click anywhere in the window is redirected into the Calendar's subtree instead of reaching its real target, with no exception and no visual feedback. In our minimal repro this swallows exactly one click per date selection; in a richer, real-world visual tree it can swallow many consecutive clicks until something (not yet identified) restores the release.
Regression?
Unknown / not tested against .NET Framework or earlier .NET Core releases. Given the relevant code (capture acquisition/release split between Cell_MouseLeftButtonDown/Cell_MouseLeftButtonUp and the generic OnMouseUp override) appears to be long-standing, this is likely not a recent regression, just a long-unnoticed defect specific to using Calendar outside of DatePicker's popup.
Known Workarounds
Force-release capture ourselves after every mouse-up inside the Calendar, deferred until after the Calendar's own click handling has had a chance to run:
calendar.PreviewMouseLeftButtonUp += (_, _) =>
Dispatcher.BeginInvoke(() =>
{
if (Mouse.Captured != null)
Mouse.Capture(null);
}, System.Windows.Threading.DispatcherPriority.Input);
This reliably fixes the symptom in my app. As a pointer for a real fix: CalendarItem's day-button click finalization in Cell_MouseLeftButtonUp (which runs FinishSelection/OnDayClick, and sets e.Handled = true) could unconditionally call ReleaseMouseCapture() itself, rather than relying solely on the separate, generic OnMouseUp override to do it later.
Impact
- Reach: any application that hosts
Calendar directly and persistently (not exclusively as DatePicker popup content) with any other interactive control reachable afterward in the same window.
- Intensity: total, silent input loss for at least one click, potentially many, with zero diagnostic signal (no exception, no visual state change, control still shows as enabled). This is very hard for an end user — or a developer without a debugger already attached — to self-diagnose, since the symptom ("clicking things stopped working") gives no hint that a
Calendar click several UI interactions earlier is the cause.
- Likely under-reported precisely because of how rarely
Calendar is used outside DatePicker's popup, where the popup's teardown on close appears to mask the leak (see root-cause analysis).
Configuration
- .NET SDK: 10.0.400 (also reproduced building with 11.0.100-preview.6.26359.118 present on the same machine; target framework in both cases
net10.0-windows)
- OS: Windows 11 Enterprise, 10.0.26200 (Build 26200), x64
- Reproduces with the default WPF theme in a bare
dotnet new wpf project (no Calendar/CalendarDayButton style overrides). Our production app additionally uses the WPF Fluent theme (ThemeMode="System"), but the minimal repro above confirms this is template/theme-independent — it is a pure input/capture-handling defect in CalendarItem's C# code, not a styling issue.
- Confirmed specific to real, OS-level mouse input.
UIAutomation.InvokePattern (programmatic Invoke()) does not reproduce it, since it does not go through Mouse.Capture/hit-testing at all.
Other information
Root-cause analysis (source references, dotnet/wpf main @ 1cfc37f708f91ff4556bd25af414546c446f3a16)
All line references are to src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs unless noted otherwise.
-
Capture is acquired in Cell_MouseLeftButtonDown, on the CalendarDayButton's MouseLeftButtonDown:
Mouse.Capture(this, CaptureMode.SubTree) — this is the owning CalendarItem, so the entire month-grid subtree is captured (needed to support drag-range selection gestures).
Full method: lines 635–732.
-
The only release path is the generic OnMouseUp override (i.e. the handler for Mouse.MouseUpEvent, not the button-specific MouseLeftButtonUpEvent):
lines 239–258:
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if (this.IsMouseCaptured)
{
this.ReleaseMouseCapture();
}
...
}
-
The day-button-specific click finalization — which actually performs the date selection, and which can trigger a synchronous DisplayDate/month change and a full repopulation of the (recycled, not re-created) CalendarDayButton grid — runs separately, in Cell_MouseLeftButtonUp, attached to the button-specific MouseLeftButtonUpEvent:
lines 784–809, calling FinishSelection (811–851) → Calendar.OnDayClick (src/.../Controls/Calendar.cs, lines 887–903), which can call MoveDisplayTo and ends with e.Handled = true.
-
Why the release is unreliable: OnMouseUp (generic) and Cell_MouseLeftButtonUp (specific) are two independently-invoked handlers for two related-but-distinct routed events (Mouse.MouseUpEvent vs. the button-specific MouseLeftButtonUpEvent, the latter "cracked"/re-raised from the former — see UIElement.CrackMouseButtonEventAndReRaiseEvent). Our instrumented repro shows that in practice, by the time the date-selection click's entire event pipeline has settled, capture has not been released — I wasn't not able to fully pin down the precise internal ordering/interaction responsible (this spans MouseDevice's raw-input promotion pipeline and UIElement's generic→specific event "cracking," both internal), but the effect is fully, deterministically reproducible as shown above. I'd be glad if a maintainer could explain the the exact sequencing; from the outside, the release logic living solely in the generic OnMouseUp override — decoupled from the specific handler that actually performs the click's real work and marks the event handled — looks like the structural cause.
-
Why the fallback amplifies this into "click swallowed anywhere in the window": confirmed directly in MouseDevice.cs, the CaptureMode.SubTree hit-test resolution (around lines 1558–1630) walks up from the physically-hit element looking for the captured element as an ancestor; if it never finds it (because the physical click landed outside the captured subtree entirely — e.g. on our Button), it falls back to treating the captured root itself as the moused-over/target element:
// If we missed the capture point, we didn't hit anything.
if (ieTest != mouseCapture)
{
mouseOver = _mouseCapture;
isPhysicallyOver = false;
...
}
This is presumably intentional, to keep a drag-range-selection gesture alive even if the mouse briefly leaves the Calendar's bounds — but it means any capture leak from Calendar doesn't just affect the Calendar; it blackholes input for the entire window until the leak clears.
-
Why DatePicker doesn't show this: I checked DatePicker.cs on main directly — it contains zero references to Mouse.Capture/ReleaseMouseCapture. There is no deliberate capture-release safety net there. I believe DatePicker simply never surfaces the defect structurally: its Calendar only exists inside a Popup that closes immediately after a SingleDate selection, and the resulting visual-tree teardown/IsVisible change is what incidentally clears the stale capture before the user ever gets to click anything else inside that same Calendar instance. A Calendar embedded directly and persistently in a window, as in our app, has no such teardown to hide behind.
Related/adjacent issues
I did not find any existing issue describing Mouse.Captured leaking from Calendar/CalendarItem itself, so I believe this is not a duplicate.
Description
A
System.Windows.Controls.Calendarembedded directly and persistently in a window (i.e. not used asDatePicker's transient popup content) leavesMouse.Capturedpointing at its internalCalendarItemafter every single date-selection click, even after that click's own routed-event processing has fully completed. Because the capture was taken withCaptureMode.SubTree, this causes the next mouse click anywhere in the window — even on a completely unrelated control — to be silently redirected into theCalendar's visual subtree instead of reaching its real target. The real target's click handler never fires. There is no exception, no visual feedback, and no indication anything went wrong; the target control (e.g. aButton) simply looks enabled and does nothing.I found this in a WPF (.NET 10,
net10.0-windows) desktop app that shows aCalendar(SelectionMode="SingleDate") permanently in the main window with an interactiveButtonpositioned below it. In production, once triggered, the stuck state can persist across many subsequent click attempts before something resets it. In an isolated minimal repro (see below), the defect is milder but 100% deterministic: exactly the one click immediately following every date selection is swallowed, and processing that swallowed click is what incidentally releases the stuck capture, after which things work normally again — until the next date is selected.I believe this has gone unreported because
Calendaris almost never used this way in the wild; asDatePicker's popup content it is torn down (thePopupcloses) on every selection, which happens to mask the leak before a user ever gets a chance to click something else inside that sameCalendarinstance.Reproduction Steps
Minimal standalone repro (
net10.0-windows, default WPF theme,dotnet new wpf):MainWindow.xaml:MainWindow.xaml.cs:Steps:
Calendar(any date — no special date is required; leading/trailing days from an adjacent month are not necessary to trigger this, contrary to our own initial hypothesis).Button.StatusTextdoes not update;Btn_Clickdoes not fire. TheButtonstill renders enabled/normal.Buttonagain — it now works normally, and continues to work normally until another date is selected in theCalendar.I have confirmed this with real, OS-level synthesized mouse input (
user32.dllSendInput, notUIAutomation.InvokePattern— the latter does not reproduce this, since it bypasses the native mouse-capture code path entirely) that step 2→3 fails 100% of the time, across dozens of trials, including with an instrumented build that logsMouse.Capturedat the tunnel (preview) phase, the bubble phase (handler added at theWindowwithhandledEventsToo: true, so it fires afterCalendarItem.OnMouseUpin the bubble), and again one dispatcher tick later atDispatcherPriority.Input. All three readings agree: capture is still non-null immediately after the date-selection click's own event processing has finished.Representative captured log (semicolon-delimited timestamps ours;
Captured=showsMouse.Capturedat each point):Note the second block: the click that lands on the
Buttonis completely re-targeted —OriginalSourcefor both its down and up isCalendarItem, not theButtonor anything the user physically clicked — and it is this redirected click's processing that happens to finally clearMouse.Captured.Field-observed variant (from our production app): with a richer visual tree — the
Calendarand an "Exclude/Include Selected Date"Buttonboth live inside aGroupBoxinside the main window — the same defect instead persists across many subsequent clicks rather than self-clearing on the very next one, matching the debugger trace below. I wasn't able to fully automate that more severe variant in the time available, but the mechanism and the capture object involved (CalendarItem,CaptureMode.SubTree) are identical, and the minimal repro above demonstrates the same underlying release failure with 100% reliability, so I'm confident it's the same defect surfacing with different self-healing timing depending on the surrounding layout complexity.Expected behavior
Mouse.Capturedshould return tonull(or to whatever legitimately claims it) by the time theMouseUpthat ends aCalendardate-selection gesture has finished being processed. A click on an unrelated control elsewhere in the window should always reach that control.Actual behavior
Mouse.Capturedremains set to the internalCalendarItem(captured withCaptureMode.SubTreefor drag-range-selection support) after the date-selection click's own event processing completes. Because ofCaptureMode.SubTree's documented hit-testing fallback ("if the hit-tested point is outside the captured subtree, the captured element itself is used" — confirmed directly inMouseDevice.cs, see below), every subsequent click anywhere in the window is redirected into theCalendar's subtree instead of reaching its real target, with no exception and no visual feedback. In our minimal repro this swallows exactly one click per date selection; in a richer, real-world visual tree it can swallow many consecutive clicks until something (not yet identified) restores the release.Regression?
Unknown / not tested against .NET Framework or earlier .NET Core releases. Given the relevant code (capture acquisition/release split between
Cell_MouseLeftButtonDown/Cell_MouseLeftButtonUpand the genericOnMouseUpoverride) appears to be long-standing, this is likely not a recent regression, just a long-unnoticed defect specific to usingCalendaroutside ofDatePicker's popup.Known Workarounds
Force-release capture ourselves after every mouse-up inside the
Calendar, deferred until after theCalendar's own click handling has had a chance to run:This reliably fixes the symptom in my app. As a pointer for a real fix:
CalendarItem's day-button click finalization inCell_MouseLeftButtonUp(which runsFinishSelection/OnDayClick, and setse.Handled = true) could unconditionally callReleaseMouseCapture()itself, rather than relying solely on the separate, genericOnMouseUpoverride to do it later.Impact
Calendardirectly and persistently (not exclusively asDatePickerpopup content) with any other interactive control reachable afterward in the same window.Calendarclick several UI interactions earlier is the cause.Calendaris used outsideDatePicker's popup, where the popup's teardown on close appears to mask the leak (see root-cause analysis).Configuration
net10.0-windows)dotnet new wpfproject (noCalendar/CalendarDayButtonstyle overrides). Our production app additionally uses the WPF Fluent theme (ThemeMode="System"), but the minimal repro above confirms this is template/theme-independent — it is a pure input/capture-handling defect inCalendarItem's C# code, not a styling issue.UIAutomation.InvokePattern(programmaticInvoke()) does not reproduce it, since it does not go throughMouse.Capture/hit-testing at all.Other information
Root-cause analysis (source references,
dotnet/wpfmain@1cfc37f708f91ff4556bd25af414546c446f3a16)All line references are to
src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.csunless noted otherwise.Capture is acquired in
Cell_MouseLeftButtonDown, on theCalendarDayButton'sMouseLeftButtonDown:Mouse.Capture(this, CaptureMode.SubTree)—thisis the owningCalendarItem, so the entire month-grid subtree is captured (needed to support drag-range selection gestures).Full method: lines 635–732.
The only release path is the generic
OnMouseUpoverride (i.e. the handler forMouse.MouseUpEvent, not the button-specificMouseLeftButtonUpEvent):lines 239–258:
The day-button-specific click finalization — which actually performs the date selection, and which can trigger a synchronous
DisplayDate/month change and a full repopulation of the (recycled, not re-created)CalendarDayButtongrid — runs separately, inCell_MouseLeftButtonUp, attached to the button-specificMouseLeftButtonUpEvent:lines 784–809, calling
FinishSelection(811–851) →Calendar.OnDayClick(src/.../Controls/Calendar.cs, lines 887–903), which can callMoveDisplayToand ends withe.Handled = true.Why the release is unreliable:
OnMouseUp(generic) andCell_MouseLeftButtonUp(specific) are two independently-invoked handlers for two related-but-distinct routed events (Mouse.MouseUpEventvs. the button-specificMouseLeftButtonUpEvent, the latter "cracked"/re-raised from the former — seeUIElement.CrackMouseButtonEventAndReRaiseEvent). Our instrumented repro shows that in practice, by the time the date-selection click's entire event pipeline has settled, capture has not been released — I wasn't not able to fully pin down the precise internal ordering/interaction responsible (this spansMouseDevice's raw-input promotion pipeline andUIElement's generic→specific event "cracking," both internal), but the effect is fully, deterministically reproducible as shown above. I'd be glad if a maintainer could explain the the exact sequencing; from the outside, the release logic living solely in the genericOnMouseUpoverride — decoupled from the specific handler that actually performs the click's real work and marks the event handled — looks like the structural cause.Why the fallback amplifies this into "click swallowed anywhere in the window": confirmed directly in
MouseDevice.cs, theCaptureMode.SubTreehit-test resolution (around lines 1558–1630) walks up from the physically-hit element looking for the captured element as an ancestor; if it never finds it (because the physical click landed outside the captured subtree entirely — e.g. on ourButton), it falls back to treating the captured root itself as the moused-over/target element:This is presumably intentional, to keep a drag-range-selection gesture alive even if the mouse briefly leaves the
Calendar's bounds — but it means any capture leak fromCalendardoesn't just affect theCalendar; it blackholes input for the entire window until the leak clears.Why
DatePickerdoesn't show this: I checkedDatePicker.csonmaindirectly — it contains zero references toMouse.Capture/ReleaseMouseCapture. There is no deliberate capture-release safety net there. I believeDatePickersimply never surfaces the defect structurally: itsCalendaronly exists inside aPopupthat closes immediately after aSingleDateselection, and the resulting visual-tree teardown/IsVisiblechange is what incidentally clears the stale capture before the user ever gets to click anything else inside that sameCalendarinstance. ACalendarembedded directly and persistently in a window, as in our app, has no such teardown to hide behind.Related/adjacent issues
ElementNotAvailableExceptionthrown when clicking navigation button in WPF Calendar control on .NET 10 (open). Different symptom (an automation-peer exception duringKeyboardDevice.Focus), but the stack trace goes through the exact same synchronous-mutation-during-click-handling path I identified:Calendar.OnNextClick→MoveDisplayTo→CalendarItem.FocusDate→MoveFocus, all happening inside theButton.OnClick/routed-event dispatch for the click that triggered the month change. That issue's own repro notes explicitly that "automatic page-turning triggered by clicking a date outside the current month" is part of the trigger condition. I believe both issues point at the same underlying architectural fragility:Calendar'sMoveDisplayTo/UpdateMonths/FocusDatechain does non-trivial, synchronous visual-tree and focus mutation while still inside the routed-event dispatch of the click that caused it, and this collides with other parts of WPF's input/automation/capture bookkeeping that assume a more stable tree during dispatch.StaysOpen="false"Popupclosing unexpectedly when aDatePicker'sCalendarpopup is larger than its hostPopup), but also rooted inCalendar's click-hit-testing interacting awkwardly with capture/focus outside its own bounds.I did not find any existing issue describing
Mouse.Capturedleaking fromCalendar/CalendarItemitself, so I believe this is not a duplicate.