Reputation: 6608
I made the MAUI sample project in Visual Studio 2022, but when I launch the program, the window's title bar is gray, a gray that doesn't change color at all when the window loses focus (though the title text I added changes from black to grey). I have "Show accent color on the following surfaces" with both boxes checked.
Why is my MAUI window not using my accent color, and how do I fix it?
Note: I'm on Windows 10, so I can't use that thing that works only on Windows 11.
Upvotes: 3
Views: 3982
Reputation: 1
you can change Platforms/Windows/App.xaml
<maui:MauiWinUIApplication
x:Class="MauiApp1.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:MauiApp1.WinUI">
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="WindowCaptionBackground">#fff</SolidColorBrush>
<SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">#fff</SolidColorBrush>
<SolidColorBrush x:Key="WindowCaptionForeground">#000</SolidColorBrush>
<SolidColorBrush x:Key="WindowCaptionForegroundDisabled">#000</SolidColorBrush>
</ResourceDictionary>
</Application.Resources>
</maui:MauiWinUIApplication>
if you want change title bar color at runtime
1.Install this package
<PackageReference Include="PInvoke.User32" Version="0.7.124" Condition="$(TargetFramework.Contains('-windows')) == true" />
2.Add the following code
using Microsoft.Maui.Platform;
using WinRT.Interop;
using System.Runtime.Versioning;
using PInvoke;
using static PInvoke.User32;
namespace MauiApp1;
[SupportedOSPlatform("Windows")]
public static class TitleBar
{
static Microsoft.UI.Xaml.Window? NativeWindow =>
(Microsoft.UI.Xaml.Window?)Application.Current?.Windows.FirstOrDefault()?.Handler?.PlatformView;
static Microsoft.UI.Xaml.ResourceDictionary Resources =>
Microsoft.UI.Xaml.Application.Current.Resources;
public static void SetColor(Color color)
{
Resources["WindowCaptionBackground"] = color.ToWindowsColor();
Resources["WindowCaptionBackgroundDisabled"] = color.ToWindowsColor();
TriggertTitleBarRepaint();
}
public static void SetStyle(TitleBarStyle style)
{
var color = style switch
{
TitleBarStyle.Default => Colors.Black,
TitleBarStyle.LightContent => Colors.White,
TitleBarStyle.DarkContent => Colors.Black,
_ => throw new NotSupportedException($"{nameof(TitleBarStyle)} {style} is not yet supported on iOS")
};
Resources["WindowCaptionForeground"] = color.ToWindowsColor();
Resources["WindowCaptionForegroundDisabled"] = color.ToWindowsColor();
TriggertTitleBarRepaint();
}
static void TriggertTitleBarRepaint()
{
if (NativeWindow is null)
{
return;
}
var hWnd = WindowNative.GetWindowHandle(NativeWindow);
var activeWindow = User32.GetActiveWindow();
if (hWnd == activeWindow)
{
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x00), IntPtr.Zero);
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x01), IntPtr.Zero);
}
else
{
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x01), IntPtr.Zero);
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x00), IntPtr.Zero);
}
}
}
public enum TitleBarStyle
{
Default = 0,
LightContent = 1,
DarkContent = 2
}
3.use
#if Windows
TitleBar.SetColor(titleBarColor);
TitleBar.SetStyle(TitleBarStyle.DarkContent);
#endif
Valid on both Win10 and Win11
Upvotes: 0
Reputation: 6608
In the end, I used a code based on this one to hide most of the grey title bar while on Windows, plus a Windows-only dependency on a Windows Forms library I use to childify and enclose the WinUI3 window inside a Windows Form (that resizes its child when resized).
The WinForms side
I start by creating a basic form and add P/Invoke code to childify and resize (as well as a simplified form of the FormClosed
event).
public partial class Form1 : Form, IContainerForm
{
public Form1()
{
InitializeComponent();
}
static class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetParent(IntPtr hWnd);
public const int GWL_STYLE = -16;
[DllImport("User32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)]
public extern static uint GetWindowLongU(IntPtr hwnd, int nIndex);
[DllImport("User32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
public extern static uint SetWindowLongU(IntPtr hwnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
}
[Flags]
public enum WindowStyles : uint
{
WS_BORDER = 0x800000,
WS_CAPTION = 0xc00000,
WS_CHILD = 0x40000000,
WS_CLIPCHILDREN = 0x2000000,
WS_CLIPSIBLINGS = 0x4000000,
WS_DISABLED = 0x8000000,
WS_DLGFRAME = 0x400000,
WS_GROUP = 0x20000,
WS_HSCROLL = 0x100000,
WS_MAXIMIZE = 0x1000000,
WS_MAXIMIZEBOX = 0x10000,
WS_MINIMIZE = 0x20000000,
WS_MINIMIZEBOX = 0x20000,
WS_OVERLAPPED = 0x0,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUP = 0x80000000u,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_SIZEFRAME = 0x40000,
WS_SYSMENU = 0x80000,
WS_TABSTOP = 0x10000,
WS_VISIBLE = 0x10000000,
WS_VSCROLL = 0x200000
}
IntPtr hwndContained;
#region IContainerForm members
public IntPtr EnclosedHandle { get => hwndContained; }
public void Enclose(IntPtr hWndToContain)
{
if(Handle==IntPtr.Zero)
throw new InvalidOperationException("Cannot enclose window because current object's window is not created.");
var windowStyles = (WindowStyles)NativeMethods.GetWindowLongU(hWndToContain, NativeMethods.GWL_STYLE);
windowStyles &= ~WindowStyles.WS_OVERLAPPEDWINDOW;
windowStyles &= ~WindowStyles.WS_POPUP;
windowStyles &= ~WindowStyles.WS_CAPTION;
windowStyles |= WindowStyles.WS_CHILD;
NativeMethods.SetWindowLongU(hWndToContain, NativeMethods.GWL_STYLE, (uint)windowStyles);
NativeMethods.SetParent(hWndToContain, Handle);
hwndContained = hWndToContain;
OnSizeChanged(EventArgs.Empty);
}
public string TitleText { get => this.Text; set => this.Text=value; }
private event EventHandler? formClosedSimple;
public event EventHandler FormClosedSimple { add => formClosedSimple+=value; remove => formClosedSimple-=value; }
private void FireFormClosedSimple()
{
if(formClosedSimple != null)
formClosedSimple(this, EventArgs.Empty);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
FireFormClosedSimple();
}
#endregion
public static bool HasParent(IntPtr hWnd)
{
return NativeMethods.GetParent(hWnd)!=IntPtr.Zero;
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
int width = Width;
int height = Height;
if(width==0 || height==0 || WindowState == FormWindowState.Minimized)
return;
if(hwndContained!=IntPtr.Zero)
{
NativeMethods.MoveWindow(hwndContained, 0, 0, ClientSize.Width, ClientSize.Height, true);
}
}
}
To avoid the calling code having to manipulate WinForms types directly, I isolate it behind an interface:
public class Class1
{
public static IContainerForm CreateContainerForm()
{
var form = new Form1();
form.Show();
return form;
}
public static IContainerForm CreateContainerForm(int width, int height)
{
var form = new Form1();
form.Width = width;
form.Height = height;
form.Show();
return form;
}
public static bool HasParent(IntPtr hWnd) => Form1.HasParent(hWnd);
}
public interface IContainerForm
{
void Enclose(IntPtr hWnd);
IntPtr EnclosedHandle { get; }
string TitleText { get; set; }
event EventHandler FormClosedSimple;
}
That's it for the Windows Forms Control Library project.
The MAUI side
For starters, I add a conditional reference to the Windows Forms Control Library:
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != false">
<ProjectReference Include="..\WinFormsLibrary1\WinFormsLibrary1.csproj" />
</ItemGroup>
Then it's just a matter of calling it (but only on Windows) from the MAUI code:
using Microsoft.Extensions.Logging;
using Microsoft.Maui.LifecycleEvents;
#if WINDOWS
using Microsoft.UI;
using Microsoft.UI.Windowing;
#endif
namespace MySecondMauiApp
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
AddTitleBarCodeOnWindows(builder, new System.Drawing.Size(640, 640));
return builder.Build();
}
public static void AddTitleBarCodeOnWindows(MauiAppBuilder builder, System.Drawing.Size size)
{
#if WINDOWS
builder.ConfigureLifecycleEvents(events =>
{
events.AddWindows(wndLifeCycleBuilder =>
{
wndLifeCycleBuilder.OnWindowCreated(window =>
{
IntPtr nativeWindowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window);
WindowId win32WindowsId = Win32Interop.GetWindowIdFromWindow(nativeWindowHandle);
AppWindow winuiAppWindow = AppWindow.GetFromWindowId(win32WindowsId);
if(winuiAppWindow.Presenter is OverlappedPresenter p)
{
window.ExtendsContentIntoTitleBar = false;
p.SetBorderAndTitleBar(false, false);
}
var containerForm = WinFormsLibrary1.Class1.CreateContainerForm(size.Width, size.Height);
containerForm.Enclose(nativeWindowHandle);
containerForm.TitleText = window.Title;
//Apparently necessary since app doesn't close on its own.
//I would have thought closing the form, and therefore destroying the child MAUI Window, would do the trick.
containerForm.FormClosedSimple += (sender, args) => Application.Current.Quit();
});
});
});
#endif
}
}//class
}
And that's it, you now have a shiny new MAUI application whose title bar correctly uses your accent color when focused (and turns white when unfocused):
Of course, none of this would have been necessary had Microsoft not unilaterally decreed that all WinUI3 windows would have a grey title bar that doesn't change color based on focus, instead of following your accent color. But now at least you can fix it.
Upvotes: 1
Reputation: 21243
Alexandar May's comment references doc that describes full customization of the title bar:
Per the official docs Title bar customization / Full customization, there are two levels of customization that you can apply to the title bar: apply minor modifications to the default title bar, or extend your app canvas into the title bar area and provide completely custom content.
From that doc / Title bar content and drag regions:
<Grid x:Name="AppTitleBar">
<Image Source="Images/WindowIcon.png"
HorizontalAlignment="Left"
Width="16" Height="16"
Margin="8,0"/>
<TextBlock x:Name="AppTitleTextBlock" Text="App title"
TextWrapping="NoWrap"
Style="{StaticResource CaptionTextBlockStyle}"
VerticalAlignment="Center"
Margin="28,0,0,0"/>
</Grid>
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar);
AppTitleTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;
}
ORIGINAL ANSWER
tl;dr: You can't control title bar color on Windows 10. At least not via WinUI-3 APIs.
IMPORTANT: This answer describes the situation with WinUI-3 APIs.
I'll leave it to someone else to figure out how to use P/Invoke: Build a C# .NET app with WinUI 3 and Win32 interop, to get at Win32 APIs that might work on Windows 10.
It might not be possible even that way.
Unclear to me whether the Window created by WinUI-3 on Windows 10 is physically capable of changing its title color.
The existing WinUI-3 APIs don't support this on Windows 10. WinUI-3 is what Maui targets on Windows.
Title bar customization says:
Title bar customization APIs are currently supported on Windows 11 only. We recommend that you check
AppWindowTitleBar.IsCustomizationSupported
in your code before you call these APIs to ensure your app doesn't crash on other versions of Windows.
Further detail is shown in Windows UI Library in the Windows App SDK (WinUI 3).
There is a table Feature Window AppWindow
showing features supported on Windows 10.
In that table, we see that Window
is supported on Windows 10, but AppWindow
is not.
It also shows that Window
allows (only) Title
to be set. Need AppWindow
to change colors.
Upvotes: 2