戻る


マウスカーソルの制御の基本型

基本的な考え方

フォーム側のソースコード

        private void button1_Click(object sender, EventArgs e) {

            using (WaitCursor waitCursor = new WaitCursor(this)) {

                #region 何らかの処理
                System.Threading.Thread.Sleep(3000);
                #endregion 
            }

        }

using 句を使い WaitCursor インスタンスの有効範囲の間だけ、マウスカーソルを有効にするという考えです

WaitCursorクラスのソースコード

    class WaitCursor : IDisposable {

        private System.Windows.Forms.Form controledForm;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public WaitCursor(System.Windows.Forms.Form controledForm) {

            this.controledForm = controledForm;

            this.controledForm.Cursor = Cursors.WaitCursor;

        #region IDisposable メンバ

        public void Dispose() {

            this.controledForm.Cursor = Cursors.Default;

        }

        #endregion
    }

コンストラクタで カーソルをWaitにし、Dispose にて Default に戻します。

ところが、これがうまく行かないケースがある

親フォームが子ダイアログフォームを表示した時である。下図は、概略イメージ

   

どうも、子ダイアログから制御が帰ってきた時に、カーソルの変更が効かないようである。
子ダイアログから制御が帰ってきた後にSystem.Windows.Forms.Application.DoEvents(); を行えば カーソル変更は効いたのだが、それはやりたくないし…。
不思議と、子ダイアログから制御が帰ってきた後で、Cursorプロパティ の設定をし直せば、変わった

ならば、このCursorプロパティの再設定を、WaitCursor クラスの中で自動的にできないものか…???

再設定するタイミングは、親Formが、アクティブになった時で良いのではないか?

WaitCursorクラスのコンストラクタで、フォームを引数に渡している。
フォームのアクティブイベントを、WaitCursorクラスの中で捕まえられないか?

という事で、イベントの追加をしてみましょう

    class WaitCursor : IDisposable {

        private System.Windows.Forms.Form controledForm;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public WaitCursor(System.Windows.Forms.Form controledForm) {

            this.controledForm = controledForm;

            this.controledForm.Cursor = Cursors.WaitCursor;

            this.controledForm.Activated += new System.EventHandler(this.CursorResetting);

        }

        private void CursorResetting(object sender, EventArgs e) {

            this.controledForm.Cursor = this.controledForm.Cursor;
        }


        #region IDisposable メンバ

        public void Dispose() {

            this.controledForm.Cursor = Cursors.Default;

            this.controledForm.Activated -= new System.EventHandler(this.CursorResetting);
        }

        #endregion
    }

WaitCursor の 内部変数で持ち込んでいる フォームの アクティブイベントに、リスナーを追加する事ができました。
これで、親フォームのアクティブイベントが発生した時に、カーソルの再設定が可能となった。

クリックボタンの二度押しの考慮

VB6時代には、DoEvents と、フラグを用いて、イベントキューに溜まったメソッドを捨てていたが、この方法については、議論の余地がある。
VB2005 ボタン二度押し防止方法 - MSDN フォーラム
http://forums.microsoft.com/msdn-ja/showpost.aspx?postid=2381944&siteid=7&sb=0&d=1&at=7&ft=11&tf=0&pageid=0

上記リンクの中に、Idleイベントを用いる方法が載っていた。Idleイベントとは、処理が終わる時に発生するイベントのようである。
つまり、イベントキューに何かが残っている時には、Idleイベントが発生しない。だからIdleイベントが発生した時点で、処理中フラグをOFFにする…というアイディアである。

Private _IsEventProcessing As Boolean

Private Sub Form1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
     AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)
End Sub

Private Sub Application_Idle(ByVal sender As ObjectByVal e As System.EventArgs)
    _IsEventProcessing = False
