SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

WPFでFluent Design Systemのアクリルっぽいウィンドウを作ってみる

この記事は、XAML Advent Calendar 2017の1日目の記事です。

今年のbuildでは、Fluent Design Systemが発表され、Windowsの各種UIでも徐々にこういうデザインの部分が増えてきました。

このFluent Designですが、UWPからは簡単に実装することができるAPIが色々用意されています。 しかし、残念ながらWPFでこのようなデザインを実装するための手段は用意されていません。

そうは言っても、WPFでもこういうカッコいいデザインのウィンドウを作ってみたいですよね。
ということでWPFでこういうFluent Designのアクリル風なウィンドウを真似してみました。

f:id:minami_SC:20171201234528g:plain

Windowを半透明にする方法

Fluent Design Systemのアクリル効果を適用するAPIはUWP用のものしかなく、WPF向のAPIはありません。
また、Win32APIでもこのようなアクリル化を行うAPIはないので、デスクトップアプリでFluentDesignと同等のアクリル効果を出すことはできません。

完全に同じ効果を出すことはできませんが、Windowsの非公開APIでSetWindowCompositionAttributeという関数があり、このAPIを使うとウィンドウ背景をいい感じにぼかすことができます。

FluentDesingのボケ方とはちょっと違うんですが、現状WPFでそれっぽい表示をする方法はこれしか見当たりません。。。
ということで、この非公開APIを使って、アクリルっぽいウィンドウを作ってみました。

参考リンク

以下のリポジトリで行っているアクリルエフェクトを参考にやってみました。 https://github.com/bbougot/AcrylicWPF 上記リポジトリのサンプルでも、SetWindowCompositionAttribute関数を使って背景のぼかし処理を行っています。

SetWindowCompositionAttribute関数を使ったぼかし処理については↓の記事も参考になります。 https://withinrafael.com/2015/07/08/adding-the-aero-glass-blur-to-your-windows-10-apps/

サンプルコード

とりあえず、XAMLとコードビハインドをそれぞれ以下のように書いてみます。

MainWindow.xaml

WindowChromeを設定し、GlassFrameThickness="-1"と設定して、ウィンドウのクライアント領域全体を透かすようにしてます。
また、以下の画像のようなノイズ画像を用意し、ぼかし背景の全面に少しノイズを加えています。
f:id:minami_SC:20171201234854p:plain

<Window x:Class="AcrylicWindowSample.MainWindow"
        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:local="clr-namespace:AcrylicWindowSample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        BorderBrush="#FF2990CC"
        BorderThickness="1"
        mc:Ignorable="d">
    <WindowChrome.WindowChrome>
        <WindowChrome CaptionHeight="{x:Static SystemParameters.CaptionHeight}"
                      GlassFrameThickness="-1"
                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}"
                      UseAeroCaptionButtons="True" />
    </WindowChrome.WindowChrome>
    <Window.Background>
        <!-- Tiled noise texture -->
        <ImageBrush ImageSource="Images/noise.png"
                    Opacity="0.05"
                    Stretch="None"
                    TileMode="Tile"
                    Viewport="0,0,128,128"
                    ViewportUnits="Absolute" />
    </Window.Background>
    <Grid>
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   FontSize="46"
                   Foreground="CornflowerBlue"
                   Text="Acrylic Window">
            <TextBlock.Effect>
                <DropShadowEffect ShadowDepth="0" BlurRadius="35" Color="DarkGray" Opacity="0.8" />
            </TextBlock.Effect>
        </TextBlock>
    </Grid>
</Window>
MainWindow.xaml.cs

コードビハインドでは、EnableBlurという関数を作り、この関数の中で非公開APIのSetWindowCompositionAttribute関数を使ったウィンドウのぼかし処理を実装します。
また、このEnableBlurメソッドは、OnApplyTemplateのタイミングで呼び出しを行います。

namespace AcrylicWindowSample
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct WindowCompositionAttributeData
    {
        public WindowCompositionAttribute Attribute;
        public IntPtr Data;
        public int SizeOfData;
    }

    internal enum WindowCompositionAttribute
    {
        WCA_ACCENT_POLICY = 19
    }

    internal enum AccentState
    {
        ACCENT_DISABLED = 0,
        ACCENT_ENABLE_GRADIENT = 1,
        ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
        ACCENT_ENABLE_BLURBEHIND = 3,
        ACCENT_INVALID_STATE = 4
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct AccentPolicy
    {
        public AccentState AccentState;
        public int AccentFlags;
        public uint GradientColor;
        public int AnimationId;
    }

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            // ウィンドウ背景のぼかし効果を有効にする
            EnableBlur(this);
        }


        [DllImport("user32.dll")]
        internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);

        internal static void EnableBlur(Window win)
        {
            var windowHelper = new WindowInteropHelper(win);

            var accent = new AccentPolicy();
            var accentStructSize = Marshal.SizeOf(accent);
            accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
            accent.AccentFlags = 2;
            //               ↓の色はAABBGGRRの順番で設定する
            accent.GradientColor = 0x99FFFFFF;  // 60%の透明度が基本

            var accentPtr = Marshal.AllocHGlobal(accentStructSize);
            Marshal.StructureToPtr(accent, accentPtr, false);

            var data = new WindowCompositionAttributeData();
            data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
            data.SizeOfData = accentStructSize;
            data.Data = accentPtr;

            SetWindowCompositionAttribute(windowHelper.Handle, ref data);

            Marshal.FreeHGlobal(accentPtr);
        }

    }
}

実行結果

こんな風に、ちょっとFluentDesignっぽいアクリル風なウィンドウができました。
f:id:minami_SC:20171201234621p:plain

ここで、以下の accent.GradientColorの値を変えると、ウィンドウ背景の色味を調整することができます。

            //               ↓の色はAABBGGRRの順番で設定する
            accent.GradientColor = 0x99FFFFFF;  // 60%の透明度が基本

accent.GradientColor = 0xAA222222;とするとこんな感じの表示になります。 f:id:minami_SC:20171201234639p:plain

サンプルコード一式は、GitHubの以下のリポジトリに置いておきました。 github.com

非公開APIを使った処理なので、あまり積極的に使うべき方法ではないとは思います。
ですが、現状のWPFでアクリルっぽいウィンドウを作るには、こんな方法しかないのかな。。。

今後、WPFの更新とかで、こういう用途のAPIが拡充されるといいなぁ。。。