システムメニューというのは、ウィンドウ左上のアイコンをクリックしたとき、またはタスクバーを Shift + 右クリック (Windows 7 以前は右クリックのみ) したときに出るメニューです。
最近、このシステムメニューを表示したくないという要件に遭遇しました。
WindowStyle プロパティを None に、ShowInTaskbar プロパティを false にすれば表示できなくなるように見えますが、実は Alt + Space キーでバッチリ表示されてしまいます。
今回は、WPF のウィンドウからこのシステムメニューを消してみます。
Win32API
ズバリ、Win32API が必要のようです。使うのは、次のふたつの関数。
GetWindowLong 関数でウィンドウ スタイルを取得し、WS_SYSMENU を抜いたウィンドウ スタイルを SetWindowLong 関数で設定する、という流れになります。
作ってみた
今回は、上記の処理を「アタッチ可能な機能」としてビヘイビアーで作りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interactivity; using System.Windows.Interop; namespace SystemMenuSample { public class HideSystemMenuBehavior : Behavior { [DllImport("user32")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwLong); [crayon-67510dfc5b77e600927453 ] private int GWL_STYLE = -16; private int WS_SYSMENU = 0x00080000; private int WM_SYSKEYDOWN = 0x0104; private int VK_F4 = 0x73; protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SourceInitialized += (sender, e) => { var handle = new WindowInteropHelper(this.AssociatedObject).Handle; var windowStyle = GetWindowLong(handle, GWL_STYLE); windowStyle &= ~WS_SYSMENU; SetWindowLong(handle, GWL_STYLE, windowStyle); var hwndSource = HwndSource.FromHwnd(handle); hwndSource.AddHook(this.WndProc); }; } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_SYSKEYDOWN && wParam.ToInt32() == VK_F4) { handled = true; } return IntPtr.Zero; } } |
}
[/crayon]
Window クラスの SourceInitialized イベントでウィンドウ スタイルの設定をしています。ハイライトした部分 (28 ~ 30 行目) が、システムメニューを外すコードになります。
一般的な初期化処理で使う Initialized イベントではない理由は、Initialized イベントの時点ではウィンドウ ハンドルを取得できないからです。
加えて、今回はおまけで Alt + F4 によるウィンドウを閉じる操作も抑止してみました。WndProc メソッドでウィンドウ メッセージを受け取り、Alt + F4 キーが押されたときに「ウィンドウ メッセージを処理した」とマークすることにより、ウィンドウを閉じる処理が行われなくなります。
あとは、このビヘイビアーをウィンドウにアタッチしてあげれば完了ですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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:l="clr-namespace:SystemMenuSample" Title="MainWindow" Height="100" Width="300" ResizeMode="NoResize" WindowStyle="None" ShowInTaskbar="False"> <i:Interaction.Behaviors> </i:Interaction.Behaviors> BorderThickness="1" BorderBrush="Purple"> HorizontalAlignment="Center" VerticalAlignment="Center" /> |
これで、システムメニューが表示されない (Alt + F4 で閉じない) ウィンドウができました。
(画像じゃわかるはずもありませんが。。。
そもそも何で必要?
余談ですが、そもそも何でこんなのが必要なの?というお話。
ここ最近ずっと、業務で WPF アプリケーション上で Win32 アプリケーションを動かすということをやっています。
HwndHost は、同じトップレベル ウィンドウの他の WPF 要素の上に表示されます。 ただし、ToolTip または ContextMenu で生成されるメニューは独立したトップレベル ウィンドウであるため、HwndHost で正しく動作します。
というように、WPF ウィンドウの上に Win32 アプリケーションを乗せると、必ず Win32 アプリケーションが一番上に描画されてしまい、その上に WPF コントロールを乗せることができません。ですので、Win32 アプリケーションの上に WPF コントロールを乗せたい場合、独立したトップレベル ウィンドウ (Popup クラスや Window クラス) を用意して、それっぽく見せる必要があります。
もうおわかりかと思いますが、Win32 アプリケーションの上に乗ってるコントロールとして見せるウィンドウは、Alt + Space でシステムメニューを表示されてしまっては (+勝手に移動したり閉じられてしまっては) 困るわけです。ということで、今回のようなシステムメニューを表示しないウィンドウを作ってみました。