泥庭

2015年4月6日

【WPF】カスタムメッセージボックス

Filed under: C#, WPF — タグ: , , , — yone64 @ 11:56 PM
WPF標準のメッセージボックスは、何かと機能不足でカスタマイズ性が低くて困ってしまいますね。
そこで、通常のWindowをMessageBoxっぽく見せることを検討してみたいと思います。

なお、Windows7以降のみをターゲットとできる方は、Windows API Code PackのTaskDialogをお勧めします。
良記事→ http://grabacr.net/archives/tag/windows-api-code-pack
# いまさらXP対応はないよね。Windows Server 2003ももうすぐサポート切れるし…
# Embedded とか、知らない子ですし。
というわけで、誰得なエントリーです。


WindowとMessageBoxの違い

まず、Windowの詳細。
[MSDN]WPF ウインドウの概要 より


https://msdn.microsoft.com/ja-jp/library/ms748948%28v=vs.110%29.aspx

手元にのWindows8で見ても、見た目は少し違いますが構成要素は同じです。

無題

次にメッセージボックス。

無題2

大まかな違いは、
  • アイコンがない
  • 最大化最小化ボタンがない
  • サイズ変更できない
  • システムメニューが少ない
  • タスクバーに表示されない(画像からはわかりませんが)
といったところでしょうか?あと、MessageBoxはYesNoの場合のみ、閉じるボタンが無効化されるようです。(何故?)

無題3

実践

それでは、WindowをMessageBoxに近づけていきましょう。まず、簡単なところから、

タスクバーにアイコンを表示しない

楽勝ですね。ShowInTaskbarプロパティーがあるので、これをfalseにするだけです。

サイズ変更を抑制する

これも、プロパティーが用意されているので、そのまま使いましょう。
ResizeModeプロパティーを、NoResize
SizeToContentプロパティーを、WidthAndHeight
と変更すると、良い感じです。
ここまでで、大分MessageBoxに近づきました。特にこだわりがなければ、これで十分だと思います。

無題4

var w = new Window() { Title = "ウインドウ", Content = new Label() { Width = 300, Height = 150, Content = "Hello" } };
w.ShowInTaskbar = false;
w.ResizeMode = ResizeMode.NoResize;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.Owner = this;
w.ShowDialog();

アイコンを消す

WindowsFormsだと、ShowIconプロパティーのみで解決するんですが、WPFだとそうもいきません。
このブログでも以前のエントリーで紹介していましたが、残念ながら既にリンク先ページもなくなってました。
# しかも、以前リンクしていたページの方法では、XPとか32bit版Win7では正しく動いてなかったらしい
というわけで、↓がお勧め(VBですが)
・WPF ウィンドウのアイコンを非表示にする。
http://d.hatena.ne.jp/hilapon/20110530/1306735416

ただ、残念な点もあって、システムメニューが全部復活してしまうのです。

無題5

今回は、このあとメニューを削除してしまうので問題ないのですが、アイコンだけ消したい場合はご注意ください。
# むしろ、システムメニューに影響を与えずにアイコンを消す方法を誰かplz

システムメニューからメニューアイテムを削除する

システムメニューからメニューアイテムを削除するためには、
GetSystemMenu関数でシステムメニューを取得したのち、DeleteMenu関数で削除します。
各メニューの定数は、ここが詳しいです。

無題6

