Mica or Acrylic with WinUI and .NET MAUI

When using .NET MAUI, we have some great capabilities to affect the way the native platform works. We can take advantage of this part of lifecycle to check if:

  • Is the runtime platform is Windows?
  • First attempt to use Mica, if the user’s device supports it (Windows 11).
  • If it doesn’t support Mica, then we try to use Acrylic.

Here’s what the end result looks like in your MauiProgram.cs code, nice and clean thanks to using lifecycle builder extension methods.

builder.ConfigureLifecycleEvents(events =>
{
#if WINDOWS10_0_17763_0_OR_GREATER
    
    events.AddWindows(wndLifeCycleBuilder =>
    {
        wndLifeCycleBuilder.OnWindowCreated(window =>
        {
            // *** For Mica or Acrylic support ** //
            window.TryMicaOrAcrylic();
        });
    });
#endif
});

To set this up, open the folder for the WinUI project (Platforms/Windows). You will see the standard Windows-specific files in there, like App.xaml. Add two new classes; WindowsHelpers.cs and WindowsSystemDispatcherQueueHelpers.cs., it should look like this:

Let’s first work on the dispatcher queue helper class, because the window helper class depends on it. Use the following code and save the file (don’t forget to fix the namespace so that it matches your project):

using System.Runtime.InteropServices;
using Windows.System; // For DllImport
using WinRT; // required to support Window.As<ICompositionSupportsSystemBackdrop>()

namespace YourProjectName.Platforms.Windows;

public 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 (DispatcherQueue.GetForCurrentThread() != null)
        {
            // one already exists, so we'll just use it.
            return;
        }

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

            CreateDispatcherQueueController(options, ref m_dispatcherQueueController);
        }
    }
}

Next, open the window helpers class and use this code:

using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using WinRT;

namespace YourProjectName.Platforms.Windows
{
    public static class WindowHelpers
    {
        public static void TryMicaOrAcrylic(this Microsoft.UI.Xaml.Window window)
        {
            var dispatcherQueueHelper = new WindowsSystemDispatcherQueueHelper(); // in Platforms.Windows folder
            dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController();

            // Hooking up the policy object
            var configurationSource = new SystemBackdropConfiguration();
            configurationSource.IsInputActive = true;

            switch (((FrameworkElement)window.Content).ActualTheme)
            {
                case ElementTheme.Dark:
                    configurationSource.Theme = SystemBackdropTheme.Dark; 
                    break;
                case ElementTheme.Light:
                    configurationSource.Theme = SystemBackdropTheme.Light;
                    break;
                case ElementTheme.Default:
                    configurationSource.Theme = SystemBackdropTheme.Default;
                    break;
            }
            
            // Let's try Mica first
            if (MicaController.IsSupported()) 
            {
                var micaController = new MicaController();
                micaController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
                micaController.SetSystemBackdropConfiguration(configurationSource);
                
                window.Activated += (object sender, WindowActivatedEventArgs args) =>
                {
                    if (args.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated)
                    {
                        // Handle situation where a window is activated and placed on top of other active windows.
                        if (micaController == null)
                        {
                            micaController = new MicaController();
                            micaController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
                            micaController.SetSystemBackdropConfiguration(configurationSource);
                        }

                        if (configurationSource != null)
                            configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
                    }
                };

                window.Closed += (object sender, WindowEventArgs args) =>
                {
                    if (micaController != null)
                    {
                        micaController.Dispose();
                        micaController = null;
                    }

                    configurationSource = null;
                };
            }
            // If no Mica, maybe we can use Acrylic instead
            else if (DesktopAcrylicController.IsSupported())
            {
                var acrylicController = new DesktopAcrylicController();
                acrylicController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
                acrylicController.SetSystemBackdropConfiguration(configurationSource);

                window.Activated += (object sender, WindowActivatedEventArgs args) =>
                {
                    if (args.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated)
                    {
                        // Handle situation where a window is activated and placed on top of other active windows.
                        if (acrylicController == null)
                        {
                            acrylicController = new DesktopAcrylicController();
                            acrylicController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
                            acrylicController.SetSystemBackdropConfiguration(configurationSource);
                        }
                    }

                    if (configurationSource != null)
                        configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
                };

                window.Closed += (object sender, WindowEventArgs args) =>
                {
                    if (acrylicController != null)
                    {
                        acrylicController.Dispose();
                        acrylicController = null;
                    }

                    configurationSource = null;
                };
            }
        }
    }
}

With both of them done, you can now use it in your MauiProgram.cs CreateMauiApp method.

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");
            })
            .RegisterLifecycleEvents();

        builder.ConfigureLifecycleEvents(events =>
        {
#if WINDOWS10_0_17763_0_OR_GREATER

            events.AddWindows(wndLifeCycleBuilder =>
            {
                wndLifeCycleBuilder.OnWindowCreated(window =>
                {
                    // *** For Mica or Acrylic support ** //
                    window.TryMicaOrAcrylic();
                });
            });
        });
#endif
        return builder.Build();
    }
}

Your application now has automatic support for Mica or Acrylic!

Tip: If you don’t see it at first, make sure you do not have another UI element covering the window’s backdrop. It’s not uncommon that a default style will set the background color of a page or root element. This will have a higher Z index and cover the window’s natural backdrop.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.