読者です 読者をやめる 読者になる 読者になる

yamamoWorks

.NET技術を中心に気まぐれに更新していきます

非同期処理中にコントロールの操作を抑制する方法

サーバと通信するクライアントアプリなどで、検索処理などサーバからの応答に時間がかかる処理を実行する際の表示について1つのアプローチを紹介したいと思います。

まず、ボタンなどを押した時にそのままの流れで時間がかかる処理を実行した場合、しばらくすると「応答なし」状態になり格好悪いですよね。

 

そこで非同期処理にしようと思うわけですが、時間がかかる処理を実行中に他のボタン等は押されたくない事が多々あります。

非同期処理を開始する時にフォーム上のコントロールをすべて「Enable=false」にして非同期処理終了時に「Enable=true」に戻すなんて事もできますがこれも格好悪いですし、押されては困るコントロールのEnableプロパティだけを個別に設定するのもスマートな感じがしません。



他にも非同期処理実行中に「実行中」と書かれた別のフォームをShowDialog()しておく方法も考えられますが、検索処理など条件によってサーバからの応答時間が変化するような場合で応答時間が短いときに「実行中」フォームが一瞬だけ表示されるのもイマイチな気がします。



そこで、見た目はそのままでフォーム上のコントロールを操作できなくする方法をご紹介します。


ウィンドウメッセージがフォームやコントロールにディスパッチされる前にほかの操作を実行できるIMessageFilterインターフェイスを実装したクラスを用意し、このクラスを非同期実行開始時にアプリケーションに登録し非同期実行終了時に解除することによって、非同期処理中にフォーム上のコントロール操作だけをできないようにしてみます。



class MyMessageFilter : IMessageFilter
{
private const int WM_PAINT = 0x000F;
private const int WM_TIMER = 0x0113;

private Form form;

public MyMessageFilter(Form form)
{
this.form = form;
}

public bool PreFilterMessage(ref System.Windows.Forms.Message m)
{
// ハンドルからコントロールを取得
Control ctrl = Control.FromChildHandle(m.HWnd);

// WM_PAINT、WM_TIMERは通す(WM_TIMERはプログレスバーが必要とする為)
if (m.Msg == WM_PAINT || m.Msg == WM_TIMER)
{
return false;
}

// Formに対するメッセージは通す
if (m.HWnd == this.form.Handle)
{
return false;
}

// Form内のコントロールに対するメッセージはフィルタリング
if (ctrl != null && this.form == ctrl.FindForm())
{
return true;
}

return false;
}
}

public partial class Form1 : Form
{
private MyMessageFilter myMessageFilter;

public Form1()
{
InitializeComponent();

// メッセージフィルタ生成
this.myMessageFilter = new MyMessageFilter(this);
}

private void button1_Click(object sender, EventArgs e)
{
// 非同期実行する内容
MethodInvoker method = new MethodInvoker(
() =>
{
// 時間のかかる処理
Thread.Sleep(30000);
});

// 非同期完了時コールバック
AsyncCallback callback = new AsyncCallback(
(ar) =>
{
this.Invoke(new MethodInvoker(
() =>
{
// メッセージフィルタ削除
Application.RemoveMessageFilter(this.myMessageFilter);

// プログレスバー停止
this.toolStripProgressBar1.Style = ProgressBarStyle.Blocks;
}));
});

// メッセージフィルタ登録
Application.AddMessageFilter(this.myMessageFilter);

// プログレスバー開始
this.toolStripProgressBar1.Style = ProgressBarStyle.Marquee;

// 非同期実行
method.BeginInvoke(callback, null);
}
}

この例ではフォーム自体の移動や大きさの変更はできるようになってますがフォームを閉じることもできてしまうので、上記コードでは省略していますがFormClosingイベント等で非同期処理中はフォームが閉じないように対処しておく必要があります。