ここまでのコードは下記通りです。
var w = new Window() { Title = "ウインドウ", Content = new Label() { Width = 300, Height = 150, Content = "Hello" } };
w.ShowInTaskbar = false;
w.ResizeMode = ResizeMode.NoResize;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.Owner = this;
w.SourceInitialized += (o, args) =>
{
    w.RemoveIcon();
    w.DeleteSystemMenu(WindowHelper.SC_RESTORE);
    w.DeleteSystemMenu(WindowHelper.SC_SIZE);
    w.DeleteSystemMenu(WindowHelper.SC_MAXIMIZE);
    w.DeleteSystemMenu(WindowHelper.SC_MINIMIZE);
};
w.ShowDialog();
public static class WindowHelper
{
    [DllImport("user32.dll")]
    private static extern int GetWindowLong(IntPtr hwnd, int index);

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,
               int x, int y, int width, int height, uint flags);

    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hwnd, uint msg,
               IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll")]
    private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    internal const int GWL_EXSTYLE = -20;
    internal const int WS_EX_DLGMODALFRAME = 0x0001;
    internal const int SWP_NOSIZE = 0x0001;
    internal const int SWP_NOMOVE = 0x0002;
    internal const int SWP_NOZORDER = 0x0004;
    internal const int SWP_FRAMECHANGED = 0x0020;
    internal const uint WM_SETICON = 0x0080;
    internal const int ICON_SMALL = 0;
    internal const int ICON_BIG = 1;

    internal const UInt32 SC_SIZE = 0xF000;
    internal const UInt32 SC_MOVE = 0xF010;
    internal const UInt32 SC_MINIMIZE = 0xF020;
    internal const UInt32 SC_MAXIMIZE = 0xF030;
    internal const UInt32 SC_NEXTWINDOW = 0xF040;
    internal const UInt32 SC_PREVWINDOW = 0xF050;
    internal const UInt32 SC_CLOSE = 0xF060;
    internal const UInt32 SC_VSCROLL = 0xF070;
    internal const UInt32 SC_HSCROLL = 0xF080;
    internal const UInt32 SC_MOUSEMENU = 0xF090;
    internal const UInt32 SC_KEYMENU = 0xF100;
    internal const UInt32 SC_ARRANGE = 0xF110;
    internal const UInt32 SC_RESTORE = 0xF120;
    internal const UInt32 SC_TASKLIST = 0xF130;
    internal const UInt32 SC_SCREENSAVE = 0xF140;
    internal const UInt32 SC_HOTKEY = 0xF150;
    internal const UInt32 SC_DEFAULT = 0xF160;
    internal const UInt32 SC_MONITORPOWER = 0xF170;
    internal const UInt32 SC_CONTEXTHELP = 0xF180;
    internal const UInt32 SC_SEPARATOR = 0xF00F;

    internal const UInt32 MF_BYCOMMAND = 0x00000000;
    internal const UInt32 MF_BYPOSITION = 0x00000400;

    public static void RemoveIcon(this Window window)
    {
        // Get this window's handle
        IntPtr hwnd = new WindowInteropHelper(window).Handle;

        // Change the extended window style to not show a window icon
        int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
        SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);

        SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_SMALL, IntPtr.Zero);
        SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_BIG, IntPtr.Zero);

        // Update the window's non-client area to reflect the changes
        SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE |
              SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    }

    public static void DeleteSystemMenu(this Window window, uint menuType)
    {
        var hwnd = new WindowInteropHelper(window).Handle;
        var hMenu = GetSystemMenu(hwnd, false);
        if (hMenu != IntPtr.Zero)
        {
            DeleteMenu(hMenu, menuType, MF_BYCOMMAND);
        }
    }

}

閉じるボタンを無効化する

MessageBoxでは、YesNoのときのみ閉じるボタンが無効化されているので、これを再現します。
ここで利用するのはEnableMenuItem関数です。 DeleteMenuでも、閉じるボタンが無効化されましたので、こちらを使います。
なお、SeparatorがIndexによる削除しか受け付けないようなので、IndexでMenuを削除するメソッドも追加しています。
ここまで来るともれなくWin32APIのお世話になりますね。
また、閉じるボタンを無効化してもAlt+F4では閉じてしまうので(ひどいですよね)、これもフックしておく必要があります。
最終のソースコードは下記通りです。あとは、ContentにDatatemplateがあたるようにしてあげればOKでしょう。
var w = new Window() { Title = "ウインドウ", Content = new Label() { Width = 300, Height = 150, Content = "Hello" } };
w.ShowInTaskbar = false;
w.ResizeMode = ResizeMode.NoResize;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.Owner = this;

w.SourceInitialized += (o, args) =>
{
    w.RemoveIcon();
    w.DeleteSystemMenu(WindowHelper.SC_RESTORE);
    w.DeleteSystemMenu(WindowHelper.SC_SIZE);
    w.DeleteSystemMenu(WindowHelper.SC_MAXIMIZE);
    w.DeleteSystemMenu(WindowHelper.SC_MINIMIZE);
    if (isYesNo)
    {
        w.DeleteSystemMenu(WindowHelper.SC_CLOSE);
        w.HandledAltF4();
        // Separator は Indexじゃないと消えないっぽい
        w.DeleteSystemMenu(1);
    }
};

