`Calendar` (outside `DatePicker`'s popup) leaks `Mouse.Capture` on every date selection, silently swallowing the next click anywhere in the window
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
### 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`:
```xml
```
`MainWindow.xaml.cs`:
```csharp
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:
1. Run the app.
2. 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).
3. Click the `Button`.
4. Observe: `StatusText` does not update; `Btn_Click` does not fire. The `Button` still renders enabled/normal.
5. 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:
```csharp
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)`](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs#L656) — `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](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs#L635-L732).
- **The only release path** is the *generic* `OnMouseUp` override (i.e. the handler for `Mouse.MouseUpEvent`, not the button-specific `MouseLeftButtonUpEvent`):
[lines 239–258](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs#L239-L258):
```csharp
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](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs#L784-L809), calling `FinishSelection` ([811–851](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs#L811-L851)) → `Calendar.OnDayClick` (`src/.../Controls/Calendar.cs`, [lines 887–903](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Calendar.cs#L887-L903)), 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](https://github.com/dotnet/wpf/blob/1cfc37f708f91ff4556bd25af414546c446f3a16/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/MouseDevice.cs#L1558-L1630)) 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:
```csharp
// 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
- **#11736** — [`ElementNotAvailableException` thrown when clicking navigation button in WPF Calendar control on .NET 10](https://github.com/dotnet/wpf/issues/11736) (open). Different symptom (an automation-peer exception during `KeyboardDevice.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 the `Button.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`'s `MoveDisplayTo`/`UpdateMonths`/`FocusDate` chain 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.
- **#4183** — [Popup is closed while selecting date from the DatePicker](https://github.com/dotnet/wpf/issues/4183) (open). Different symptom (a `StaysOpen="false"` `Popup` closing unexpectedly when a `DatePicker`'s `Calendar` popup is larger than its host `Popup`), but also rooted in `Calendar`'s click-hit-testing interacting awkwardly with capture/focus outside its own bounds.
I did not find any existing issue describing `Mouse.Captured` leaking from `Calendar`/`CalendarItem` itself, so I believe this is not a duplicate.
Contributor guide
Research direction
Start with src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/CalendarItem.cs, tracing Cell_MouseLeftButtonDown, Cell_MouseLeftButtonUp, and OnMouseUp. Run the minimal net10.0-windows WPF reproduction with real mouse input, then verify that date selection leaves no stale capture and the following Button click reaches Btn_Click.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100