Skip to content

[Windows] Fix Shell title bar overlap with window controls in RTL mode#33109

Open
Shalini-Ashokan wants to merge 8 commits intodotnet:mainfrom
Shalini-Ashokan:fix-32476
Open

[Windows] Fix Shell title bar overlap with window controls in RTL mode#33109
Shalini-Ashokan wants to merge 8 commits intodotnet:mainfrom
Shalini-Ashokan:fix-32476

Conversation

@Shalini-Ashokan
Copy link
Copy Markdown
Contributor

@Shalini-Ashokan Shalini-Ashokan commented Dec 11, 2025

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Issue Details

When Shell.FlowDirection is set to RightToLeft on Windows, the Shell title and hamburger menu overlap with native window control buttons (minimize, maximize, close) because the content mirrors to RTL while title bar buttons remain on the right side.

Root Cause

Windows title bar buttons stay fixed on the right side by default, but Shell UI elements (title, hamburger menu) mirror to the right in RTL mode, causing visual overlap and making window controls unusable.

Description of Change

Applied WS_EX_LAYOUTRTL extended window style flag to mirror the entire window including title bar buttons to the left side, and set WindowRootView.FlowDirection to properly handle WinUI content layout, ensuring all elements move together consistently in RTL mode without overlap.

Validated the behavior in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed

Fixes #32476

Output ScreenShot

Before After
Before-Fix After-Fix

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Dec 11, 2025
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Hey there @@Shalini-Ashokan! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Dec 11, 2025
@Tamilarasan-Paranthaman Tamilarasan-Paranthaman added platform/windows area-controls-shell Shell Navigation, Routes, Tabs, Flyout labels Dec 11, 2025
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 28, 2026

🚦 Gate — Test Verification

📊 Expand Full Gatec5c00f1 · committed the images

Gate Result: ✅ PASSED

Platform: WINDOWS

Tests Detected

# Type Test Name Filter
1 UITest Issue32476 Issue32476

Verification

Step Expected Actual Result
Without fix FAIL FAIL
With fix PASS PASS

Test Execution

Without fix (expect FAIL):

  • Issue32476: FAIL (1 total, 0 passed, 1 failed) — 575 s
    • Failed: ShellRTLFlowDirectionShouldNotCauseOverlap [5 s]
    • Error: VisualTestUtils.VisualTestFailedException : Snapshot different than baseline: ShellRTLFlowDirectionShouldNotCauseOverlap.png (0.72% difference) If the correct baseline has changed (this isn't a a b...

With fix (expect PASS):

  • Issue32476: PASS (1 total, 1 passed, failed) — 476 s

Fix Files Reverted

  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/Handlers/Shell/ShellHandler.Windows.cs

Base Branch: main | Merge Base: 720a9d4


@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 28, 2026

🤖 AI Summary

📊 Expand Full Reviewc5c00f1 · committed the images
🔍 Pre-Flight — Context & Validation

Issue: #32476 - [Windows] Binding RTL FlowDirection in Shell causes Flyout MenuIcon and native window controls to overlap
PR: #33109 - [Windows] Fix Shell title bar overlap with window controls in RTL mode
Platforms Affected: Windows (primary), MacCatalyst (test also covers it)
Files Changed: 1 implementation, 4 test/snapshot

Key Findings

  • Root Cause: In Windows Shell, when FlowDirection is set to RightToLeft, Shell UI elements (title, hamburger menu) mirror to the right side but the native window control buttons (minimize, maximize, close) remain on the right — causing overlap.
  • Fix Approach: Uses P/Invoke (GetWindowLong/SetWindowLong) to apply or remove the WS_EX_LAYOUTRTL extended window style flag, which mirrors the entire window including title bar buttons to the left. Also sets WindowRootView.FlowDirection to align WinUI content layout.
  • P/Invoke Usage: The fix directly calls Win32 APIs via P/Invoke (user32.dll). This is unusual for a MAUI handler — Windows App SDK / WinUI 3 apps typically use AppWindowTitleBar or OverlappedPresenter APIs.
  • Potential Side Effect: WS_EX_LAYOUTRTL mirrors the entire window coordinate system. This could affect non-Shell UI elements, hit-testing, tooltips, pop-ups, etc. It's a heavy-handed approach.
  • GetService<Window>() usage: The fix resolves the window via DI (Services.GetService<Microsoft.UI.Xaml.Window>()). This pattern is fragile for multi-window scenarios and accessing services this way is not the preferred pattern.
  • Test uses Task.Delay(500).Wait(): The PR test uses an explicit delay before VerifyScreenshot(), which the guidelines say to avoid — use retryTimeout instead.
  • Test taps ToggleButton twice: The test toggles RTL and then back — test should verify the RTL state directly.
  • Missing newline at end of files: Both new test files are missing a trailing newline.
  • Snapshot committed: Windows and Mac snapshot images were committed as part of the PR.
  • Gate result: ✅ PASSED — tests fail without fix, pass with fix on Windows.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #33109 Apply WS_EX_LAYOUTRTL via P/Invoke + set WindowRootView.FlowDirection ✅ PASSED (Gate) ShellHandler.Windows.cs Original PR; P/Invoke-based; may have side effects on full window layout

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) AppWindowTitleBar inset swap — WindowRootView.FlowDirection=RTL + swap LeftInset/RightInset ❌ FAIL (0.72% diff) ShellHandler.Windows.cs Caption buttons stay on right; reference expects them on left
2 try-fix (claude-sonnet-4.6) WS_EX_LAYOUTRTL via GetWindowLongPtrW/SetWindowLongPtrW in nested NativeMethods class; window resolved via view.Window MAUI API ✅ PASS ShellHandler.Windows.cs 64-bit safe; cleaner window resolution; no new file needed
3 try-fix (gpt-5.3-codex) XAML-only WindowTitleBarContainerMargin approach — no P/Invoke ❌ FAIL (0.78% diff) ShellHandler.Windows.cs Caption buttons remain on right; XAML cannot physically reposition them
4 try-fix (gpt-5.4, gemini fallback) WS_EX_LAYOUTRTL via SetWindowLongPtr; window from view.Window; P/Invoke in nested NativeMethods class ✅ PASS ShellHandler.Windows.cs 64-bit safe; MAUI window API; single-file change
5 try-fix (claude-sonnet-4.6, cross-poll) DwmSetWindowAttribute(DWMWA_NONCLIENT_RTL_LAYOUT) — surgical DWM attribute ❌ FAIL (0.72% diff) ShellHandler.Windows.cs Mirrors glyphs only, does not reposition caption buttons physically
PR PR #33109 WS_EX_LAYOUTRTL via SetWindowLong (32-bit) + Services.GetService<Window>() ✅ PASSED (Gate) ShellHandler.Windows.cs Works but 32-bit P/Invoke + fragile window resolution

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 No WS_EX_LAYOUTRTL is the only mechanism; solution space exhausted
claude-sonnet-4.6 2 Yes DWMWA_NONCLIENT_RTL_LAYOUT (tested as attempt 5 → ❌ FAIL)
gpt-5.3-codex 2 No No new ideas
gpt-5.4 2 Yes (low value) Full custom title bar — too invasive, not worth testing
claude-opus-4.6 3 No Exhausted
gpt-5.3-codex 3 Yes (same mechanism) Apply at HWND creation time + SWP_FRAMECHANGED — variant of WS_EX_LAYOUTRTL, not meaningfully different since existing fixes already pass

