Skip to content

Calendar (outside DatePicker's popup) leaks Mouse.Capture on every date selection, silently swallowing the next click anywhere in the window #11857

Description

@drolevar

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:

  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:

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugProduct bug (most likely)Cost:SWork that requires one engineer up to 1 weekPriority:2Work that is important, but not critical for the release

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions