我注意到关于设置光标的一个有趣的事情,所以我想澄清一下我自己之前的一些误解,希望它也可以帮助其他人:
当您尝试使用设置表单的光标时
this.cursor = 光标.Waitcursor
您实际上为控件设置了光标,而不是整个表单,因为光标是 Control 类的属性。
当然,只有当鼠标实际位于实际控件上时光标才会更改为给定的光标(明确地是表单的区域)
正如 Hans Passant 已经说过的那样:
Windows 向包含鼠标光标的窗口发送 WM_SETCURSOR 消息,使其有机会更改光标形状
我不知道 Windows 是否直接向控件发送消息,或者表单是否根据鼠标位置将这些消息中继到它的子控件,我很可能会猜测第一种方法,因为当我使用表单的覆盖 WndProc 获取消息时控件,例如,当我在文本框上时,表单没有处理任何消息。(请有人澄清这一点)
基本上我的建议是停止使用 this.cursor 并坚持使用 this.usewaitcursor,因为这会将所有子控件的 cursor 属性更改为 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 块中使用它