Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Windows 10 title bar borders #36429

Merged
12 changes: 12 additions & 0 deletions src/common/ManagedCommon/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ internal static class NativeMethods
[DllImport("user32.dll")]
internal static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

[DllImport("dwmapi")]
internal static extern IntPtr DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);

[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
Expand Down Expand Up @@ -100,5 +103,14 @@ internal enum INPUTTYPE : uint
INPUT_KEYBOARD = 1,
INPUT_HARDWARE = 2,
}

[StructLayout(LayoutKind.Sequential)]
internal struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
}
}
12 changes: 12 additions & 0 deletions src/common/ManagedCommon/WindowHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,17 @@ public static void BringToForeground(IntPtr handle)
}
}
}

/// <summary>
/// Workaround for a WinUI bug on Windows 10 in which a window's top border is always
/// black. Calls <c>DwmExtendFrameIntoClientArea()</c> with a <c>cyTopHeight</c> of 2 to force
/// the window's top border to be visible.<br/><br/>
/// Has no visible effect on Windows 11.
/// </summary>
public static void ForceTopBorder1PixelInset(IntPtr handle)
{
var margins = new NativeMethods.MARGINS { cxLeftWidth = 0, cxRightWidth = 0, cyBottomHeight = 0, cyTopHeight = 2 };
Copy link
Collaborator

Choose a reason for hiding this comment

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

As a safety measure, I think we should still only run this on Windows 10. Perhaps a check to a check like the "IsWindows11" function and renaming this function to ForceTopBorder1PixelInsetOnWindows10. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm in favor, but since OSVersionHelper is down in Common.UI, it'd mean:

a) moving WindowHelpers down into Common.UI
b) moving OSVersionHelper up into ManagedCommon
c) Moving ForceTopBorder1PixelInset() out of WindowHelpers and into some new class in Common.UI.

I think I have a slight preference for b), because it makes sense to me to keep an OS version check in the top level helper library, instead of a UI-specific one (as Common.UI appears to be). I'm not super familiar with conventions in this repo though, so is there any particular preference?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think b) makes sense, since it doesn't make sense it's on a UI library. ManagedCommon makes more sense.

NativeMethods.DwmExtendFrameIntoClientArea(handle, ref margins);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ double GetHeight(int maxCustomActionCount) =>
};

WindowHelpers.BringToForeground(this.GetWindowHandle());
WindowHelpers.ForceTopBorder1PixelInset(this.GetWindowHandle());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The docs for DwmExtendFrameIntoClientArea() say that:

This function must be called whenever Desktop Window Manager (DWM) composition is toggled. Handle the WM_DWMCOMPOSITIONCHANGED message for composition change notification.

...however, the docs for WM_DWMCOMPOSITIONCHANGED go on to note that:

As of Windows 8, DWM composition is always enabled, so this message is not sent regardless of video mode changes.

...so I don't think we need to handle this in Window.Activated--just one call at creation time seems to be enough.

}

