WinUI3の背景アクリルの色調整をする

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

WinUI3のアクリルを使って、半透明のぼかしの入ったウインドウを作りたいのだが、半透明に少し色を入れた、例えば黄色がかったアクリル板のような色にしたいときに、どこの値をどうしたら思った色になってくれるのかを試したい。

WinUI3 Gallleryの中で動きを試すことができる「System Backdrops」の「Acrylic system backdrop」を参考に、 色々、色に関係する設定値を変えれるように、サンプルを作りたい。

参考にしたWinUI3ギャラリー

サンプルコード

今回、手軽に動かせるように、パッケージしないWinUI3のexeを直に実行して起動できるツールを作りたい。

プロジェクトファイル

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
      <OutputType>WinExe</OutputType>
      <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
      <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
      <RootNamespace>WinUI3Blur</RootNamespace>
      <ApplicationManifest>app.manifest</ApplicationManifest>
      <Platforms>x64</Platforms>
      <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
      <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
      <UseWinUI>true</UseWinUI>
      <WindowsPackageType>None</WindowsPackageType>
      <SelfContained>true</SelfContained>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.2" />
    <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
    <Manifest Include="$(ApplicationManifest)" />
  </ItemGroup>
</Project>

App.xaml.cs

using Microsoft.UI.Xaml;

namespace WinUI3Blur
{
    public partial class App : Application
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
        {
            var newWindow = new AppUIBasics.SamplePages.SampleSystemBackdropsWindow();
            newWindow.SetBackdrop(AppUIBasics.SamplePages.SampleSystemBackdropsWindow.BackdropType.DesktopAcrylic);
            newWindow.Activate();
        }
    }
}
<Window
    x:Class="AppUIBasics.SamplePages.SampleSystemBackdropsWindow"
    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">

    <StackPanel>
        <StackPanel Margin="20" Spacing="10">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Current backdrop: " />
                <TextBlock x:Name="tbCurrentBackdrop" />
            </StackPanel>
            <Button x:Name="btnChangeBackdrop" AutomationProperties.Name="ChangeBackdropButton" Content="Change Backdrop" Click="ChangeBackdropButton_Click" Visibility="Collapsed"/>
            <TextBlock x:Name="tbChangeStatus" />
            <StackPanel>
                <TextBlock Width="400" Text="TintColor A : "/>
                <Slider Name="SliderTintA" Width="400" ValueChanged="SliderTintA_ValueChanged" Maximum="255" Minimum="0" StepFrequency="10"/>
            </StackPanel>
            <StackPanel>
                <TextBlock Width="400" Text="TintColor R : "/>
                <Slider Name="SliderTintR" Width="400" ValueChanged="SliderTintR_ValueChanged"  Maximum="255" Minimum="0" StepFrequency="10"/>
            </StackPanel>
            <StackPanel>
                <TextBlock Width="400" Text="TintColor G : "/>
                <Slider Name="SliderTintG" Width="400" ValueChanged="SliderTintG_ValueChanged"  Maximum="255" Minimum="0" StepFrequency="10"/>
            </StackPanel>
            <StackPanel>
                <TextBlock Width="400" Text="TintColor B : "/>
                <Slider Name="SliderTintB" Width="400" ValueChanged="SliderTintB_ValueChanged"  Maximum="255" Minimum="0" StepFrequency="10"/>
            </StackPanel>
            <StackPanel>
                <TextBlock Width="400" Text="TintOpacity : (色をいじらないと反映されない)"/>
                <Slider Name="SliderTintOpacity" Width="400" ValueChanged="SliderTintOpacity_ValueChanged"  Maximum="1.0" Minimum="0.0" StepFrequency="0.05"/>
            </StackPanel>
            <StackPanel>
                <TextBlock Width="400" Text="LuminousityOpacity : "/>
                <Slider Name="LuminousityOpacity" Width="400" ValueChanged="LuminousityOpacity_ValueChanged"  Maximum="1.0" Minimum="0.0" StepFrequency="0.05"/>
            </StackPanel>
        </StackPanel>
    </StackPanel>
</Window>

半透明のぼかし表示をするウインドウコード。
ほぼWinUI3ギャラリーのコードのままで、設定値をいじれるスライダを付けただけ。

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

