Search code examples
c#winui-3winui

how to make the top panel of the application adaptive to changes in the Windows theme in WinUI?


I am just learning how to create applications on WinUI 3. and I ran into a problem: the top panel does not match the colors with the application itself if it is in a dark theme. Despite the fact that everything is fine in the light theme (you can see it in the picture)

enter image description here

Here is the code

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using WinRT.Interop;
using Microsoft.UI.Composition.SystemBackdrops;
using System.Runtime.InteropServices;
using WinRT; // required to support Window.As<ICompositionSupportsSystemBackdrop>()
using System.Text;


namespace PasswordGenerator
{

    public sealed partial class MainWindow : Window
    {
        WindowsSystemDispatcherQueueHelper m_wsdqHelper;
        MicaController m_backdropController;
        SystemBackdropConfiguration m_configurationSource;

        private IntPtr hWnd = IntPtr.Zero;
        private AppWindow appWindow = null;
        private OverlappedPresenter presenter = null;


        private AppWindow m_AppWindow;

        public MainWindow()
        {
            this.InitializeComponent();
            m_AppWindow = GetAppWindowForCurrentWindow();

            m_AppWindow.Title = "Password Generator";
            m_AppWindow.SetIcon("Assets\\key.ico");

            IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
            var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

            appWindow.Resize(new Windows.Graphics.SizeInt32 { Width = 550, Height = 350 });
            presenter = appWindow.Presenter as OverlappedPresenter;
            presenter.IsResizable = false;

            TrySetSystemBackdrop();

        }

        private AppWindow GetAppWindowForCurrentWindow()
        {
            IntPtr hWnd = WindowNative.GetWindowHandle(this);
            WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
            return AppWindow.GetFromWindowId(wndId);
        }

        bool TrySetSystemBackdrop()
        {
            if (Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported())
            {
                m_wsdqHelper = new WindowsSystemDispatcherQueueHelper();
                m_wsdqHelper.EnsureWindowsSystemDispatcherQueueController();

                m_configurationSource = new SystemBackdropConfiguration();
                this.Activated += Window_Activated;
                this.Closed += Window_Closed;
                ((FrameworkElement)this.Content).ActualThemeChanged += Window_ThemeChanged;

                m_configurationSource.IsInputActive = true;
                SetConfigurationSourceTheme();

                m_backdropController = new Microsoft.UI.Composition.SystemBackdrops.MicaController();
                m_backdropController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
                m_backdropController.SetSystemBackdropConfiguration(m_configurationSource);
                return true;
            }

            return false;
        }

        private void Window_Activated(object sender, WindowActivatedEventArgs args)
        {
            m_configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
        }

        private void Window_Closed(object sender, WindowEventArgs args)
        {
            if (m_backdropController != null)
            {
                m_backdropController.Dispose();
                m_backdropController = null;
            }
            this.Activated -= Window_Activated;
            m_configurationSource = null;
        }

        private void Window_ThemeChanged(FrameworkElement sender, object args)
        {
            if (m_configurationSource != null)
            {
                SetConfigurationSourceTheme();
            }
        }

        private void SetConfigurationSourceTheme()
        {
            switch (((FrameworkElement)this.Content).ActualTheme)
            {
                case ElementTheme.Dark: m_configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Dark; break;
                case ElementTheme.Light: m_configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Light; break;
                case ElementTheme.Default: m_configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Default; break;
            }
        }
    }

    class WindowsSystemDispatcherQueueHelper
    {
        [StructLayout(LayoutKind.Sequential)]
        struct DispatcherQueueOptions
        {
            internal int dwSize;
            internal int threadType;
            internal int apartmentType;
        }

        [DllImport("CoreMessaging.dll")]
        private static extern int CreateDispatcherQueueController([In] DispatcherQueueOptions options, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);

        object m_dispatcherQueueController = null;
        public void EnsureWindowsSystemDispatcherQueueController()
        {
            if (Windows.System.DispatcherQueue.GetForCurrentThread() != null)
            {
                return;
            }

            if (m_dispatcherQueueController == null)
            {
                DispatcherQueueOptions options;
                options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
                options.threadType = 2;
                options.apartmentType = 2;

                CreateDispatcherQueueController(options, ref m_dispatcherQueueController);
            }
        }
    }
}

I will be glad to help.

I would also be willing to hear criticism about my code if it really can be improved


Solution

  • You can see here how to customize the title bar.

    This is a simple example using the WinUI 3 way.

    MainWindow.xaml

    <Window
        x:Class="WinUI3TitleBars.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Grid
            Background="HotPink"
            RowDefinitions="32,*">
            <Grid
                x:Name="AppTitleBar"
                Grid.Row="0">
                <Image
                    Width="16"
                    Height="16"
                    Margin="5,0,0,0"
                    HorizontalAlignment="Left"
                    Source="Assets/MicrosoftLogo.png" />
                <TextBlock
                    Margin="28,0,0,0"
                    VerticalAlignment="Center"
                    Text="App Title Bar" />
            </Grid>
            <Grid Grid.Row="1">
                <TextBlock Text="Your contents go here." />
            </Grid>
        </Grid>
    
    </Window>
    

    MainWindow.xaml.cs

    using Microsoft.UI.Xaml;
    
    namespace WinUI3TitleBars;
    
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            this.SetTitleBar(this.AppTitleBar);
            this.ExtendsContentIntoTitleBar = true;
        }
    }
    

    And to avoid the brighter background of the title bar, add these SolidColorBrush definitions.

    App.xaml

    <Application
        x:Class="WinUI3TitleBars.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WinUI3TitleBars">
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
                    <!--  Other merged dictionaries here  -->
                </ResourceDictionary.MergedDictionaries>
                <!--  Other app resources here  -->
                <SolidColorBrush x:Key="WindowCaptionBackground">Transparent</SolidColorBrush>
                <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">Transparent</SolidColorBrush>
            </ResourceDictionary>
        </Application.Resources>
    </Application>