Cursor.Current vs. this.Cursor

C’è una differenza tra Cursor.Current e this.Cursor (dove this è un WinForm) in .Net? Ho sempre usato this.Cursor e ho avuto molta fortuna con esso, ma recentemente ho iniziato a usare CodeRush e ho appena incorporato del codice in un blocco “Wait Cursor” e CodeRush ha usato la proprietà Cursor.Current . Ho visto su Internet e al lavoro dove altri programmatori hanno avuto problemi con la proprietà Cursor.Current . Mi sono solo chiesto se c’è una differenza nei due. Grazie in anticipo.

Ho fatto un piccolo test. Ho due winforms. Faccio clic su un pulsante su form1, impostiamo la proprietà Cursors.WaitCursor su Cursors.WaitCursor e quindi mostriamo form2. Il cursore non cambia in nessuna delle due forms. Rimane Cursors.Default (puntatore) cursore.

Se si imposta this.Cursor su Cursors.WaitCursor nell’evento click button su form1 e mostra form2, il cursore di attesa viene visualizzato solo su form1 e il cursore predefinito è su form2, che è previsto. Quindi, non so ancora cosa fa Cursor.Current .

Windows invia la finestra che contiene il cursore del mouse sul messaggio WM_SETCURSOR, dandogli l’opportunità di cambiare la forma del cursore. Ne approfitta un controllo come TextBox, cambiando il cursore in una I-bar. La proprietà Control.Cursor determina quale forma verrà utilizzata.

La proprietà Cursor.Current modifica la forma direttamente, senza attendere una risposta WM_SETCURSOR. Nella maggior parte dei casi, è improbabile che tale forma possa sopravvivere a lungo. Non appena l’utente sposta il mouse, WM_SETCURSOR lo cambia in Control.Cursor.

La proprietà UseWaitCursor è stata aggiunta in .NET 2.0 per semplificare la visualizzazione di una clessidra. Sfortunatamente, non funziona molto bene. Richiede un messaggio WM_SETCURSOR per modificare la forma e ciò non accadrà quando si imposta la proprietà su true e quindi si esegue qualcosa che richiede un po ‘di tempo. Prova questo codice per esempio:

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

Il cursore non cambia mai. Per distruggerlo, dovrai utilizzare anche Cursor.Current. Ecco una piccola class di supporto per semplificare:

 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); } 

E usalo in questo modo:

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

Credo che Cursor.Current sia il cursore del mouse attualmente in uso (indipendentemente da dove si trova sullo schermo), mentre questo cursore è il cursore su cui verrà impostato quando il mouse passa sopra la finestra.

In realtà se si desidera utilizzare HourGlass da un altro thread che restituirà un’eccezione di cross-threading in quanto si sta tentando di accedere a f.Handle da thread diversi rispetto a quelli creati in origine. Utilizzare GetForegroundWindow () invece da user32.dll.

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

e poi

 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); } } 

this.Cursor è il cursore che verrà utilizzato quando il mouse si trova sopra la finestra a cui fa riferimento. Cursor.Current è il cursore del mouse corrente, che potrebbe essere diverso da this.Cursor se il mouse si trova su una finestra diversa.

Ho notato una cosa interessante nell’impostare i cursori, quindi vorrei chiarire alcuni fraintendimenti che ho avuto prima e spero che possa aiutare anche gli altri:

Quando si tenta di impostare il cursore di un modulo utilizzando

this.cursor = Cursors.Waitcursor

in realtà si imposta il cursore per il controllo e non l’intera forma poiché il cursore è proprietà della class Control.

Ovviamente, il cursore verrà modificato solo sul cursore dato quando il mouse si trova effettivamente sul controllo effettivo (esplicitamente l’area del modulo)

Come Hans Passant ha già affermato che:

Windows invia la finestra che contiene il cursore del mouse sul messaggio WM_SETCURSOR, dandogli l’opportunità di cambiare la forma del cursore

Non so se Windows invia i messaggi direttamente ai controlli o se il modulo inoltra tali messaggi ai suoi controlli figlio in base alla posizione del mouse, molto probabilmente indovinerei sul primo metodo da quando ho recuperato i messaggi con WndProc che sovrascrive il modulo controllo, quando ero sopra la casella di testo, ad esempio, il modulo non ha elaborato alcun messaggio. (per favore qualcuno dia chiarezza su questo)

Fondamentalmente il mio suggerimento sarebbe quello di risiedere dall’uso di this.cursor anche e attenersi a this.usewaitcursor, dal momento che cambia la proprietà del cursore su waitcursor per tutti i controlli figlio.

Il problema con questo è anche lo stesso del livello applicazione Application.usewaitcursor, mentre non si è sopra il form / form con il cursore, nessun messaggio WM_SETCURSOR viene inviato da Windows, quindi se si avvia un’operazione che richiede tempo sincrono prima di spostare il proprio mouse sopra l’area del modulo, il modulo può elaborare tale messaggio solo quando termina l’operazione sincrona che richiede tempo.

(Non suggerirei di eseguire attività che richiedono tempo nel thread dell’interfaccia utente, principalmente questo è ciò che sta causando il problema qui)

Ho apportato alcuni miglioramenti alla risposta di Hans Passant, pertanto la clessidra può essere impostata sia a livello di applicazione che a livello di modulo, evitando inoltre InvalidOperationException dalle chiamate con operazioni su thread incrociati:

 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); } 

Per usarlo a livello di applicazione:

 try { HourGlass.ApplicationEnabled = true; //time consuming synchronous task } finally { HourGlass.ApplicationEnabled = false; } 

Per utilizzarlo a livello di modulo puoi utilizzare per il modulo attivo corrente:

 using (new HourGlass()) { //time consuming synchronous task } 

oppure puoi inizializzare una variabile locale nel modulo in questo modo:

 public readonly HourGlass hourglass; public Form1() { InitializeComponent(); hourglass = new HourGlass(this, false); } 

e usarlo più tardi in un tentativo di catturare finalmente il blocco

Questo funziona perfettamente per me quando LongRunningOperation () sta elaborando i messaggi.

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

Da VB.net VS 2012

 Windows.Forms.Cursor.Current = Cursors.Default