Search code examples
.netmauiwinui-3maui-windowsmaui-shell

Unable to set the background color of the app title bar / hamburger icon on WinUI in MAUI


The problem

I am trying to style the entire app title bar on Windows to set the background color purple, but my changes to titleBar.BackgroundColor and titleBar.InactiveBackgroundColor are having no effect.

This is how it looks (black parts hidden from the screenshots):

(Click to enlarge)

In ./Platforms/Windows/App.xaml if I set Background="{StaticResource IIT_Purple}" on the <Grid> in the <DataTemplate x:Key="MauiAppTitleBarContainerTemplate"> then it mostly works, but the hamburger menu icon remains without a background color:

(Click to enlarge)

With the <Flyout> open, the hamburger icon has the correct background color:

(Click to enlarge)


The question

How can I either change the background color of the hamburger icon, or otherwise set the background color of the entire app title bar (without having to set a background color on the <Grid>?

I am using .NET 8 (definitely) with WinUI 3 (I think).


The code

This should hopefully be all of the relevant code:

./Platforms/Windows/App.xaml

<maui:MauiWinUIApplication
    x:Class="YourNamespaceGoesHere.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:YourNamespaceGoesHere.WinUI">
    <maui:MauiWinUIApplication.Resources>
        <ResourceDictionary>

            <!-- TODO: Load these from ./Resources/Styles/Colors.xaml -->
            <Color x:Key="IIT_Purple">#6A00FF</Color>
            <Color x:Key="IIT_SlightlyLighterPurple">#8200FF</Color>

            <!-- Window's chrome / app title bar -->
            <DataTemplate x:Key="MauiAppTitleBarContainerTemplate">
                <Grid HorizontalAlignment="Stretch" Background="{StaticResource IIT_Purple}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                    
                        <!-- TODO: Magic number :( -->
                        <RowDefinition Height="32" />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Column="0" VerticalAlignment="Center" Foreground="White">
                        Some App Title Goes Here
                    </TextBlock>

                    <ContentControl Grid.Column="1" IsTabStop="False" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" x:Name="AppTitleBarContentControl" />
                </Grid>
            </DataTemplate>

            <!-- Fills up the right gap on the selected flyout item -->
            <StaticResource x:Key="NavigationViewItemBackgroundSelected" ResourceKey="SystemControlHighlightListMediumBrush" />
            <StaticResource x:Key="NavigationViewItemBackgroundSelectedPointerOver" ResourceKey="SystemControlHighlightListMediumBrush" />
            <SolidColorBrush x:Key="SystemControlHighlightListMediumBrush" Color="{StaticResource IIT_SlightlyLighterPurple}" />

            <!-- Hides the selected indicator by styling it the same color like a chameleon -->
            <StaticResource x:Key="NavigationViewSelectionIndicatorForeground" ResourceKey="SystemControlForegroundAccentBrush" />
            <SolidColorBrush x:Key="SystemControlForegroundAccentBrush"  Color="{StaticResource IIT_SlightlyLighterPurple}" />

        </ResourceDictionary>
    </maui:MauiWinUIApplication.Resources>
</maui:MauiWinUIApplication>

I tried changing MauiAppTitleBarContainerTemplate to MauiAppTitleBarTemplate but there was no difference.


./Platforms/Windows/App.xaml.cs

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using WinRT.Interop;
using Colors = Microsoft.Maui.Graphics.Colors;
using Controls = Microsoft.Maui.Controls;

namespace YourNamespaceGoesHere.WinUI
{
    public partial class App : MauiWinUIApplication
    {
        public App()
        {
            this.InitializeComponent();

            WindowHandler.Mapper.AppendToMapping(nameof(IWindow), (handler, view) =>
            {
                var nativeWindow = handler.PlatformView;
                nativeWindow.Activate();

                var hWnd = WindowNative.GetWindowHandle(nativeWindow);
                var windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
                var appWindow = AppWindow.GetFromWindowId(windowId);
                var titleBar = appWindow.TitleBar;

                // From ./Styles/Colors.xaml
                var Resources =                         Controls.Application.Current!.Resources;
                var Color_IIT_PalePurple =              Resources.GetResource<Color>("IIT_PalePurple")!.ToWindowsColor();
                var Color_IIT_LighterPurple =           Resources.GetResource<Color>("IIT_LighterPurple")!.ToWindowsColor();
                var Color_IIT_SlightlyLighterPurple =   Resources.GetResource<Color>("IIT_SlightlyLighterPurple")!.ToWindowsColor();
                var Color_IIT_Purple =                  Resources.GetResource<Color>("IIT_Purple")!.ToWindowsColor();
                var Color_IIT_SlightlyDarkerPurple =    Resources.GetResource<Color>("IIT_SlightlyDarkerPurple")!.ToWindowsColor();
                var Color_White =                       Colors.White.ToWindowsColor();

                // This first block appears to have no effect
                titleBar.ForegroundColor =          Color_White;
                titleBar.InactiveForegroundColor =  Color_White;
                titleBar.BackgroundColor =          Color_IIT_Purple;
                titleBar.InactiveBackgroundColor =  Color_IIT_Purple;

                // Minimize, Maximize & Close buttons
                titleBar.ButtonForegroundColor =            Color_White;
                titleBar.ButtonInactiveForegroundColor =    Color_White;
                titleBar.ButtonBackgroundColor =            Color_IIT_Purple;
                titleBar.ButtonInactiveBackgroundColor =    Color_IIT_Purple;
                titleBar.ButtonHoverForegroundColor =       Color_White;
                titleBar.ButtonPressedForegroundColor =     Color_White;
                titleBar.ButtonHoverBackgroundColor =       Color_IIT_SlightlyLighterPurple;
                titleBar.ButtonPressedBackgroundColor =     Color_IIT_SlightlyDarkerPurple;
            });
        }

        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
    }
}

./Extensions.cs

namespace YourNamespaceGoesHere
{
    public static class Extensions
    {
        public static T? GetResource<T>(this ResourceDictionary dictionary, string key)
        {
            if(dictionary.TryGetValue(key, out var value) && value is T resource) {
                return resource;
            } else {
                return default;
            }
        }
    }
}

./AppShell.xaml

I've removed the parts that shouldn't be relevant for a minimal required example, like the header and footer:

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="YourNamespaceGoesHere.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:YourNamespaceGoesHere"
    FlyoutBackgroundColor="{StaticResource IIT_Purple}">

    <Shell.ItemTemplate>
        <DataTemplate>
            <FlexLayout Direction="Row" Style="{StaticResource FlyoutItemStyle}" Margin="10">
                <Image Source="{Binding FlyoutIcon}" Margin="0" />
                <Label FlexLayout.Grow="1" Text="{Binding Title}" Margin="10,0,0,0" TextColor="{StaticResource White}" FontSize="Body" VerticalTextAlignment="Center" x:Name="_label" HorizontalOptions="StartAndExpand" />
            </FlexLayout>
        </DataTemplate>
    </Shell.ItemTemplate>

    <FlyoutItem Title="Home" FlyoutIcon="icon_home.png">
        <ShellContent Title="Get Started" Icon="icon_home.png" ContentTemplate="{DataTemplate local:HomePage}" />
    </FlyoutItem>

    <FlyoutItem Title="Some Menu Item" FlyoutIcon="icon_some_menu_item.png">
        <ShellContent Title="Start" Icon="icon_some_menu_item.png" ContentTemplate="{DataTemplate local:SomeMenuItemPage}" />
    </FlyoutItem>

    <FlyoutItem Title="Settings" FlyoutIcon="icon_settings.png">
        <ShellContent Title="Settings" Icon="icon_setings.png" ContentTemplate="{DataTemplate local:SettingsPage}" />
    </FlyoutItem>
</Shell>

./Resources/Styles/Styles.xaml

Just the relevant part that I've changed, with the standard defaults below this:

<!-- Custom styles start -->

<Style TargetType="FlyoutItem">
    <Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource IIT_Purple}, Dark={StaticResource White}}" />