End Sub

Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
    If _IsEventProcessing Then Exit Sub
    _IsEventProcessing = True
    Threading.Thread.Sleep(5000' wait for 5secs
End Sub  

Application_Idle イベントに、Application_Idleをリスナーとして、登録している。

Applicationは、スタティックな為、Form1が破棄されたとしても残ってしまうので、リスナー解除を検討する必要がある。
また、ダイアログなどでもリスナー登録をした場合、アイドルイベントは全てのフォームのリスナーを呼び出してしまうことになる。

これらを回避する為に、フォーカスを持っているフォームだけが、リスナーとして有効である必要がある。

その為、フォームがアクティブになった際にリスナー登録を行い、非アクティブになった時には、リスナーを解除するようなシカケが必要が思われる。

作ってみた

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace _20080409_usingの検証 {
    /// <summary>
    /// イベントの処理中を管理する
    /// </summary>
    class EventBusy :IDisposable{

        /// <summary>
        /// 管理対象のフォームオブジェクト
        /// </summary>
        private Form form = null;

        /// <summary>
        /// 処理中である事を示す
        /// </summary>
        private bool isBusy = false;

        /// <summary>
        /// カーソルを制御する
        /// </summary>
        private bool withWaitCursor = false;

        /// <summary>
        /// コントラクタ
        /// </summary>
        /// <param name="form">管理対象のフォーム</param>
        /// <param name="withWaitCursor">ビジーの時にカーソルを制御するかを指定します</param>
        public EventBusy(Form form, bool withWaitCursor) {

            //変数の退避
            this.form = form;
            this.withWaitCursor = withWaitCursor;

            //イベントリスナーの追加
            //アイドルイベントを追加するトリガーを捕まえる為
            this.form.Activated += new System.EventHandler(this.ActivatedEventListener);

            //アイドルイベントを削除するトリガーを捕まえる為
            this.form.Deactivate += new System.EventHandler(this.DeactivatedEventListener);
        }


        /// <summary>
        /// ビジー状態を取得する
        /// </summary>
        public bool IsBusy{
            get{
                return this.isBusy;
            }
        }

        /// <summary>
        /// ビジー状態にする
        /// </summary>
        public void SetBusy(){
            this.isBusy = true;

            if (withWaitCursor) {
                form.Cursor = Cursors.WaitCursor;
            }
        }

        /// <summary>
        /// ビジー状態を解除する
        /// </summary>
        private void setUnBusy() {
            this.isBusy = false;

            if (withWaitCursor) {
                form.Cursor = Cursors.Default;
            }
        }


        /// <summary>
        /// フォームがアクティブになった時は、アイドルイベントをリスニングするよう設定する
        /// </summary>
        /// <param name="sender">イベント発生オブジェクト</param>
        /// <param name="e">イベント引数</param>
        private void ActivatedEventListener(object sender, EventArgs e) {

            Application.Idle += new System.EventHandler(this.ApplicationIdle);

            //モーダルフォームから返却された時、カーソルが変わってくれない時があるので
            //強制的に再設定する
            if (isBusy) {
                SetBusy();
            }
        }

        /// <summary>
        /// フォームがディアクティブになった時は、アイドルイベントをリスニング解除する
        /// </summary>
        /// <param name="sender">イベント発生オブジェクト</param>
        /// <param name="e">イベント引数</param>
        private void DeactivatedEventListener(object sender, EventArgs e) {

            Application.Idle -= new System.EventHandler(this.ApplicationIdle);
        }

        /// <summary>
        /// アプリケーションがアイドルになる時のイベントで、ビジーフラグを解除する
        /// </summary>
        /// <param name="sender">イベント発生オブジェクト</param>
        /// <param name="e">イベント引数</param>
        private void ApplicationIdle(object sender, EventArgs e) {
            this.setUnBusy();

            System.Diagnostics.Debug.WriteLine("ApplicationIdle");
        }

        #region IDisposable メンバ
        /// <summary>
        /// 破棄依頼時の処理
        /// </summary>
        public void Dispose() {
            //追加登録したイベントリスナーを解除する
            Application.Idle -= new System.EventHandler(this.ApplicationIdle);
            this.form.Activated -= new System.EventHandler(this.ActivatedEventListener);
            this.form.Deactivate -= new System.EventHandler(this.DeactivatedEventListener);
        }

        #endregion
    }
}
  

解説

コンストラクタのwithWaitCursor は 処理中になった時にカーソルをwaitに変更するかどうかを制御するものとして追加してある。

フォームのアクティブ系イベントをリスニングするため、フォームオブジェクトを持ち込む方法を取った。

ビジー状態を解除するのは、アイドルイベント発生時のみであるので、setUnBusyメソッドは公開しない。

利用する側

    public partial class Form3 : Form {

        private EventBusy eventBusy = null;

        public Form3() {
            InitializeComponent();

            eventBusy = new EventBusy(this,true);
        }

        private void button1_Click(object sender, EventArgs e) {

            if (eventBusy.IsBusy) {
                return;
            }
            eventBusy.SetBusy();

            System.Console.WriteLine("普通のボタン1クリックイベント");
            System.Threading.Thread.Sleep(2000);
        }
    }

eventBusy をインスタンス変数として宣言する。

2度押し抑制したいイベントだけ、IsBusyプロパティにて判定する。


戻る