private void OnActivated(object sender, WindowActivatedEventArgs args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public MainWindow()
var handle = this.GetWindowHandle();
RegisterWindow(handle);

WindowHelpers.ForceTopBorder1PixelInset(handle);
WindowHelpers.BringToForeground(handle);

MainPage = App.GetService<EnvironmentVariablesMainPage>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;

using ManagedCommon;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
Expand All @@ -22,6 +22,7 @@ public MainWindow(bool isElevated)
SetTitleBar(AppTitleBar);
Activated += MainWindow_Activated;
AppWindow.SetIcon("Assets/FileLocksmith/Icon.ico");
WindowHelpers.ForceTopBorder1PixelInset(this.GetWindowHandle());

var loader = ResourceLoaderInstance.ResourceLoader;
var title = isElevated ? loader.GetString("AppAdminTitle") : loader.GetString("AppTitle");
Expand Down
1 change: 1 addition & 0 deletions src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public MainWindow()

var handle = this.GetWindowHandle();

WindowHelpers.ForceTopBorder1PixelInset(handle);
WindowHelpers.BringToForeground(handle);
Activated += MainWindow_Activated;

Expand Down
3 changes: 2 additions & 1 deletion src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public MainWindow()
ViewModel = Application.Current.GetService<MainWindowViewModel>();

TitleBarControl.SetTitleBarToWindow(this);
AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(this.GetWindowHandle());
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
AppWindow.SetIcon("Assets/Peek/Icon.ico");

Expand Down
2 changes: 1 addition & 1 deletion src/modules/peek/Peek.UI/PeekXAML/Views/TitleBar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ private void UpdateTitleBarCustomization(MainWindow mainWindow)
if (AppWindowTitleBar.IsCustomizationSupported())
{
AppWindow appWindow = mainWindow.AppWindow;
appWindow.TitleBar.ExtendsContentIntoTitleBar = true;
mainWindow.ExtendsContentIntoTitleBar = true;
appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
appWindow.TitleBar.ButtonForegroundColor = ThemeHelpers.GetAppTheme() == AppTheme.Light ? Colors.DarkSlateGray : Colors.White;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ internal MainWindow()
OpenWindowPlacementFile(settingsFolder, windowPlacementFile);

// Update the Win32 looking window with the correct icon (and grab the appWindow handle for later)
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
Microsoft.UI.WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
IntPtr windowHandle = this.GetWindowHandle();
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\RegistryPreview\\RegistryPreview.ico");

Expand All @@ -49,6 +49,7 @@ internal MainWindow()

// Extend the canvas to include the title bar so the app can support theming
ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(windowHandle);
SetTitleBar(titleBar);

// if have settings, update the location of the window
Expand Down
7 changes: 7 additions & 0 deletions src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
// Need to call SetForegroundWindow to actually gain focus.
WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle());

// https://github.com/microsoft/microsoft-ui-xaml/issues/8948 - A window's top border incorrectly
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this note be inline at call sites, or on the function itself?

// renders as black on Windows 10.
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(settingsWindow));
}
else
{
Expand All @@ -255,6 +259,7 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview);
oobeWindow.Activate();
oobeWindow.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(settingsWindow));
SetOobeWindow(oobeWindow);
}
else if (ShowScoobe)
Expand All @@ -263,6 +268,7 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
scoobeWindow.Activate();
scoobeWindow.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(settingsWindow));
SetOobeWindow(scoobeWindow);
}
else if (ShowFlyout)
Expand Down Expand Up @@ -310,6 +316,7 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
// Window is also needed to show MessageDialog
settingsWindow = new MainWindow();
settingsWindow.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(settingsWindow));
settingsWindow.Activate();
settingsWindow.NavigateToSection(StartupPage);
ShowMessageDialog("The application is running in Debug mode.", "DEBUG");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
using System.Collections.ObjectModel;
using System.Globalization;

using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinRT.Interop;

namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
Expand Down Expand Up @@ -306,6 +307,7 @@ private void SetTitleBar()
// A custom title bar is required for full window theme and Mica support.
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
u.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(u));
u.SetTitleBar(AppTitleBar);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.UI.Xaml.Controls;
using Windows.Data.Json;
using Windows.System;
using WinRT.Interop;

namespace Microsoft.PowerToys.Settings.UI.Views
{
Expand Down Expand Up @@ -421,6 +422,7 @@ private void SetTitleBar()
// A custom title bar is required for full window theme and Mica support.
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
u.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInset(WindowNative.GetWindowHandle(u));
u.SetTitleBar(AppTitleBar);
var loader = ResourceLoaderInstance.ResourceLoader;
AppTitleBarText.Text = App.IsElevated ? loader.GetString("SettingsWindow_AdminTitle") : loader.GetString("SettingsWindow_Title");
Expand Down
Loading