Exhausted: Yes
Selected Fix: Attempt 2/4 (both passing, both better than PR) — Reason: WS_EX_LAYOUTRTL is the only mechanism that physically repositions caption buttons. Attempt 4's diff (nested NativeMethods, SetWindowLongPtr, view.Window resolution) is the cleanest single-file implementation. PR's fix has 32-bit P/Invoke and fragile DI window resolution that should be improved.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #32476, Windows Shell RTL overlap
Gate ✅ PASSED Windows — tests fail without fix, pass with fix
Try-Fix ✅ COMPLETE 5 attempts, 2 independent passes (attempts 2 & 4)
Report ✅ COMPLETE

Summary

The PR correctly identifies WS_EX_LAYOUTRTL as the required Win32 mechanism to physically reposition caption buttons (minimize/maximize/close) to the left side in RTL mode. The approach is validated — 5 attempts across multiple models confirmed there is no WinUI3-native (non-P/Invoke) alternative that achieves the same visual result. However, the PR's implementation has addressable quality issues that should be resolved before merge.

Root Cause

When Shell.FlowDirection = RightToLeft on Windows, the WinUI/XAML content mirrors correctly but the native non-client area (caption buttons) is controlled by DWM and does not respond to XAML FlowDirection. The WS_EX_LAYOUTRTL extended window style is the only Win32 API that instructs DWM to physically move caption buttons from the right to the left side.

Fix Quality

What's correct:

  • WS_EX_LAYOUTRTL is the right mechanism (confirmed by exhaustive exploration)
  • Setting WindowRootView.FlowDirection to sync WinUI content layout is correct
  • Clearing the flag when reverting to LTR is correct
  • The test covers the Windows scenario and gate passes

Issues requiring changes:

1. 🔴 32-bit P/Invoke on 64-bit process (SetWindowLong vs SetWindowLongPtr)

// ❌ PR uses:
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

// ✅ Should use pointer-sized variants:
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = true)]
static extern nint GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)]
static extern nint SetWindowLongPtr(IntPtr hWnd, int nIndex, nint dwNewLong);

SetWindowLong is 32-bit only; SetWindowLongPtr is required for correct behavior in 64-bit processes on modern Windows.

2. 🔴 Fragile window resolution via DI (Services.GetService<Window>())

