37
47

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-19

概要

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

最終目標として、以下のような表示を目指した。
art

開発・検証環境

  • 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 で書くとこんな感じ

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 で書くとこんな感じ

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 で書くとこんな感じ

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の分離コードに記述していきます。

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 );

        }
    }
}

演算子に関しては以下を参照

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

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

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

  • ShowInTaskbar = false

Xaml で書くとこんな感じ

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 で対応していきます。

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 );

        }
    }
}

演算子に関しては以下を参照

7. Alt+F4で閉じない

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

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;

        }
    }
}

ビヘイビアにまとめる

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

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に適用します。

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>

ビヘイビアにまとめない

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

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
            {

            }

        }

    }
}

参考文献

37
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
47