カーソルの設定について興味深いことに気づいたので、私自身が以前持っていたいくつかの誤解を解消したいと思います。それが他の人にも役立つことを願っています:
を使用してフォームのカーソルを設定しようとすると
this.cursor = Cursors.Waitcursor
カーソルは Control クラスのプロパティであるため、実際にはフォーム全体ではなくコントロールにカーソルを設定します。
また、もちろん、マウスが実際のコントロール (明示的にフォームの領域) の上にある場合にのみ、カーソルは指定されたカーソルに変更されます。
Hans Passant がすでに述べているように、
Windows は、マウス カーソルを含むウィンドウに WM_SETCURSOR メッセージを送信し、カーソルの形状を変更する機会を与えます。
Windowsがメッセージをコントロールに直接送信するのか、フォームがマウスの位置に基づいてそれらのメッセージを子コントロールに中継するのかはわかりません。フォームのWndProcをオーバーライドしてメッセージをフェッチしたときから、最初の方法を推測する可能性が最も高いでしょう。たとえば、テキストボックスの上にいるとき、フォームはメッセージを処理しませんでした。(誰かこれについて明確にしてください)
基本的に私の提案は、this.cursor も使用しないようにし、this.usewaitcursor に固執することです。これは、すべての子コントロールのカーソル プロパティを waitcursor に変更するためです。
これに関する問題は、アプリケーション レベルの Application.usewaitcursor と同じです。フォーム上にカーソルを置いていない間は、Windows から WM_SETCURSOR メッセージが送信されていないため、移動する前に時間のかかる同期操作を開始するとフォームの領域にマウスを合わせると、時間のかかる同期操作が終了したときにのみ、フォームはそのようなメッセージを処理できます。
(UIスレッドで時間のかかるタスクを実行することはお勧めしません。主にこれが問題の原因です)
Hans Passant の回答を少し改善したので、砂時計はアプリケーション レベルまたはフォーム レベルで設定でき、クロス スレッド操作呼び出しからの InvalidOperationException も回避できます。
using System;
using System.Windows.Forms;
public class HourGlass : IDisposable
{
public static bool ApplicationEnabled
{
get{ return Application.UseWaitCursor; }
set
{
Form activeFrom = Form.ActiveForm;
if (activeFrom == null || ApplicationEnabled == value) return;
if (ApplicationEnabled == value)return;
Application.UseWaitCursor = (bool)value;
if (activeFrom.InvokeRequired)
{
activeFrom.BeginInvoke(new Action(() =>
{
if (activeFrom.Handle != IntPtr.Zero)
SendMessage(activeFrom.Handle, 0x20, activeFrom.Handle, (IntPtr)1); // Send WM_SETCURSOR
}));
}
else
{
if (activeFrom.Handle != IntPtr.Zero)
SendMessage(activeFrom.Handle, 0x20, activeFrom.Handle, (IntPtr)1); // Send WM_SETCURSOR
}
}
}
private Form f;
public HourGlass()
{
this.f = Form.ActiveForm;
if (f == null)
{
throw new ArgumentException();
}
Enabled = true;
}
public HourGlass(bool enabled)
{
this.f = Form.ActiveForm;
if (f == null)
{
throw new ArgumentException();
}
Enabled = enabled;
}
public HourGlass(Form f, bool enabled)
{
this.f = f;
if (f == null)
{
throw new ArgumentException();
}
Enabled = enabled;
}
public HourGlass(Form f)
{
this.f = f;
if (f == null)
{
throw new ArgumentException();
}
Enabled = true;
}
public void Dispose()
{
Enabled = false;
}
public bool Enabled
{
get { return f.UseWaitCursor; }
set
{
if (f == null || Enabled == value) return;
if (Application.UseWaitCursor == true && value == false) return;
f.UseWaitCursor = (bool)value;
if(f.InvokeRequired)
{
f.BeginInvoke(new Action(()=>
{
if (f.Handle != IntPtr.Zero)
SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1); // Send WM_SETCURSOR
}));
}
else
{
if (f.Handle != IntPtr.Zero)
SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1); // Send WM_SETCURSOR
}
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
アプリケーションレベルで使用するには:
try
{
HourGlass.ApplicationEnabled = true;
//time consuming synchronous task
}
finally
{
HourGlass.ApplicationEnabled = false;
}
フォーム レベルで使用するには、現在のアクティブなフォームに対して次のいずれかを使用できます。
using (new HourGlass())
{
//time consuming synchronous task
}
または、次のような形式でローカル変数を初期化できます。
public readonly HourGlass hourglass;
public Form1()
{
InitializeComponent();
hourglass = new HourGlass(this, false);
}
後で try catch finally ブロックで使用します