namespace AppUIBasics.SamplePages
{
    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);
            }
        }
    }

    public sealed partial class SampleSystemBackdropsWindow : Window
    {
        public SampleSystemBackdropsWindow()
        {
            this.InitializeComponent();
            //((FrameworkElement)this.Content).RequestedTheme = AppUIBasics.Helper.ThemeHelper.RootTheme;

            m_wsdqHelper = new WindowsSystemDispatcherQueueHelper();
            m_wsdqHelper.EnsureWindowsSystemDispatcherQueueController();

            SetBackdrop(BackdropType.Mica);
        }

        public enum BackdropType
        {
            Mica,
            DesktopAcrylic,
            DefaultColor,
        }

        WindowsSystemDispatcherQueueHelper m_wsdqHelper;
        BackdropType m_currentBackdrop;
        Microsoft.UI.Composition.SystemBackdrops.MicaController m_micaController;
        Microsoft.UI.Composition.SystemBackdrops.DesktopAcrylicController m_acrylicController;
        Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration m_configurationSource;

        public void SetBackdrop(BackdropType type)
        {
            m_currentBackdrop = BackdropType.DefaultColor;
            tbCurrentBackdrop.Text = "None (default theme color)";
            tbChangeStatus.Text = "";
            if (m_micaController != null)
            {
                m_micaController.Dispose();
                m_micaController = null;
            }
            if (m_acrylicController != null)
            {
                m_acrylicController.Dispose();
                m_acrylicController = null;
            }
            this.Activated -= Window_Activated;
            this.Closed -= Window_Closed;
            ((FrameworkElement)this.Content).ActualThemeChanged -= Window_ThemeChanged;
            m_configurationSource = null;

            if (type == BackdropType.Mica)
            {
                if (TrySetMicaBackdrop())
                {
                    tbCurrentBackdrop.Text = "Mica";
                    m_currentBackdrop = type;
                }
                else
                {
                    // Mica isn't supported. Try Acrylic.
                    type = BackdropType.DesktopAcrylic;
                    tbChangeStatus.Text += "  Mica isn't supported. Trying Acrylic.";
                }
            }
            if (type == BackdropType.DesktopAcrylic)
            {
                if (TrySetAcrylicBackdrop())
                {
                    tbCurrentBackdrop.Text = "Acrylic";
                    m_currentBackdrop = type;
                }
                else
                {
                    // Acrylic isn't supported, so take the next option, which is DefaultColor, which is already set.
                    tbChangeStatus.Text += "  Acrylic isn't supported. Switching to default color.";
                }
            }
        }

        bool TrySetMicaBackdrop()
        {
            if (Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported())
            {
                // Hooking up the policy object
                m_configurationSource = new Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration();
                this.Activated += Window_Activated;
                this.Closed += Window_Closed;
                ((FrameworkElement)this.Content).ActualThemeChanged += Window_ThemeChanged;
                
                // Initial configuration state.
                m_configurationSource.IsInputActive = true;
                SetConfigurationSourceTheme();

                m_micaController = new Microsoft.UI.Composition.SystemBackdrops.MicaController();

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

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

        bool TrySetAcrylicBackdrop()
        {
            if (Microsoft.UI.Composition.SystemBackdrops.DesktopAcrylicController.IsSupported())
            {
                // Hooking up the policy object
                m_configurationSource = new Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration();
                //this.Activated += Window_Activated;
                this.Closed += Window_Closed;
                ((FrameworkElement)this.Content).ActualThemeChanged += Window_ThemeChanged;

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

                m_acrylicController = new Microsoft.UI.Composition.SystemBackdrops.DesktopAcrylicController();
                m_acrylicController.TintColor = new Windows.UI.Color() { A = tintA, R = tintR, G = tintG, B = tintB };
                m_acrylicController.TintOpacity = tintOpacity;
                m_acrylicController.LuminosityOpacity = luminousityOpacity;
                // Enable the system backdrop.
                // Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
                m_acrylicController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
                m_acrylicController.SetSystemBackdropConfiguration(m_configurationSource);
                return true; // succeeded
            }

            return false; // Acrylic 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_micaController != null)
            {
                m_micaController.Dispose();
                m_micaController = null;
            }
            if (m_acrylicController != null)
            {
                m_acrylicController.Dispose();
                m_acrylicController = 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;
            }
        }

        void ChangeBackdropButton_Click(object sender, RoutedEventArgs e)
        {
            BackdropType newType;
            switch (m_currentBackdrop)
            {
                case BackdropType.Mica: newType = BackdropType.DesktopAcrylic; break;
                case BackdropType.DesktopAcrylic: newType = BackdropType.DefaultColor; break;
                default:
                case BackdropType.DefaultColor: newType = BackdropType.Mica; break;
            }
            SetBackdrop(newType);
        }

        byte tintA = 0;
        byte tintR = 0;
        byte tintG = 0;
        byte tintB = 0;
        float tintOpacity = 0.0f;
        float luminousityOpacity = 0.0f;

        private void SetTintColorARGB()
        {
            m_acrylicController.TintColor = new Windows.UI.Color() { A = tintA, R = tintR, G = tintG, B = tintB };
            m_acrylicController.TintOpacity = tintOpacity;
            m_acrylicController.LuminosityOpacity = luminousityOpacity;

            m_acrylicController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
            m_acrylicController.SetSystemBackdropConfiguration(m_configurationSource);

            Debug.WriteLine($"{tintA}, {tintR}, {tintG}, {tintB}, {tintOpacity}, {luminousityOpacity}");
        }

        private void SliderTintA_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            tintA = (byte)e.NewValue;
            SetTintColorARGB();
        }

        private void SliderTintR_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            tintR = (byte)e.NewValue;
            SetTintColorARGB();
        }

        private void SliderTintG_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            tintG = (byte)e.NewValue;
            SetTintColorARGB();
        }

        private void SliderTintB_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            tintB = (byte)e.NewValue;
            SetTintColorARGB();
        }

        private void SliderTintOpacity_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            tintOpacity = (float)e.NewValue;
            SetTintColorARGB();
        }

        private void LuminousityOpacity_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            luminousityOpacity = (float)e.NewValue;
            SetTintColorARGB();
        }
    }
}

動かしたところ

黄色めにぼかしたところ

地味にはまったところ

・「透明効果」の設定をONにしておかないと、半透明にならない。  そのときは、m_acrylicController.FallbackColorにセットした色になる。

参考

WinUI3 ギャラリーのコード

https://github.com/microsoft/WinUI-Gallery

アクリル背景部分のコード

https://github.com/microsoft/WinUI-Gallery/blob/main/WinUIGallery/SamplePages/SampleSystemBackdropsWindow.xaml.cs