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

参考書

WinUI3

WinUI3でアプリを作ろうと思ったときのとっかかりによかった。 msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。

C#①

表紙に書いてある通り、教科書として最適。 これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、 ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。

C#②

C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。