フォーム側のソースコード
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.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)
End Sub
Private Sub Application_Idle(ByVal sender As Object, ByVal e As System.EventArgs)
_IsEventProcessing = False
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal 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プロパティにて判定する。