2008-11-19 10 views
52

Y a-t-il une différence entre Cursor.Current et this.Cursor (où this est un WinForm) dans .Net? J'ai toujours utilisé this.Cursor et j'ai eu beaucoup de chance, mais j'ai récemment commencé à utiliser CodeRush et j'ai juste intégré du code dans un bloc "Wait Cursor" et CodeRush a utilisé la propriété Cursor.Current. J'ai vu sur Internet et au travail où d'autres programmeurs ont eu des problèmes avec la propriété Cursor.Current. Cela m'a juste amené à me demander s'il y avait une différence entre les deux. Merci d'avance.Cursor.Currentor this.Cursor

J'ai fait un petit test. J'ai deux winforms. Je clique sur un bouton sur formulaire1, définissez la propriété Cursor.Current sur Cursors.WaitCursor, puis affichez form2. Le curseur ne change pas sur les deux formes. Il reste Cursors.Default (pointeur) curseur. Si je mets this.Cursor à Cursors.WaitCursor dans l'événement de clic de bouton sur form1 et show form2, le curseur d'attente n'apparaît que sur form1 et le curseur par défaut est sur form2 ce qui est attendu. Donc, je ne sais toujours pas ce que fait Cursor.Current.

Répondre

84

Windows envoie à la fenêtre contenant le curseur de la souris le message WM_SETCURSOR, lui donnant l'opportunité de changer la forme du curseur. Un contrôle comme TextBox tire parti de cela, en changeant le curseur en I-bar. La propriété Control.Cursor détermine quelle forme sera utilisée.

La propriété Cursor.Current modifie directement la forme, sans attendre une réponse WM_SETCURSOR. Dans la plupart des cas, il est peu probable que cette forme survive longtemps. Dès que l'utilisateur déplace la souris, WM_SETCURSOR la ​​replace dans Control.Cursor.

La propriété UseWaitCursor a été ajoutée dans .NET 2.0 pour faciliter l'affichage d'un sablier. Malheureusement, cela ne fonctionne pas très bien. Il nécessite un message WM_SETCURSOR pour modifier la forme et cela ne se produira pas lorsque vous définissez la propriété sur true, puis que vous effectuez une opération qui prend un certain temps. Essayez ce code par exemple:

private void button1_Click(object sender, EventArgs e) { 
    this.UseWaitCursor = true; 
    System.Threading.Thread.Sleep(3000); 
    this.UseWaitCursor = false; 
} 

Le curseur ne change jamais. Pour le mettre en forme, vous devez également utiliser Cursor.Current. Voici une petite classe d'aide pour le rendre facile:

using System; 
using System.Windows.Forms; 

public class HourGlass : IDisposable { 
    public HourGlass() { 
    Enabled = true; 
    } 
    public void Dispose() { 
    Enabled = false; 
    } 
    public static bool Enabled { 
    get { return Application.UseWaitCursor; } 
    set { 
     if (value == Application.UseWaitCursor) return; 
     Application.UseWaitCursor = value; 
     Form f = Form.ActiveForm; 
     if (f != null && f.Handle != IntPtr.Zero) // Send WM_SETCURSOR 
     SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1); 
    } 
    } 
    [System.Runtime.InteropServices.DllImport("user32.dll")] 
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); 
} 

Et l'utiliser comme ceci:

private void button1_Click(object sender, EventArgs e) { 
    using (new HourGlass()) { 
    System.Threading.Thread.Sleep(3000); 
    } 
} 
+1

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. –

+0

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 –

+0

Ceci est une classe auxiliaire AWESOME. A travaillé quand rien d'autre ne l'a fait. – KeithS

10

Je crois que Cursor.Current est le curseur de la souris en cours d'utilisation (quel que soit l'endroit où il se trouve sur l'écran), tandis que this.Cursor est le curseur sur lequel il sera défini lorsque la souris passe sur votre fenêtre.

+0

Cela semble être ** pas vrai **. J'ai créé un exemple d'application pour le vérifier et il semble que 'System.Windows.Cursors.Current' est mis à jour uniquement lorsque le changement de curseur est associé à la fenêtre de l'application. –

+0

La différence est que 'this.Cursor' n'est pas mis à jour même si le curseur se trouve sur un contrôle enfant d'une fenêtre ou sur une zone non-client de la fenêtre. Désolé pour les deux commentaires suivants, le temps alloué pour l'édition du premier est terminé. –

5

this.Cursor est le curseur qui sera utilisé lorsque la souris est au-dessus de la fenêtre référencée par this. Cursor.Current est le curseur de la souris en cours, qui peut être différent de this.Cursor si la souris est sur une fenêtre différente.

6

En fait, si vous souhaitez utiliser HourGlass d'un autre thread qui vous redonneront cross-threading exception parce que vous essayez d'accéder à f.Handle à partir de différents threads que la forme a été créée à l'origine. Utilisez GetForegroundWindow() à la place de user32.dll.

[DllImport("user32.dll")] 
private static extern IntPtr GetForegroundWindow(); 

puis

public static bool Enabled 
{ 
    get 
    { 
     return Application.UseWaitCursor; 
    } 

    set 
    { 
     if (value == Application.UseWaitCursor) 
     { 
      return; 
     } 

     Application.UseWaitCursor = value; 
     var handle = GetForegroundWindow(); 
     SendMessage(handle, 0x20, handle, (IntPtr)1); 
    } 
} 
+0

Désolé! J'ai eu une erreur d'exécution mais c'était de ma faute! J'ai manqué le '[DllImport (" user32.dll ")]' en haut de la signature de la méthode. Cela fonctionne très bien! –

+0

C'est la seule solution qui a fonctionné de manière fiable pour ma situation croisée. –

0

Cela fonctionne très bien pour moi quand le LongRunningOperation() est le traitement des messages.

private void btnDoLongRunningOperation_Click(object sender, System.EventArgs e) 
{ 
    this.Cursor = Cursors.WaitCursor; 
    LongRunningOperation(); 
    this.Cursor = Cursors.Arrow; 
} 
+1

Que faire si l'opération LongRunningOperation échoue? Devrait au moins essayer/enfin ici. De même, que se passe-t-il si le curseur n'est pas une flèche pour commencer, par ex. vous êtes dans un TextBox? –

0

De VB.net VS 2012

Windows.Forms.Cursor.Current = Cursors.Default 
+0

Je ne suis pas sûr de savoir comment cela répond à la question principale. Pouvez-vous clarifier? –

+0

c'est juste le moyen d'accéder au curseur actuel à partir de la classe Cursor, pas de la propriété Cursor. – Elian

2

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