J'ai remarqué une chose intéressante sur la configuration des curseurs, donc je voudrais éclaircir certains malentendus que j'avais moi-même avant et je l'espère peut aider les autres aussi:
Lorsque vous essayez de définir le curseur d'un formulaire en utilisant
this.cursor = Cursors.Waitcursor
vous définissez réellement le curseur pour le contrôle et non l'ensemble du formulaire puisque le curseur est la propriété de la classe Control.
aussi bien sûr le curseur ne sera changé pour le curseur donné lorsque la souris est en fait sur le contrôle effectif (explicitement de la zone de la forme)
Comme Hans Passant a déjà déclaré que:
windows envoie la fenêtre qui contient le curseur de la souris le message WM_SETCURSOR, ce qui lui donne l'occasion de changer le curseur forme
Je ne suis pas k maintenant si Windows envoie des messages directement aux contrôles ou si le formulaire relaie ces messages à ses contrôles enfants en fonction de la position de la souris, je devinerais probablement sur la première méthode depuis quand j'ai récupéré les messages avec WndProc override du contrôle de formulaire, quand je était sur la zone de texte par exemple, le formulaire n'a traité aucun message. (J'aimerais que quelqu'un donne la clarté sur ceci)
Fondamentalement ma suggestion serait de résider d'employer this.cursor aussi et coller à this.usewaitcursor, puisque cela change la propriété de curseur en waitcursor pour tous les contrôles enfants. Le problème avec ceci est également le même qu'avec le niveau application Application.usewaitcursor, alors que vous n'êtes pas sur le formulaire/formulaires avec votre curseur aucun message WM_SETCURSOR n'est envoyé par windows, donc si vous démarrez un synchrone prenant du temps opération avant de déplacer votre souris sur la zone du formulaire, le formulaire ne peut traiter ce message que lorsque l'opération synchrone prend du temps.
(je ne suggérera pas des tâches chronophages en cours d'exécution dans le thread d'interface utilisateur du tout, surtout c'est ce qui est à l'origine de la question ici)
J'ai fait une petite amélioration sur la réponse de Hans Passant, de sorte que le sablier peut être situé sur le niveau d'application ou le niveau de forme, ce qui évite également InvalidOperationException des appels opération filetée croix:
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);
}
Pour l'utiliser au niveau de l'application:
try
{
HourGlass.ApplicationEnabled = true;
//time consuming synchronous task
}
finally
{
HourGlass.ApplicationEnabled = false;
}
Pour l'utiliser au niveau de la forme que vous pouvez utiliser pour la forme active actuelle:
using (new HourGlass())
{
//time consuming synchronous task
}
ou vous pouvez initialiser une variable locale sous la forme comme ceci:
public readonly HourGlass hourglass;
public Form1()
{
InitializeComponent();
hourglass = new HourGlass(this, false);
}
et de l'utiliser plus tard dans un try catch finally block
J'ai rencontré un cas lors de la combinaison avec un écran de démarrage qui provoquerait une InvalidOperationException - "Opération inter-thread non valide". Ajouter un! F.InvokeRequired entre f! = Null et f.Handle! = Null a résolu le problème. –
Cela fonctionne très bien pour moi, mais selon ReSharper, "Expression est toujours vrai" sur cette ligne: si (f! = Null && f.Handle! = Null) // Envoyer WM_SETCURSOR –
Ceci est une classe auxiliaire AWESOME. A travaillé quand rien d'autre ne l'a fait. – KeithS