WPF で オーバーレイ表示をする

# 概要
画面上に情報を被せて表示する必要があったので、色々調べた。

最終目標として、以下のような表示を目指した。
![art](https://i.gyazo.com/f038e709b279d87cb1ccaa7f1ab7d12d.png)

# 開発・検証環境
- Microsoft Visual Studio 2017 
- .Net Framework 4.7
- Windows 10 Pro [1709] Build:16299.192

Frameworkは、4.7を使用していますが以前のバージョンでも動作するとは思います。

# 要件
1. 透過背景
2. 全画面表示
3. 最前面表示
4. クリック・スルー
5. タスクバーに表示しない
6. システムメニュー非表示
7. Alt+F4で閉じない

## 1. 透過背景

Window のPropertyを以下の様に設定することで、透過することができます。

- ` WindowStyle = WindowStyle.None `
- ` AllowsTransparency = true `
- ` Background = new SolidColorBrush( Colors.Transparent ) `

Xaml で書くとこんな感じ

```xml:OverlayWindow.xaml
<Window x:Class="hogehoge.Views.OverlayWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="OverlayWindow"
        WindowStyle="None"
        AllowsTransparency="True"
        Background="Transparent">
    <Grid>
        
    </Grid>
</Window>
```

## 2. 全画面表示

Window のPropertyを以下の様に設定することで、全画面表示にできます。

- ` WindowState = WindowState.Maximized `

Xaml で書くとこんな感じ

``` xml:OverlayWindow.xaml
<Window x:Class="hogehoge.Views.OverlayWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="OverlayWindow"
        WindowState="Maximized">
    <Grid>
        
    </Grid>
</Window>
```

## 3. 最前面表示

Window のPropertyを以下の様に設定することで、最前面固定表示にできます。

- ` Topmost = true `


Xaml で書くとこんな感じ

``` xml:OverlayWindow.xaml
<Window x:Class="hogehoge.Views.OverlayWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="OverlayWindow"
        Topmost="True">
    <Grid>
        
    </Grid>
</Window>
```

## 4. クリック・スルー

Windowのクリックをスルーするには、拡張Windowスタイルを変更する必要があります。
拡張Windowスタイルの取得と設定に P/Invoke を使います。
ひとまず、`OverlayWindow`の分離コードに記述していきます。

``` c#:OverlayWindow.xaml.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace hogehoge.Views {
    /// <summary>
    /// OverlayWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class OverlayWindow : Window {

        protected const int GWL_EXSTYLE = ( -20 );
        protected const int WS_EX_TRANSPARENT = 0x00000020;

        [DllImport( "user32" )]
        protected static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport( "user32" )]
        protected static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwLong);

        public OverlayWindow() {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e) {

            base.OnSourceInitialized( e );

            //WindowHandle(Win32) を取得
            var handle = new WindowInteropHelper( this ).Handle;

            //クリックをスルー
            int extendStyle = GetWindowLong( handle, GWL_EXSTYLE );
            extendStyle |= WS_EX_TRANSPARENT; //フラグの追加
            SetWindowLong( handle, GWL_EXSTYLE, extendStyle );

        }
    }
}
```

演算子に関しては以下を参照
> - [|= 演算子 (C# リファレンス)]
(https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/or-assignment-operator)

さて、ここまですればオーバーレイ用のWindowとして使用できます。
ですが、Alt+Space でシステムメニューが開けてしまったりと、残念なポイントがあるので修正していきます。

## 5. タスクバーに表示しない

Window のPropertyを以下の様に設定することで、タスクバーに表示されないようにできます。

- ` ShowInTaskbar = false `


Xaml で書くとこんな感じ

``` xml:OverlayWindow.xaml
<Window x:Class="hogehoge.Views.OverlayWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="OverlayWindow"
        ShowInTaskbar="False">
    <Grid>
        
    </Grid>
</Window>
```

## 6. システムメニュー非表示

システムメニューが表示されないようにするには、 Windowスタイルを変更する必要があります。
`Window`クラスの`WindowStyle`プロパティではなく、NativeなWindowスタイルの設定です。

なので、これまた P/Invoke で対応していきます。

``` c#:OverlayWindow.xaml.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace hogehoge.Views {
    /// <summary>
    /// OverlayWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class OverlayWindow : Window {

        protected const int GWL_STYLE = ( -16 );
        protected const int WS_SYSMENU = 0x00080000;

        [DllImport( "user32" )]
        protected static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport( "user32" )]
        protected static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwLong);

        public OverlayWindow() {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e) {

            base.OnSourceInitialized( e );

            //WindowHandle(Win32) を取得
            var handle = new WindowInteropHelper( this ).Handle;

            //システムメニュを非表示
            int windowStyle = GetWindowLong( handle, GWL_STYLE );
            windowStyle &= ~WS_SYSMENU; //フラグを消す
            SetWindowLong( handle, GWL_STYLE, windowStyle );

        }
    }
}
```

演算子に関しては以下を参照
> - [&= 演算子 (C# リファレンス)]
(https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/and-assignment-operator)
> - [~ 演算子 (C# リファレンス)]
(https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/bitwise-complement-operator)

## 7. Alt+F4で閉じない

Windowプロシージャをフックして、Alt+F4が入力された時に処理済みとしてマークします。

``` C#:OverlayWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Interop;

namespace hogehoge.Views {
    /// <summary>
    /// OverlayWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class OverlayWindow : Window {

        protected const int WM_SYSKEYDOWN = 0x0104;
        protected const int VK_F4 = 0x73;

        public OverlayWindow() {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e) {

            base.OnSourceInitialized( e );

            //WindowHandle(Win32) を取得
            var handle = new WindowInteropHelper( this ).Handle;

            //Alt + F4 を無効化
            var hwndSource = HwndSource.FromHwnd( handle );
            hwndSource.AddHook( WndProc );

        }

        protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr IParam, ref bool handled) {

            //Alt + F4 が入力されたら
            if ( msg == WM_SYSKEYDOWN && wParam.ToInt32() == VK_F4 ) {

                //処理済みにセットする
                //(Windowは閉じられなくなる)
                handled = true;

            }

            return IntPtr.Zero;

        }
    }
}

```

# ~~ビヘイビアにまとめる~~

上で紹介したコード片をまとめて、ビヘイビアにします。

``` c#:OverlayWindowSettingBehavior.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.Windows.Media;

namespace hogehoge.Behaviors {

    public class OverlayWindowSettingBehavior : Behavior<Window> {

        public const int GWL_STYLE = ( -16 ); // ウィンドウスタイル
        public const int GWL_EXSTYLE = ( -20 ); // 拡張ウィンドウスタイル

        public const int WS_SYSMENU = 0x00080000; // システムメニュを表示する
        public const int WS_EX_TRANSPARENT = 0x00000020; // 透過ウィンドウスタイル

        public const int WM_SYSKEYDOWN = 0x0104; // Alt + 任意のキー の入力

        public const int VK_F4 = 0x73; 

        [DllImport( "user32" )]
        protected static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport( "user32" )]
        protected static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwLong);

        protected override void OnAttached() {

            base.OnAttached();

            // 透過背景
            this.AssociatedObject.WindowStyle = WindowStyle.None;
            this.AssociatedObject.AllowsTransparency = true;
            this.AssociatedObject.Background = new SolidColorBrush( Colors.Transparent );

            // 全画面表示
            this.AssociatedObject.WindowState = WindowState.Maximized;

            // 最前面表示
            this.AssociatedObject.Topmost = true;

            //タスクバーに表示しない
            this.AssociatedObject.ShowInTaskbar = false; 

            this.AssociatedObject.SourceInitialized += (sender, eventArgs) => {

                //WindowHandle(Win32) を取得
                var handle = new WindowInteropHelper( this.AssociatedObject ).Handle;

                //システムメニュを非表示
                int windowStyle = GetWindowLong( handle, GWL_STYLE );
                windowStyle &= ~WS_SYSMENU; //フラグを消す
                SetWindowLong( handle, GWL_STYLE, windowStyle );

                //クリックをスルー
                int extendStyle = GetWindowLong( handle, GWL_EXSTYLE );
                extendStyle |= WS_EX_TRANSPARENT; //フラグの追加
                SetWindowLong( handle, GWL_EXSTYLE, extendStyle );

                //Alt + F4 を無効化
                var hwndSource = HwndSource.FromHwnd( handle );
                hwndSource.AddHook( WndProc );

            };
            
        }

        protected IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr IParam, ref bool handled ) {

            //Alt + F4 が入力されたら
            if ( msg == WM_SYSKEYDOWN && wParam.ToInt32() == VK_F4 ) {

                //処理済みにセットする
                //(Windowは閉じられなくなる)
                handled = true;

            }

            return IntPtr.Zero;

        }

    }

}

```

これを、Windowに適用します。

``` xml:OverlayWindow.xaml
<Window x:Class="hogehoge.Views.OverlayWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:behavior="clr-namespace:hogehoge.Behaviors"
        Title="OverlayWindow">
    <i:Interaction.Behaviors>
        <behavior:OverlayWindowSettingBehavior/>
    </i:Interaction.Behaviors>
    <Grid>
        
    </Grid>
</Window>
```

# ビヘイビアにまとめない

ビヘイビアにするより、継承元のクラスとした方が使い勝手が良かった(^^;)

``` c#:OverlayWindow.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

namespace hogehoge
{
    public class OverlayWindow : Window
    {

        #region DependencyProperties

        #region AltF4Cancel

        public bool AltF4Cancel {
            get { return (bool)GetValue(AltF4CancelProperty); }
            set { SetValue(AltF4CancelProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AltF4Cancel.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AltF4CancelProperty =
            DependencyProperty.Register(
                "AltF4Cancel",
                typeof(bool),
                typeof(OverlayWindow),
                new PropertyMetadata(true));

        #endregion

        #region ShowSystemMenu

        public bool ShowSystemMenu {
            get { return (bool)GetValue(ShowSystemMenuProperty); }
            set { SetValue(ShowSystemMenuProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShowSystemMenu.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowSystemMenuProperty =
            DependencyProperty.Register(
                "ShowSystemMenu",
                typeof(bool),
                typeof(OverlayWindow),
                new PropertyMetadata(
                    false,
                    ShowSystemMenuPropertyChanged));

        private static void ShowSystemMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            if (d is OverlayWindow window)
            {

                window.SetShowSystemMenu( (bool)e.NewValue );

            }

        }

        #endregion

        #region ClickThrough

        public bool ClickThrough {
            get { return (bool)GetValue(ClickThroughProperty); }
            set { SetValue(ClickThroughProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ClickThrough.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ClickThroughProperty =
            DependencyProperty.Register(
                "ClickThrough",
                typeof(bool),
                typeof(OverlayWindow),
                new PropertyMetadata(
                    true,
                    ClickThroughPropertyChanged));

        private static void ClickThroughPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            if (d is OverlayWindow window)
            {

                window.SetClickThrough((bool)e.NewValue);

            }

        }

        #endregion

        #endregion

        #region const values

        private const int GWL_STYLE = (-16); // ウィンドウスタイル
        private const int GWL_EXSTYLE = (-20); // 拡張ウィンドウスタイル

        private const int WS_SYSMENU = 0x00080000; // システムメニュを表示する
        private const int WS_EX_TRANSPARENT = 0x00000020; // 透過ウィンドウスタイル

        private const int WM_SYSKEYDOWN = 0x0104; // Alt + 任意のキー の入力

        private const int VK_F4 = 0x73;

        #endregion

        #region Win32Apis

        [DllImport("user32")]
        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32")]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwLong);

        #endregion

        public OverlayWindow()
        {

            // 透過背景
            this.WindowStyle = WindowStyle.None;
            this.AllowsTransparency = true;
            this.Background = new SolidColorBrush(Colors.Transparent);

            // 全画面表示
            this.WindowState = WindowState.Maximized;

            // 最前面表示
            this.Topmost = true;

            //タスクバーに表示しない
            this.ShowInTaskbar = false;

        }

        protected override void OnSourceInitialized(EventArgs e)
        {

            //システムメニュを非表示
            this.SetShowSystemMenu(this.ShowSystemMenu);

            //クリックをスルー
            this.SetClickThrough( this.ClickThrough );

            //Alt + F4 を無効化
            var handle = new WindowInteropHelper(this).Handle;
            var hwndSource = HwndSource.FromHwnd(handle);
            hwndSource.AddHook(WndProc);

            base.OnSourceInitialized(e);

        }

        protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr IParam, ref bool handled)
        {

            //Alt + F4 が入力されたら
            if (msg == WM_SYSKEYDOWN && wParam.ToInt32() == VK_F4)
            {

                if (this.AltF4Cancel)
                {

                    //処理済みにセットする
                    //(Windowは閉じられなくなる)
                    handled = true;

                }

            }

            return IntPtr.Zero;

        }

        /// <summary>
        /// システムメニュの表示を切り替える
        /// </summary>
        /// <param name="value"><see langword="true"/> = 表示, <see langword="false"/> = 非表示</param>
        protected void SetShowSystemMenu( bool value )
        {

            try
            {

                var handle = new WindowInteropHelper(this).Handle;

                int windowStyle = GetWindowLong(handle, GWL_STYLE);

                if (value)
                {
                    windowStyle |= WS_SYSMENU; //フラグの追加
                }
                else
                {
                    windowStyle &= ~WS_SYSMENU; //フラグを消す
                }

                SetWindowLong(handle, GWL_STYLE, windowStyle);

            }
            catch
            {

            }

        }

        /// <summary>
        /// クリックスルーの設定
        /// </summary>
        /// <param name="value"><see langword="true"/> = クリックをスルー, <see langword="false"/>=クリックを捉える</param>
        protected void SetClickThrough(bool value)
        {

            try
            {

                var handle = new WindowInteropHelper(this).Handle;

                int extendStyle = GetWindowLong(handle, GWL_EXSTYLE);

                if (value)
                {
                    extendStyle |= WS_EX_TRANSPARENT; //フラグの追加
                }
                else
                {
                    extendStyle &= ~WS_EX_TRANSPARENT; //フラグを消す
                }

                SetWindowLong(handle, GWL_EXSTYLE, extendStyle);

            }
            catch
            {

            }

        }

    }
}

```

# 参考文献

> - [WPFアプリケーションでウィンドウプロシージャをフックする]
(https://qiita.com/tricogimmick/items/86141bc33c0e06e9d2e9)

> - [WPF でシステムメニューのないウィンドウを作る]
(http://grabacr.net/archives/208)

> - [WPFでフルスクリーン表示]
(http://sourcechord.hatenablog.com/entry/20131117/1384697484)

> - [半透明の、触れないフォームの作り方]
(https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/f0ec615d-e183-4faa-85c7-4d031300a2d2?forum=vbgeneralja)

> - [C#(WPF) – xamlで『タスクバーにアイコン非表示』『ウィンドウ非表示』設定]
(http://mizutanikirin.net/cwpf-xaml%E3%81%A7%E3%80%8E%E3%82%BF%E3%82%B9%E3%82%AF%E3%83%90%E3%83%BC%E3%81%AB%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E9%9D%9E%E8%A1%A8%E7%A4%BA%E3%80%8F%E3%80%8E%E3%82%A6%E3%82%A3%E3%83%B3)