</Style>

<Style Class="FlyoutItemLabelStyle" TargetType="Label">
    <Setter Property="TextColor" Value="{StaticResource White}"></Setter>
</Style>

<Style x:Key="FlyoutItemStyle" TargetType="FlexLayout">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal" />
                <VisualState x:Name="Selected">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="{StaticResource IIT_SlightlyLighterPurple}"/>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

<!-- Custom styles end -->


<!-- Customised defaults start -->

<Style TargetType="Shell" ApplyToDerivedTypes="True">

    <!-- Hamburger menu icon - white on Windows -->
    <Setter Property="Shell.ForegroundColor" Value="{OnPlatform {StaticResource IIT_Purple}, WinUI={StaticResource White}}" />

    <Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource IIT_Purple}}" />
    <Setter Property="Shell.FlyoutBackground" Value="{StaticResource IIT_Purple}" />
    <Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
    <Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
    <Setter Property="Shell.UnselectedColor" Value="{StaticResource Gray200}" />
    <Setter Property="Shell.NavBarHasShadow" Value="False" />
    <Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource IIT_Purple}, Dark={StaticResource White}}" />
    <Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
    <Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource IIT_Purple}, Dark={StaticResource White}}" />
    <Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource IIT_LighterPurple}, Dark={StaticResource Gray200}}" />
</Style>

<!-- Customised defaults end -->

I have tried changing these from Gray200 to another color but it did not help:

  • Shell.DisabledColor
  • Shell.UnselectedColor

./Resources/Styles/Colors.xaml

Again, just the custom parts, with the defaults below this:

<Color x:Key="IIT_PalePurple">#F7F2FF</Color>
<Color x:Key="IIT_LighterPurple">#AD56FD</Color>
<Color x:Key="IIT_SlightlyLighterPurple">#8200FF</Color>
<Color x:Key="IIT_Purple">#6A00FF</Color>
<Color x:Key="IIT_SlightlyDarkerPurple">#5500CC</Color>

Some thoughts

Please let me know if there is anything else that's needed, I'm just conscious that the code required for a minimal example is already quite large.

I would really appreciate any help please!

As an aside, any help loading the colors properly would be amazing too please - I've struggled to get these to work on the WinUI side. I have posted a separate question for that here:

In MAUI, how to load the colors in ./Styles/Colors.xaml from within ./Platforms/Windows/App.xaml + App.xaml.cs?

I know there's a bug with the margin of menu items too - until the flyout is opened for a 2nd time (I'll post a question for that one soon).


Solution

  • In ./Platforms/Windows/App.xaml add:

    <Style TargetType="ContentControl" x:Key="WindowChromeStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Grid x:Name="LayoutRoot" Background="{StaticResource IIT_Purple}">
                        <ContentPresenter x:Name="ClientAreaPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Foreground="{TemplateBinding Foreground}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    Where IIT_Purple is defined at the top of the same file (like in the original question).

    You can then remove Background="{StaticResource IIT_Purple}" from the <Grid> (in the code in the same file from the original question).

    All of the other code in the original question should be kept.

    This successfully changes the entire window's top bar to purple:

    (Click to enlarge)

    Details on where I found this code are in a comment I left on Alexandar's answer.

    All that's left to figure out is how to change the background color of the hamburger icon on hover (it's currently a darker shade of purple, when it should be lighter like the minimize/maximize buttons).