Skip to content

WinUI3应用Mica或Acrylic材料

不管怎么说,我用于学习WinUI3的项目WSAFileLink已经告一段落了。接下来的几篇WinUI相关博文主要是记录这当中的一些实现。

虽然我依旧觉得Microsoft Doc写的并不算好,但是看习惯了就还好。至于WinUI3本身,虽然和之前比好很多,但仍然不算成熟,社区里也经常能看到弃坑为结尾的issue。

两种材料的简介

Mica和Acrylic是Windows11中新提出的两个表面视觉效果,类似于当年的Aero。

Mica - 云母

Mica是一种不透明的材料,它结合用户的主题和壁纸,以创建高度个性化的外观。

Mica专为性能设计,因此它只捕获一次背景墙纸来创建其可视化效果。因此微软官方建议Mica用于应用的基础层,尤其是标题栏区域。

Acrylic - 亚克力

Acrylic是一种半透明材料,类似亚克力玻璃的效果。

Acrylic的效果更加绚丽,它会捕获应用下面所有的画面来创建其可视化效果。因为性能代价,微软官方它仅用于浮出控件和上下文菜单等暂时性的轻型消除图面。

如何在背景上应用材料

重要 APIMicaController类DesktopAcrylicController类SystemBackdropConfiguration类

想要在应用的背景上使用这些材料,要使用实现ISystemBackdropController接口的控制器,即:MicaControllerDesktopAcrylicController。这些类管理系统背景材料的呈现以及材料系统策略的处理。

如果你想用Mica作为背景材料,那么你需要创建MicaController对象。相应的,如果要使用Acrylic,那么要创建DesktopAcrylicController对象。

以创建Mica为例:

csharp
MicaController m_backdropController;

bool TrySetSystemBackdrop()
{
    if (MicaController.IsSupported())
    {
        ...
        m_backdropController = new MicaController();
        ...
    }
}

若要使用Mica的Mica Alt变体,请创建一个MicaController对象并将Kind属性设置为MicaKind.BaseAlt

csharp
MicaController m_backdropController;

bool TrySetSystemBackdrop()
{
    if (MicaController.IsSupported())
    {
        ...
        m_backdropController = new MicaController()
        {
            Kind = MicaKind.BaseAlt
        };
        ...
    }
}

如果想要Acrylic材料,那么:

csharp
DesktopAcrylicController m_backdropController;

bool TrySetSystemBackdrop()
{
    if (DesktopAcrylicController.IsSupported())
    {
        ...
        m_backdropController = new DesktopAcrylicController();
        ...
    }
}

默认情况下,控制器会响应系统浅色和深色主题。 若要替代此行为,可以在控制器上设置以下属性:

  • FallbackColor
  • LuminosityOpacity
  • TintColor
  • TintOpacity

注意事项

系统支持

应用运行的系统必须支持背景材料。 调用MicaController.IsSupportedDesktopAcrylicController.IsSupported方法,以确保在运行时支持背景材料。

有效目标

必须提供实现ICompositionSupportsSystemBackdrop接口的目标。 在XAML应用中,XAML窗口实现此接口,并用作背景目标。

SystemBackdropConfiguration对象

SystemBackdropConfiguration为系统背景控制器提供特定于应用的策略信息,以正确配置系统背景材料。

DispatcherQueue对象。

主XAML线程上需要一个可用的Windows.System.DispatcherQueueWindowsSystemDispatcherQueueHelper请参阅示例代码中的类,或在WinUI3库示例中。

官方示例:在 Windows AppSDK/WinUI 3 应用中使用 Mica

对这个示例加上一个简单的判断条件,就能根据设定来切换背景材料。

csharp
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using System.Runtime.InteropServices; // For DllImport
using WinRT; // required to support Window.As<ICompositionSupportsSystemBackdrop>()

public sealed partial class MainWindow : Window
{
    WindowsSystemDispatcherQueueHelper m_wsdqHelper; // See below for implementation.
    MicaController m_backdropController;
    SystemBackdropConfiguration m_configurationSource;

    public MainWindow()
    {
        this.InitializeComponent();

        TrySetSystemBackdrop();
    }

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

            // Create the policy object.
            m_configurationSource = new SystemBackdropConfiguration();
            this.Activated += Window_Activated;
            this.Closed += Window_Closed;
            ((FrameworkElement)this.Content).ActualThemeChanged += Window_ThemeChanged;

            // Initial configuration state.
            m_configurationSource.IsInputActive = true;
            SetConfigurationSourceTheme();

            m_backdropController = new Microsoft.UI.Composition.SystemBackdrops.MicaController()

            // Enable the system backdrop.
            // Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
            m_backdropController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
            m_backdropController.SetSystemBackdropConfiguration(m_configurationSource);
            return true; // succeeded
        }

        return false; // Mica is not supported on this system
    }

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

    private void Window_Closed(object sender, WindowEventArgs args)
        {
            // Make sure any Mica/Acrylic controller is disposed
            // so it doesn't try to use this closed window.
            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)
        {
            // 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);
        }
    }
}

参考

1、将 SystemBackdropController 与 WinUI 3 XAML 配合使用 - Windows apps | Microsoft Learn