Mica (マイカ) を利用したウィンドウ背景を実装する - WinUI

WinUI 3アプリケーションでMica (マイカ) を利用したウィンドウ背景を実装するコードを紹介します。

概要

Windows 11のUWPアプリケーションなどで、背景の壁紙の色がウィンドウ背景に適用されたアプリケーションを見かけることがあります。 この機能は、Mica(マイカ)と呼ばれる機能でOSでサポートされています。
この記事では、WinUI 3アプリケーションで Micaの機能を諒してウィンドウ背景を設定するコードを紹介します。

プログラム例

WinUI 3アプリケーションを作成します。

コード

以下のコードを記述します。
MainWindow.xaml
<Window
    x:Class="MicaDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MicaDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
  
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    </StackPanel>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using WinRT;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace MicaDemo
{
  /// <summary>
  /// An empty window that can be used on its own or navigated to within a Frame.
  /// </summary>
  public sealed partial class MainWindow : Window
  {
    WindowsSystemDispatcherQueueHelper wsdqHelper;
    Microsoft.UI.Composition.SystemBackdrops.MicaController micaController;
    Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration configurationSource;

    public MainWindow()
    {
      this.InitializeComponent();
      wsdqHelper = new WindowsSystemDispatcherQueueHelper();
      wsdqHelper.EnsureWindowsSystemDispatcherQueueController();
      SetBackdrop();
    }

    private void myButton_Click(object sender, RoutedEventArgs e)
    {
      myButton.Content = "Clicked";
    }

    public void SetBackdrop()
    {
      if (micaController != null) {
        micaController.Dispose();
        micaController = null;
      }
      configurationSource = null;

      TrySetMicaBackdrop();
    }

    private bool TrySetMicaBackdrop()
    {
      if (Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported()) {
        configurationSource = new Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration();

        configurationSource.IsInputActive = true;
        switch (((FrameworkElement)this.Content).ActualTheme) {
          case ElementTheme.Dark: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Dark; break;
          case ElementTheme.Light: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Light; break;
          case ElementTheme.Default: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Default; break;
        }

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

        micaController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
        micaController.SetSystemBackdropConfiguration(configurationSource);
        return true;
      }
      return false;
    }
  }
}
WindowsSystemDispatcherQueueHelper.cs
using System.Runtime.InteropServices;

namespace MicaDemo
{
  internal 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 (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);
      }
    }
  }
}

解説

WindowsSystemDispatcherQueueHelper.cs

Micaを実装するにあたり、WindowsSystemDispatcherQueueHelperが必要です。厳密にはクラスである必要はありませんが、 CreateDispatcherQueueController()関数を呼び出す必要があります。

このメソッドを呼び出さないと、MicaControllerオブジェクトの、AddSystemBackdropTarget() メソッドを呼び出したタイミングで、 System.UnauthorizedAccessException例外が発生してしまいます。
エラーメッセージ
System.UnauthorizedAccessException: 'アクセスが拒否されました。 (0x80070005 (E_ACCESSDENIED))'

MainWindow.xaml

プロジェクトの作成時に、ひな形で生成されるXAMLから変更はありません。

MainWindow.xaml.cs

コンストラクタの InitializeComponent呼び出しの後に、WindowsSystemDispatcherQueueHelperクラスの作成と、 CreateDispatcherQueueController() 関数を呼び出すための EnsureWindowsSystemDispatcherQueueController メソッドの呼び出しを実行します。
呼び出し後に、SetBackdropメソッドを呼び出し、MicaControllerの設定をします。
  this.InitializeComponent();

  wsdqHelper = new WindowsSystemDispatcherQueueHelper();
  wsdqHelper.EnsureWindowsSystemDispatcherQueueController();
  SetBackdrop();

SetBackdropメソッドでは、MicaControllerのインスタンスが作成されているか確認し、インスタンスが作成されていれば、 Disposeして開放します。その後、TrySetMicaBackdrop メソッドを呼び出し、MicaControllerの設定をします。
    public void SetBackdrop()
    {
      if (micaController != null) {
        micaController.Dispose();
        micaController = null;
      }
      configurationSource = null;

      TrySetMicaBackdrop();
    }

TrySetMicaBackdrop メソッドがMicaControllerオブジェクトを作成し、設定をする処理です。
実行環境で、Micaが有効かのチェックを Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported() でチェックし、 trueである場合にのみ処理を実行します。
MicaControllerに渡すMicaの設定(Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration)をMicrosoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration() メソッドで作成し、 その後、現在のカラーテーマに合わせて、SystemBackdropConfigurationオブジェクトのThemeプロパティを設定します。

作成したMicaControllerに、 AddSystemBackdropTargetメソッドで、このウィンドウをターゲットとするよう指定し、 SetSystemBackdropConfigurationメソッドでSystemBackdropConfigurationを設定します。

    private bool TrySetMicaBackdrop()
    {
      if (Microsoft.UI.Composition.SystemBackdrops.MicaController.IsSupported()) {
        configurationSource = new Microsoft.UI.Composition.SystemBackdrops.SystemBackdropConfiguration();

        configurationSource.IsInputActive = true;
        switch (((FrameworkElement)this.Content).ActualTheme) {
          case ElementTheme.Dark: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Dark; break;
          case ElementTheme.Light: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Light; break;
          case ElementTheme.Default: configurationSource.Theme = Microsoft.UI.Composition.SystemBackdrops.SystemBackdropTheme.Default; break;
        }

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

        micaController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
        micaController.SetSystemBackdropConfiguration(configurationSource);
        return true;
      }
      return false;
    }

実行結果

ダークカラーのモードでプロジェクトを実行します。下図のウィンドウが表示されます。
背景の壁紙の青色がウィンドウカラーに反映されていることが確認できます。


ウィンドウをドラッグして壁紙の暗い場所に移動すると、ウィンドウの背景色の青みがなくなります。


色の変化の大きい部分に移動すると、左側に青みがあり、右側に青みがない状態になります。


壁紙を変えた結果です。壁紙の色がウィンドウの背景色に少しだけ反映されている様子がわかります。~\



ライトカラーの場合です。
背景色に若干青みがかかっています。




壁紙を変えた結果です。壁紙の色がウィンドウの背景色に反映されています。




Micaを利用してウィンドウの背景を設定できました。

次の作業


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2022-08-29
作成日: 2022-08-13
iPentec all rights reserverd.