Skip to content

TabViewItem: don't enter the drag visual state when dragging is disallowed - #11672

Open
Martin Zikmund (MartinZikmund) wants to merge 3 commits into
microsoft:mainfrom
MartinZikmund:user/mzikmund/tabitem-drag
Open

TabViewItem: don't enter the drag visual state when dragging is disallowed#11672
Martin Zikmund (MartinZikmund) wants to merge 3 commits into
microsoft:mainfrom
MartinZikmund:user/mzikmund/tabitem-drag

Conversation

@MartinZikmund

Copy link
Copy Markdown
Contributor

Fixes

Fixes #10749

PR Type

  • Bugfix

Description

A TabViewItem would enter (and get stuck in) the DragDropVisualVisible visual state even when the parent TabView disallowed dragging and reordering.

TabViewItem::BeginCheckingForDrag unconditionally armed drag tracking on PointerPressed. ListViewBaseItem only captures the pointer for drag detection when the item is actually draggable, so when dragging is disallowed the item stops receiving pointer events as soon as the pointer leaves its bounds — the PointerReleased/PointerCaptureLost that would take it back out of the drag visual state never arrives, and the state sticks until the item is unfocused and refocused.

The fix adds TabViewItem::IsDraggingAllowed(), which mirrors the conditions under which ListViewBaseItem captures the pointer (TabView.CanDragTabs, TabView.CanReorderTabs, or the item's own UIElement.CanDrag). BeginCheckingForDrag now bails out early when dragging isn't allowed, so the drag visual state is never entered in the first place. When the item has no parent TabView, the previous behavior is preserved.

Current Behavior

With <TabView CanReorderTabs="False">, dragging a tab still shows the drag visual (DragDropVisualVisible) and the tab remains in that state after the pointer is released.

New Behavior

With dragging and reordering disallowed, the tab never enters DragDropVisualVisible. Behavior is unchanged when dragging or reordering is allowed, or when the item sets CanDrag="True" itself.

Customer Impact

User-facing. Fixes an invalid, persistent visual state on TabViewItem for apps that turn off tab dragging/reordering.

Regression Potential

  • Low risk — isolated change, limited scope

The change is scoped to TabViewItem's pointer-pressed drag tracking and only skips work in the case where the drag could not have been carried out anyway. All existing paths where dragging is allowed are untouched.

How Has This Been Tested?

  • I have performed a self-review of my own code
  • I have added tests to cover my changes
  • Existing tests pass locally

Two new interaction tests in TabViewTests.cs:

  • VerifyTabEntersDragVisualStateWhenDraggingIsEnabled — guards against regressing the normal case, asserting the drag visual still appears when dragging is allowed.
  • VerifyTabDoesNotEnterDragVisualStateWhenDraggingIsDisabled — the regression test for this issue: unchecks CanDragTabs/CanReorderTabs, drags the tab off the tab strip, and asserts the tab neither visits nor is left in DragDropVisualVisible.

The test page (TabViewPage) gained CanDragTabs/CanReorderTabs checkboxes and a small readout of the first tab's drag/drop visual state group (current state plus the log of states visited), which is what the tests assert against.

…lowed

BeginCheckingForDrag armed drag-checking on every pointer press, regardless of
whether the parent TabView permits a drag gesture. With CanReorderTabs=false
(and CanDragTabs defaulting to false), ListViewBase::GetIsDragEnabled() is
false, so ListViewBaseItem never calls BeginCheckingForMouseDrag and never
captures the pointer. The item therefore stops receiving pointer events once
the pointer leaves its bounds and never sees the PointerReleased that would
take it back out of DragDropVisualVisible, leaving the tab stuck in the drag
visual state until it is unfocused and refocused.

Only arm drag-checking when the framework will actually capture the pointer,
mirroring ListViewBase::GetIsDragEnabled() plus the item's own CanDrag.

CanTearOutTabs is deliberately not part of the condition: tear-out registers
the tab strip as a Caption non-client region, so pointer events do not reach
the item at all.

Fixes microsoft#10749

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEWdGmNFY5YbSAAf9xBmDi
Covers both directions of the drag/drop visual state:
- VerifyTabEntersDragVisualStateWhenDraggingIsEnabled: dragging a tab still
  shows the drag visual when the TabView allows dragging.
- VerifyTabDoesNotEnterDragVisualStateWhenDraggingIsDisabled: with both
  CanReorderTabs and CanDragTabs off, dragging a tab away and releasing the
  pointer outside it neither enters DragDropVisualVisible nor leaves the tab
  stuck in it.

TabViewPage gains CanDragTabs/CanReorderTabs checkboxes and a readout that
records every transition of the drag/drop visual state group on FirstTab. The
group is unnamed in the TabViewItem template, so it is located by the states it
contains. Recording the transitions (rather than only sampling the final state)
is what lets the disabled case assert that the visual state is never entered,
not merely that it was left correctly.

Verified against a local x64chk build: the second test fails without the
TabViewItem fix ("Visited states: 'DragDropVisualVisible'", with no return to
DragDropVisualNotVisible) and passes with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEWdGmNFY5YbSAAf9xBmDi
Copilot AI lite review requested due to automatic review settings August 27, 2026 12:13
@microsoft-github-policy-service microsoft-github-policy-service Bot added the needs-triage Issue needs to be triaged by the area owners label Aug 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a TabViewItem visual-state bug where DragDropVisualVisible could be entered (and remain stuck) even when the owning TabView disallows dragging/reordering, by preventing drag-tracking from being armed in non-draggable scenarios and adding interaction tests to cover both the enabled and disabled cases.

Changes:

  • Add TabViewItem::IsDraggingAllowed() and gate BeginCheckingForDrag so the drag visual state is never entered when dragging cannot occur.
  • Extend the TabView TestUI page with toggles for CanDragTabs/CanReorderTabs and a UIA-readable readout/log of the first tab’s drag visual state.
  • Add two new interaction tests validating drag visual-state behavior when dragging is enabled vs. disabled.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
controls/dev/TabView/TestUI/TabViewPage.xaml.cs Adds drag visual-state group discovery + state logging/readout for interaction tests.
controls/dev/TabView/TestUI/TabViewPage.xaml Adds CanDragTabs/CanReorderTabs toggles and buttons/text for reading/resetting drag visual state.
controls/dev/TabView/TabViewItem.h Declares IsDraggingAllowed() helper.
controls/dev/TabView/TabViewItem.cpp Implements IsDraggingAllowed() and prevents drag tracking when dragging is disallowed.
controls/dev/TabView/InteractionTests/TabViewTests.cs Adds regression and non-regression interaction tests for the drag visual state.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread controls/dev/TabView/TestUI/TabViewPage.xaml.cs Outdated
…tate groups

Addresses PR review feedback: FindDragDropVisualStateGroup cast the first
visual child with `as FrameworkElement` and passed the possibly-null result
to VisualStateManager.GetVisualStateGroups, which would throw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeTCwZRu14zhNaPSPxU7vw
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TabItem enters dragging state in TabView with CanReorderTabs=false

2 participants