From 162d08f2cf1699b90fb780e6e7a317864a96cd11 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Tue, 23 Sep 2025 06:02:44 +0300 Subject: [PATCH 1/3] TitleBar Captions buttons automatically adjust WindowStyle to match flow direction --- components/TitleBar/src/NativeMethods.cs | 36 ++++++++++++++++------- components/TitleBar/src/TitleBar.WASDK.cs | 27 ++++++++++++++++- components/TitleBar/src/WndProcHelper.cs | 2 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/components/TitleBar/src/NativeMethods.cs b/components/TitleBar/src/NativeMethods.cs index d3874648c..d5dff8ccf 100644 --- a/components/TitleBar/src/NativeMethods.cs +++ b/components/TitleBar/src/NativeMethods.cs @@ -19,14 +19,21 @@ public enum WindowMessage : int [Flags] public enum WindowStyle : uint { - WS_SYSMENU = 0x80000 + WS_SYSMENU = 0x80000, + } + + [Flags] + public enum WindowStyleExtended : ulong + { + WS_EX_LAYOUTRTL= 0x00400000L, } [Flags] public enum WindowLongIndexFlags : int { GWL_WNDPROC = -4, - GWL_STYLE = -16 + GWL_STYLE = -16, + GWL_EXSTYLE = -20, } [Flags] @@ -44,43 +51,50 @@ public enum SystemCommand SC_KEYMENU = 0xF100 } + // TODO: Check for typing online. IntPtr, int, or long? + [DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = false)] - public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + public static extern int GetWindowLongW(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = false)] + public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = false)] - public static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex); + public static extern int GetWindowLongPtrW(IntPtr hWnd, int nIndex); public static int GetWindowLongAuto(IntPtr hWnd, int nIndex) { if (IntPtr.Size is 8) { - return GetWindowLongPtr(hWnd, nIndex); + return GetWindowLongPtrW(hWnd, nIndex); } else { - return GetWindowLong(hWnd, nIndex); + return GetWindowLongW(hWnd, nIndex); } } [DllImport("user32.dll", EntryPoint = "FindWindowExW", SetLastError = true, CharSet = CharSet.Unicode)] public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow); - [DllImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = false)] - public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + public static extern IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, IntPtr dwNewLong); - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)] + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = false)] public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)] + public static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + public static IntPtr SetWindowLongAuto(IntPtr hWnd, int nIndex, IntPtr dwNewLong) { if (IntPtr.Size is 8) { - return SetWindowLongPtr(hWnd, nIndex, dwNewLong); + return SetWindowLongPtrW(hWnd, nIndex, dwNewLong); } else { - return SetWindowLong(hWnd, nIndex, dwNewLong); + return SetWindowLongW(hWnd, nIndex, dwNewLong); } } diff --git a/components/TitleBar/src/TitleBar.WASDK.cs b/components/TitleBar/src/TitleBar.WASDK.cs index 6e5286658..218f3f50a 100644 --- a/components/TitleBar/src/TitleBar.WASDK.cs +++ b/components/TitleBar/src/TitleBar.WASDK.cs @@ -3,11 +3,12 @@ // See the LICENSE file in the project root for more information. #if WINDOWS_WINAPPSDK && !HAS_UNO -using Windows.Graphics; using Microsoft.UI; using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml.Media; +using System.Runtime.InteropServices; +using Windows.Graphics; namespace CommunityToolkit.WinUI.Controls; @@ -100,6 +101,30 @@ private void UpdateCaptionButtons(FrameworkElement rootElement) Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.Black; Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray; } + + // Set the caption buttons to match the flow direction of the app + UpdateCaptionButtonsDirection(rootElement.FlowDirection); + } + + private void UpdateCaptionButtonsDirection(FlowDirection direction) + { + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this.Window); + + if (hwnd != 0) + { + var exStyle = NativeMethods.GetWindowLongPtr(hwnd, (int)NativeMethods.WindowLongIndexFlags.GWL_EXSTYLE); + + if (direction == FlowDirection.RightToLeft) + { + exStyle |= (nint)NativeMethods.WindowStyleExtended.WS_EX_LAYOUTRTL; + } + else + { + exStyle &= (nint)NativeMethods.WindowStyleExtended.WS_EX_LAYOUTRTL; + } + + NativeMethods.SetWindowLongPtr(hwnd, (int)NativeMethods.WindowLongIndexFlags.GWL_EXSTYLE, exStyle); + } } private void ResetWASDKTitleBar() diff --git a/components/TitleBar/src/WndProcHelper.cs b/components/TitleBar/src/WndProcHelper.cs index 63ed9732e..e40347233 100644 --- a/components/TitleBar/src/WndProcHelper.cs +++ b/components/TitleBar/src/WndProcHelper.cs @@ -44,7 +44,7 @@ public void RegisterInputNonClientPointerSourceWndProc(WNDPROC wndProc) if (inputNonClientPointerSourceHandle != IntPtr.Zero) { - int style = NativeMethods.GetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE); + IntPtr style = NativeMethods.GetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE); NativeMethods.SetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE, (IntPtr)(style & ~(int)NativeMethods.WindowStyle.WS_SYSMENU)); newInputNonClientPointerSourceWndProc = wndProc; From 5b8ca6282001acc83dc3911e4874222ed287d035 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Tue, 23 Sep 2025 07:27:47 +0300 Subject: [PATCH 2/3] Fixed caption button padding in RTL and added FlowDirection inhereitance to window creation sample --- .../TitleBar/samples/TitleBarFullSample.xaml.cs | 9 ++++++++- components/TitleBar/src/TitleBar.WASDK.cs | 15 +++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/components/TitleBar/samples/TitleBarFullSample.xaml.cs b/components/TitleBar/samples/TitleBarFullSample.xaml.cs index 546d4ec0a..9f950cd4a 100644 --- a/components/TitleBar/samples/TitleBarFullSample.xaml.cs +++ b/components/TitleBar/samples/TitleBarFullSample.xaml.cs @@ -44,7 +44,14 @@ await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { SystemBackdrop = new MicaBackdrop() }; - newWindow.Content = new ShellPage(newWindow); + + // Create the content for the window to show + // and set the FlowDirection to match the current region. + newWindow.Content = new ShellPage(newWindow) + { + FlowDirection = this.FlowDirection + }; + newWindow.Activate(); #endif } diff --git a/components/TitleBar/src/TitleBar.WASDK.cs b/components/TitleBar/src/TitleBar.WASDK.cs index 218f3f50a..cf51023f4 100644 --- a/components/TitleBar/src/TitleBar.WASDK.cs +++ b/components/TitleBar/src/TitleBar.WASDK.cs @@ -14,7 +14,6 @@ namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))] [TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))] - public partial class TitleBar : Control { WndProcHelper? WndProcHelper; @@ -55,12 +54,23 @@ private void SetWASDKTitleBar() }; } + // Set the caption buttons to match the flow direction of the titlebar + UpdateCaptionButtonsDirection(this.FlowDirection); + PART_ContentPresenter = GetTemplateChild(nameof(PART_ContentPresenter)) as ContentPresenter; PART_FooterPresenter = GetTemplateChild(nameof(PART_FooterPresenter)) as ContentPresenter; // Get caption button occlusion information. int CaptionButtonOcclusionWidthRight = Window.AppWindow.TitleBar.RightInset; int CaptionButtonOcclusionWidthLeft = Window.AppWindow.TitleBar.LeftInset; + + // Swap left/right if in RTL mode + if (this.FlowDirection == FlowDirection.RightToLeft) + { + (CaptionButtonOcclusionWidthRight, CaptionButtonOcclusionWidthLeft) = (CaptionButtonOcclusionWidthLeft, CaptionButtonOcclusionWidthRight); + } + + // Set padding columns to match caption button occlusion. PART_LeftPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthLeft); PART_RightPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthRight); @@ -101,9 +111,6 @@ private void UpdateCaptionButtons(FrameworkElement rootElement) Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.Black; Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray; } - - // Set the caption buttons to match the flow direction of the app - UpdateCaptionButtonsDirection(rootElement.FlowDirection); } private void UpdateCaptionButtonsDirection(FlowDirection direction) From 6614a118cae8de6aeef316dc979de696e5911085 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Tue, 23 Sep 2025 19:24:50 +0300 Subject: [PATCH 3/3] Added a property for whether to change the WindowStyle to match the TitleBar flow direction --- components/TitleBar/src/TitleBar.Properties.cs | 14 ++++++++++++++ components/TitleBar/src/TitleBar.WASDK.cs | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/components/TitleBar/src/TitleBar.Properties.cs b/components/TitleBar/src/TitleBar.Properties.cs index 17237dacb..168512ea1 100644 --- a/components/TitleBar/src/TitleBar.Properties.cs +++ b/components/TitleBar/src/TitleBar.Properties.cs @@ -58,6 +58,11 @@ public partial class TitleBar : Control public static readonly DependencyProperty AutoConfigureCustomTitleBarProperty = DependencyProperty.Register(nameof(AutoConfigureCustomTitleBar), typeof(bool), typeof(TitleBar), new PropertyMetadata(true, AutoConfigureCustomTitleBarChanged)); #if WINAPPSDK + /// + /// The backing for the property. + /// + public static readonly DependencyProperty AutoChangeWindowLayoutStyleProperty = DependencyProperty.Register(nameof(AutoChangeWindowLayoutStyle), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); + /// /// The backing for the property. /// @@ -165,6 +170,15 @@ public bool AutoConfigureCustomTitleBar } #if WINAPPSDK + /// + /// Gets or sets if the TitleBar should automatically change the Window's LayoutStyle to match the FlowDirection of the TitleBar (WASDK only). + /// + public bool AutoChangeWindowLayoutStyle + { + get => (bool)GetValue(AutoChangeWindowLayoutStyleProperty); + set => SetValue(AutoChangeWindowLayoutStyleProperty, value); + } + /// /// Gets or sets the window the TitleBar should configure (WASDK only). /// diff --git a/components/TitleBar/src/TitleBar.WASDK.cs b/components/TitleBar/src/TitleBar.WASDK.cs index cf51023f4..321872132 100644 --- a/components/TitleBar/src/TitleBar.WASDK.cs +++ b/components/TitleBar/src/TitleBar.WASDK.cs @@ -55,7 +55,10 @@ private void SetWASDKTitleBar() } // Set the caption buttons to match the flow direction of the titlebar - UpdateCaptionButtonsDirection(this.FlowDirection); + if (AutoChangeWindowLayoutStyle) + { + UpdateCaptionButtonsDirection(this.FlowDirection); + } PART_ContentPresenter = GetTemplateChild(nameof(PART_ContentPresenter)) as ContentPresenter; PART_FooterPresenter = GetTemplateChild(nameof(PART_FooterPresenter)) as ContentPresenter;