// ❌ PR uses:
var window = handler.MauiContext?.Services?.GetService<Microsoft.UI.Xaml.Window>();

// ✅ Should use MAUI's window abstraction:
var mauiWindow = view.Window ?? handler.MauiContext?.GetPlatformWindow()?.GetWindow();
if (mauiWindow?.Handler?.PlatformView is not Microsoft.UI.Xaml.Window platformWindow) return;

Services.GetService<Window>() will return an arbitrary or null window in multi-window MAUI apps. view.Window is the correct, contextual reference.

3. 🟡 P/Invoke declarations at handler class level (minor)

P/Invoke declarations are scattered at the top of ShellHandler class. They should be encapsulated in a nested private static class NativeMethods for clarity (as demonstrated in attempt 4's diff).

4. 🟡 Test uses Task.Delay(500).Wait() (anti-pattern)

// ❌ PR test:
Task.Delay(500).Wait();
VerifyScreenshot(includeTitleBar: true);

// ✅ Should use:
VerifyScreenshot(includeTitleBar: true, retryTimeout: TimeSpan.FromSeconds(2));

Per UI test guidelines, explicit delays before VerifyScreenshot should be replaced with retryTimeout.

5. 🟡 Double assignment in HostApp test code

// ❌ Bug in Issue32476.cs HostApp:
FlowDirection = FlowDirection = FlowDirection.RightToLeft;  // double assignment

The assignment FlowDirection = FlowDirection = X is redundant — should be FlowDirection = FlowDirection.RightToLeft;.

6. 🟡 Missing newlines at end of test files

Both src/Controls/tests/TestCases.HostApp/Issues/Issue32476.cs and src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32476.cs are missing trailing newlines.

Suggested Implementation (from Try-Fix attempt 4 — ✅ PASS)

The diff from attempt 4 provides a drop-in replacement that addresses all critical issues:

  • Resolves window via view.Window (MAUI API)
  • Uses GetWindowLongPtrW/SetWindowLongPtrW (64-bit safe)
  • Encapsulates P/Invoke in nested NativeMethods class
  • Single-file change to ShellHandler.Windows.cs

See CustomAgentLogsTmp/PRState/33109/PRAgent/try-fix/attempt-2/fix.diff for the exact diff.

Selected Fix: Agent Fix (attempt 4) — cleaner implementation of same mechanism


@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33109

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33109"

@Shalini-Ashokan
Copy link
Copy Markdown
Contributor Author

🤖 AI Summary

📊 Expand Full Reviewc5c00f1 · committed the images

Concern 1: PlatformMethods is internal to the Microsoft.Maui.Essentials assembly (Microsoft.Maui.ApplicationModel namespace) and is not accessible from the handler layer. Using raw P/Invokes is the only viable option here.

Concern 2: This concern is not valid. handler.MauiContext?.Services?.GetService() resolves from the handler's own scoped MauiContext, which is already bound to the current window — it cannot return an arbitrary window.

Concern 3: No other handler in the MAUI codebase uses a nested NativeMethods class, so this is not an established pattern to follow. The current approach is consistent with the rest of the codebase.

Concern 4 : Using retryTimeout caused errors during testing, so it was intentionally avoided.

concern 5 & 6 : I resolved the concerns.

@sheiksyedm sheiksyedm marked this pull request as ready for review April 6, 2026 06:47
Copilot AI review requested due to automatic review settings April 6, 2026 06:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 Windows Shell RTL layout issue where the Shell title/hamburger can overlap native window caption buttons by mirroring the window’s non-client area when Shell.FlowDirection is RightToLeft, and adds UI test coverage/snapshots for the scenario.

Changes:

  • Update Windows ShellHandler flow-direction mapping to apply WS_EX_LAYOUTRTL and adjust the root view flow direction.
  • Add new HostApp repro (Issue32476) and a corresponding UI test with updated baseline screenshots for Windows and Mac.
  • Add new snapshot baselines for ShellRTLFlowDirectionShouldNotCauseOverlap on WinUI and Mac.

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Controls/src/Core/Handlers/Shell/ShellHandler.Windows.cs Applies a Win32 extended window style during RTL to mirror the title bar/caption buttons and sets WindowRootView.FlowDirection.
src/Controls/tests/TestCases.HostApp/Issues/Issue32476.cs Adds a Shell-based repro page with a button intended to switch FlowDirection to RTL.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32476.cs Adds an Appium/NUnit UI test validating no overlap (via screenshot).
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShellRTLFlowDirectionShouldNotCauseOverlap.png Adds/updates the Windows screenshot baseline for the new test.
src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShellRTLFlowDirectionShouldNotCauseOverlap.png Adds/updates the Mac screenshot baseline for the new test.

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

Labels

area-controls-shell Shell Navigation, Routes, Tabs, Flyout community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/windows s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] Binding RTL FlowDirection in Shell causes Flyout MenuIcon and native window controls to overlap

5 participants