C#のWinformsでuseWaitCursorが戻らないバグ

今回私がハマったのは、C#のWinFormsでuseWaitCursorを使ったあとに元のカーソルに戻らない!というバグ。

たぶんこれ、WinFormsのバグですよ。

っていうのもね。下記のようなコードを書いたわけです。

try{

this.UseWaitCursor = true; // 時間のかかるイベント。非同期実装 MessageBox.Show("処理が終わりました。")

}catch(Exception exception){

MessageBox.Show("エラーが発生しました。")

}finally{

this.UseWaitCursor = false;

}

ありがちなコードですね。

処理に時間がかかるから、非同期実装して、その前後にUseWaitCursorを置く。

めちゃくちゃシンプルです。

私の場合、try catch finallyを仕掛けて途中でエラーが出ても戻るようにしています。

なのに!!

なのに!!!

数回に1回待機中からもとに戻らんことがある!!!

っということで、UseWaitCursorから戻らないときに内部的にどのようなステータスを持っているのか見てみました。

目次

Cursorに関係するプロパティ

💡 UseWaitCursor が戻らないバグでハマったら別記事 UseWaitCursor が戻らない解決法 で詳しくまとめてます。

まずはCursorに関するプロパティはどこにあるのか?ということですが、下記の通りです。

  • Control.Cursor
  • Control.UseWaitCursor
  • Application.UseWaitCursor
  • Cursor.Current

続いて、この事象が発生した時のすべてのプロパティを見ていきます。

MessageBox.Show(Cursor.Current.ToString());

適当なボタンに上記のプログラムを仕込んで、事象が発生したらボタンを押して、中身を見るようにします。

  • Cursor.CurrentはDefaultになってる。

続いてApplication.UseWaitCursorを見てみる。

実行前はこんな感じ。

事象が発生している状態はこんな感じ。

続いて各コントロールのUseWaitCursorのプロパティを見てみる。

MessageBox.Show(GetWaitCursorStatus(this));

これを仕込んで、GetWaitCursorStatusは下記の関数を作った。

        private string GetWaitCursorStatus(Control control)
        {
            var str = $@"{control.Name} : {control.UseWaitCursor.ToString()}";

        foreach (Control c in control.Controls)
        {
            str += Environment.NewLine + GetWaitCursorStatus(c);
        }

        return str;
    }</code></pre></div>

コントロールの中にあるコントロールを再帰的に取得して、中身を見てくだけの関数。

事象が発生する前はこんな感じ。

全部Falseになってる。

事象が発生した時のプロパティはこんな感じ。

うん。

変わりないね。

っで、最後のControl.Cursorを見てみる。

        private string GetWaitCursorStatus(Control control)
        {
            var str = $@"{control.Name} : {control.Cursor.ToString()}";

        foreach (Control c in control.Controls)
        {
            str += Environment.NewLine + GetWaitCursorStatus(c);
        }

        return str;
    }</code></pre></div>

さっきのコードをちょっと修正して実行。

まあ普通って感じですね。

事象発生してるときはこんな感じ。

はい見つけました。

Control.CursorがWaitCursorになっててもとに戻ってない!!!!

ということでバグが発生しているコードに、下記を追加してFix!!

dgv_MainGrid.Cursor = Cursor.Current;
try{

this.UseWaitCursor = true; // 時間のかかるイベント。非同期実装 MessageBox.Show("処理が終わりました。")

}catch(Exception exception){

MessageBox.Show("エラーが発生しました。")

}finally{

this.UseWaitCursor = false; dgv_MainGrid.Cursor = Cursor.Current; }

💡 補足: 業務系の現場でよくハマるパターン

俺もこの WaitCursor、 業務でハマってきたところを3つ並べておきます。

① 例外発生時にカーソルが戻らない

処理開始時に Cursor.Current = Cursors.WaitCursor セットしたが、 try-catch で例外発生 → finally で戻す処理を入れ忘れ → 永久に砂時計のまま。 UseWaitCursor 戻らない問題 で詳細解説。

② Form.UseWaitCursor は Form 全体だけ

this.UseWaitCursor = true は Form 上の全コントロールに適用。 個別コントロール (DataGridView の特定セル等) には別途 Cursor プロパティを設定。 混同して半日溶かす。

③ 非同期処理中にカーソル戻し忘れ

Task.Run で重い処理 → UI スレッドではないので Cursor は直接戻せない。 Invoke 経由で UI スレッドに戻すか、 async/await + IProgress<T> でやる。

❓ よくある質問

Q1. UseWaitCursor と Cursor.Current の違いは?

A. UseWaitCursor は Form/Control プロパティで設定後継続。 Cursor.Current は1ショット (次の WindowsMessage で消える)。 業務系では UseWaitCursor 推奨 (戻し忘れ防止)。

Q2. using で自動戻しはできる?

A. はい。 using (new WaitCursor(this)) 的なヘルパークラスを作ると自動 Dispose で戻る。 try-finally が冗長になる時に便利。 業務系チームではユーティリティ化して使い回す。

Q3. SplashScreen 表示中のカーソル制御は?

A. SplashScreen 表示後の本 Form 起動時に WaitCursor を出すなら、 SplashScreen 側で完了通知 → 本 Form 側で UseWaitCursor=true → 初期化終了で false。

Q4. モーダルダイアログ表示時のカーソルは?

A. ShowDialog() は同期処理なので呼び出し前後で UseWaitCursor 切替するパターン。 詳細は ShowDialog vs Show 参照。

Q5. async/await でのベストプラクティスは?

A. this.UseWaitCursor = true; try { await DoWork(); } finally { this.UseWaitCursor = false; } パターン。 UI スレッド維持のまま戻せる。 WinForms 非同期パターン でも触れてます。

📚 関連記事

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次