w.ShowDialog();
public static class WindowHelper
{
    [DllImport("user32.dll")]
    private static extern int GetWindowLong(IntPtr hwnd, int index);

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,
               int x, int y, int width, int height, uint flags);

    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hwnd, uint msg,
               IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll")]
    private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    internal const int GWL_EXSTYLE = -20;
    internal const int WS_EX_DLGMODALFRAME = 0x0001;
    internal const int SWP_NOSIZE = 0x0001;
    internal const int SWP_NOMOVE = 0x0002;
    internal const int SWP_NOZORDER = 0x0004;
    internal const int SWP_FRAMECHANGED = 0x0020;
    internal const uint WM_SETICON = 0x0080;
    internal const int ICON_SMALL = 0;
    internal const int ICON_BIG = 1;

    internal const UInt32 SC_SIZE = 0xF000;
    internal const UInt32 SC_MOVE = 0xF010;
    internal const UInt32 SC_MINIMIZE = 0xF020;
    internal const UInt32 SC_MAXIMIZE = 0xF030;
    internal const UInt32 SC_NEXTWINDOW = 0xF040;
    internal const UInt32 SC_PREVWINDOW = 0xF050;
    internal const UInt32 SC_CLOSE = 0xF060;
    internal const UInt32 SC_VSCROLL = 0xF070;
    internal const UInt32 SC_HSCROLL = 0xF080;
    internal const UInt32 SC_MOUSEMENU = 0xF090;
    internal const UInt32 SC_KEYMENU = 0xF100;
    internal const UInt32 SC_ARRANGE = 0xF110;
    internal const UInt32 SC_RESTORE = 0xF120;
    internal const UInt32 SC_TASKLIST = 0xF130;
    internal const UInt32 SC_SCREENSAVE = 0xF140;
    internal const UInt32 SC_HOTKEY = 0xF150;
    internal const UInt32 SC_DEFAULT = 0xF160;
    internal const UInt32 SC_MONITORPOWER = 0xF170;
    internal const UInt32 SC_CONTEXTHELP = 0xF180;
    internal const UInt32 SC_SEPARATOR = 0xF00F;

    internal const UInt32 MF_BYCOMMAND = 0x00000000;
    internal const UInt32 MF_BYPOSITION = 0x00000400;

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

    public static void RemoveIcon(this Window window)
    {
        // Get this window's handle
        IntPtr hwnd = new WindowInteropHelper(window).Handle;

        // Change the extended window style to not show a window icon
        int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
        SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);

        SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_SMALL, IntPtr.Zero);
        SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_BIG, IntPtr.Zero);

        // Update the window's non-client area to reflect the changes
        SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE |
              SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    }

    public static void DeleteSystemMenu(this Window window, uint menuType)
    {
        var hwnd = new WindowInteropHelper(window).Handle;
        var hMenu = GetSystemMenu(hwnd, false);
        if (hMenu != IntPtr.Zero)
        {
            DeleteMenu(hMenu, menuType, MF_BYCOMMAND);
        }
    }

    public static void DeleteSystemMenu(this Window window, int position)
    {
        var hwnd = new WindowInteropHelper(window).Handle;
        var hMenu = GetSystemMenu(hwnd, false);
        if (hMenu != IntPtr.Zero)
        {
            DeleteMenu(hMenu, (uint)position, MF_BYPOSITION);
        }
    }

    public static void HandledAltF4(this Window window)
    {
        var source = (HwndSource)HwndSource.FromVisual(window);
        source.AddHook(WindowHelper.HookProcedure);
    }

    private static IntPtr HookProcedure(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        //--- Alt + F4を無効化
        if (msg == WM_SYSKEYDOWN && wParam.ToInt32() == VK_F4)
        {
            handled = true;
        }

        return IntPtr.Zero;
    }
}
この辺の変更は、一度添付プロパティーとして作っておくとその後が楽なので、本番適用の場合は作成をお勧めします。
でも、本当にそこまで必要なの?っていう感じもしますので、ご利用は計画的にw
# 標準プロパティーでください。

コメントする »

まだコメントはありません。

RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com で無料サイトやブログを作成.

%d人のブロガーが「いいね」をつけました。