ページ

2004年9月22日水曜日

タイムアウトつきのメッセージボックス (C# による実装)

AILight さんとこの掲示板 で出ていたので公開。


指定した時間がたつと勝手に閉じるようにしたメッセージボックスです。
たとえば、こんな風に使います。





MessageBoxTimeout.Show("テスト", 5000);



こうすると 5秒(5000ミリ秒)たつとボタンを押さなくても勝手にメッセージボックスが閉じます。もちろん、ボタンを押したときは普通のメッセージボックスと同じように即座に閉じます。


仕組みは、別スレッドでタイムアウトを監視していて、時間がきたらメッセージボックスに PostMessage して 「キャンセルしたことにしちゃう」 というものです。PostMessage すべきメッセージボックスを探すときに、そのメッセージボックスを開いたスレッドと同じスレッドのみを対象としているので別プロセス or 別スレッドが表示しているメッセージボックスを間違って閉じちゃうということはないはずです。
ただ、以下のソースを見てもらうとわかるとおり Win32 API を使いまくりです。というか、このクラスってずいぶん前に Win32 API / C++ で作ったものを C# に置き換えただけだったりします。ほんとは、.NET らしい実現方法があるのかもしれませんが、とりあえずこれで良しとしています(^^;





using System;

using System.Runtime.InteropServices;

using System.Threading;

using System.Windows.Forms;

using System.Text;

 

/// <summary>

/// タイムアウトつきのメッセージボックスを表示するクラスです。

/// </summary>

public class MessageBoxTimeout

{

    [DllImport("kernel32.dll")]

    private static extern uint GetCurrentThreadId();

 

    private delegate int EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

 

    [DllImport("user32.dll")]

    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

 

    [DllImport("user32.dll", SetLastError=true)]

    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

 

    [DllImport("user32.dll")]

    private static extern int GetClassName(IntPtr hWnd, [Out] StringBuilder lpClassName, int nMaxCount);

 

    [DllImport("user32.dll")]

    private static extern bool IsWindowEnabled(IntPtr hWnd);

 

    [DllImport("user32.dll", SetLastError = true)]

    [return: MarshalAs(UnmanagedType.Bool)]

    private static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

 

    /// <summary>

    /// 別スレッドでタイムアウトを監視するためのクラスです。

    /// </summary>

    private class TimerThread

    {

        private DateTime timeoutTime;

        private uint currentThreadId;

        private bool terminateFlag;

        private Thread thread;

 

        /// <summary>

        /// コンストラクタです。

        /// メッセージボックスのタイムアウト監視を開始します。

        /// </summary>

        /// <param name="timeoutMillisec">タイムアウト値(ミリ秒)。</param>

        public TimerThread(int timeoutMillisec)

        {

            this.timeoutTime = DateTime.Now.AddMilliseconds(timeoutMillisec);

            this.currentThreadId = GetCurrentThreadId();

            this.terminateFlag = false;

            this.thread = new Thread(new ThreadStart(this.ThreadProc));

            this.thread.Start();

        }

 

        /// <summary>

        /// スレッド関数です。

        /// </summary>

        private void ThreadProc()

        {

            while (!this.terminateFlag)

            {

                Thread.Sleep(100);

                if (DateTime.Now > this.timeoutTime)

                {

                    // タイムアウトが発生

                    // EnumWindows API を使ってメッセージボックスウインドウを探す

                    EnumWindows(new EnumWindowsProc(this.EnumWindowsProc), new IntPtr(0));

                    return;

                }

            }

        }

 

        /// <summary>

        /// メッセージボックスウインドウを探して、見つかった場合は閉じます。

        /// </summary>

        /// <param name="hWnd"></param>

        /// <param name="lParam"></param>

        /// <returns></returns>

        private int EnumWindowsProc(IntPtr hWnd, IntPtr lParam)

        {

            uint processId;

            uint threadId;

            threadId = GetWindowThreadProcessId(hWnd, out processId);

            if (threadId == this.currentThreadId)

            {

                StringBuilder className = new StringBuilder("", 256);

                GetClassName(hWnd, className, 256);

                if (className.ToString() == "#32770" && IsWindowEnabled(hWnd))

                {

                    const int WM_COMMAND = 0x111;

                    PostMessage(hWnd, WM_COMMAND, new IntPtr(2), new IntPtr(0));

                    return 0;

                }

            }

            return 1;

        }

 

        /// <summary>

        /// タイムアウト監視用スレッドを終了させます。

        /// </summary>

        public void Terminate()

        {

            this.terminateFlag = true;

            this.thread.Join();

        }

    }

 

    /// <summary>

    /// 指定したテキストのボタンを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text);

        }

        finally

        {

            tt.Terminate();

        }

    }

 

    /// <summary>

    /// 指定したテキスト、およびキャプションのボタンを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <param name="caption">メッセージ ボックスのタイトル バーに表示するテキスト。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, string caption, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text, caption);

        }

        finally

        {

            tt.Terminate();

        }

    }

 

    /// <summary>

    /// 指定したテキスト、キャプション、およびボタンのボタンを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <param name="caption">メッセージ ボックスのタイトル バーに表示するテキスト。</param>

    /// <param name="buttons">メッセージ ボックスに表示されるボタンを指定する <see cref="MessageBoxButtons"/> 値の 1 つ。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text, caption, buttons);

        }

        finally

        {

            tt.Terminate();

        }

    }

 

    /// <summary>

    /// 指定したテキスト、キャプション、ボタン、およびアイコンのボタンを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <param name="caption">メッセージ ボックスのタイトル バーに表示するテキスト。</param>

    /// <param name="buttons">メッセージ ボックスに表示されるボタンを指定する <see cref="MessageBoxButtons"/> 値の 1 つ。</param>

    /// <param name="icon">メッセージ ボックスに表示されるアイコンを指定する <see cref="MessageBoxIcon"/> 値の 1 つ。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text, caption, buttons, icon);

        }

        finally

        {

            tt.Terminate();

        }

    }

 

    /// <summary>

    /// 指定したテキスト、キャプション、ボタン、アイコン、および既定のボタンを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <param name="caption">メッセージ ボックスのタイトル バーに表示するテキスト。</param>

    /// <param name="buttons">メッセージ ボックスに表示されるボタンを指定する <see cref="MessageBoxButtons"/> 値の 1 つ。</param>

    /// <param name="icon">メッセージ ボックスに表示されるアイコンを指定する <see cref="MessageBoxIcon"/> 値の 1 つ。</param>

    /// <param name="defaultButton">メッセージ ボックスの既定のボタンを指定する <see cref="MessageBoxDefaultButton"/> 値の 1 つ。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text, caption, buttons, icon, defaultButton);

        }

        finally

        {

            tt.Terminate();

        }

    }

 

    /// <summary>

    /// 指定したテキスト、キャプション、ボタン、アイコン、既定のボタン、およびオプションを表示するメッセージ ボックスを表示します。

    /// </summary>

    /// <param name="text">メッセージ ボックスに表示するテキスト。</param>

    /// <param name="caption">メッセージ ボックスのタイトル バーに表示するテキスト。</param>

    /// <param name="buttons">メッセージ ボックスに表示されるボタンを指定する <see cref="MessageBoxButtons"/> 値の 1 つ。</param>

    /// <param name="icon">メッセージ ボックスに表示されるアイコンを指定する <see cref="MessageBoxIcon"/> 値の 1 つ。</param>

    /// <param name="defaultButton">メッセージ ボックスの既定のボタンを指定する <see cref="MessageBoxDefaultButton"/> 値の 1 つ。</param>

    /// <param name="options">メッセージ ボックスで使用する表示オプションと関連付けオプションを指定する <see cref="MessageBoxOptions"/> 値の 1 つ。</param>

    /// <returns><see cref="DialogResult"/> 値の 1 つ。</returns>

    public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options, int timeoutMillsec)

    {

        TimerThread tt = new TimerThread(timeoutMillsec);

        try

        {

            return MessageBox.Show(text, caption, buttons, icon, defaultButton, options);

        }

        finally

        {

            tt.Terminate();

        }

    }

}

0 件のコメント:

コメントを投稿