visual tutorial studio pagina framework examples application wpf

tutorial - wpf vs winforms



muestra el reloj de arena cuando la aplicación está ocupada (8)

Cambiar el cursor no significa que la aplicación no responderá a los eventos del mouse y del teclado una vez que la tarea de ejecución larga haya finalizado. Para evitar el error de los usuarios, utilizo la clase a continuación que elimina todos los mensajes del teclado y el mouse de la lista de mensajes de la aplicación.

using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Input; public class WpfHourGlass : IDisposable { [StructLayout(LayoutKind.Sequential)] private struct POINTAPI { public int x; public int y; } [StructLayout(LayoutKind.Sequential)] private struct MSG { public int hwnd; public int message; public int wParam; public int lParam; public int time; public POINTAPI pt; } private const short PM_REMOVE = 0x1; private const short WM_MOUSELAST = 0x209; private const short WM_MOUSEFIRST = 0x200; private const short WM_KEYFIRST = 0x100; private const short WM_KEYLAST = 0x108; [DllImport("user32", EntryPoint = "PeekMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] private static extern int PeekMessage([MarshalAs(UnmanagedType.Struct)] ref MSG lpMsg, int hwnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg); public WpfHourGlass() { Mouse.OverrideCursor = Cursors.Wait; bActivated = true; } public void Show(bool Action = true) { if (Action) { Mouse.OverrideCursor = Cursors.Wait; } else { Mouse.OverrideCursor = Cursors.Arrow; } bActivated = Action; } #region "IDisposable Support" // To detect redundant calls private bool disposedValue; private bool bActivated; // IDisposable protected virtual void Dispose(bool disposing) { if (!this.disposedValue) { if (disposing) { //remove todas as mensagens de mouse //e teclado que tenham sido produzidas //durante o processamento e estejam //enfileiradas if (bActivated) { MSG pMSG = new MSG(); while (Convert.ToBoolean(PeekMessage(ref pMSG, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))) { } while (Convert.ToBoolean(PeekMessage(ref pMSG, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))) { } Mouse.OverrideCursor = Cursors.Arrow; } } // TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. // TODO: set large fields to null. } this.disposedValue = true; } public void Dispose() { // Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(true); GC.SuppressFinalize(this); } #endregion }

Para una vista construida con WPF, quiero cambiar el cursor del mouse a un reloj de arena cuando la aplicación está ocupada y no responde.

Una solución es agregar

this.Cursor = Cursors.Wait;

a todos los lugares que pueden causar que la UI no responda. Pero obviamente esta no es la mejor solución. Me pregunto cuál es la mejor manera de lograr esto?

¿Es posible lograr esto usando estilos o recursos?

Gracias,


Hicimos una clase desechable que cambia el cursor para nosotros cuando la aplicación va a tomar mucho tiempo, se ve así:

public class WaitCursor : IDisposable { private Cursor _previousCursor; public WaitCursor() { _previousCursor = Mouse.OverrideCursor; Mouse.OverrideCursor = Cursors.Wait; } #region IDisposable Members public void Dispose() { Mouse.OverrideCursor = _previousCursor; } #endregion }

Y lo usamos así:

using(new WaitCursor()) { // very long task }

Puede que no sea el mejor diseño, pero sí el truco =)


La mejor manera sería no hacer que la IU deje de responder, descargando todo el trabajo a otros hilos / tareas, según corresponda.

Aparte de eso, eres amable en un catch-22: si agregaste una forma de detectar que el ui no responde, no hay una buena manera de cambiar el cursor, como el lugar donde lo necesitarías ( el hilo par) no responde ... Sin embargo, ¿es posible que pueda localizar el código estándar de win32 para cambiar el cursor de toda la ventana?

De lo contrario, tendrías que hacerlo preventivamente, como tu pregunta sugiere.


Personalmente prefiero no ver el puntero del mouse cambiando muchas veces del reloj de arena a la flecha. Para ayudar a evitar ese comportamiento al llamar funciones incorporadas que tardan un poco y cada una intenta controlar el puntero del mouse, uso una pila (contador) que llamo LifeTrackerStack. Y solo cuando la pila está vacía (en oposición a 0), ajusto el reloj de arena a una flecha.

También uso MVVM. También prefiero el código seguro de subprocesos.

En mi clase de raíz del modelo, declaro mi LifeTrackerStack que completé en las clases de modelos secundarios o las utilicé directamente desde las clases de modelos secundarios cuando tengo acceso a ellas desde ellas.

Mi rastreador de vida tiene 2 estados / acciones:

  • Alive (contador> 0) => convierte Model.IsBusy en verdadero;
  • Done (contador == 0) => convierte Model.IsBusy en falso;

Entonces, en mi opinión, me enlace manualmente a mi Model.IsBusy y hago:

void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "IsBusy") { if (this._modelViewAnalysis.IsBusy) { if (Application.Current.Dispatcher.CheckAccess()) { this.Cursor = Cursors.Wait; } else { Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = Cursors.Wait)); } } else { Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = null)); } }

Esta es mi clase LifeTrackerStack:

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace HQ.Util.General { /// <summary> /// Usage is to have only one event for a recursive call on many objects /// </summary> public class LifeTrackerStack { // ****************************************************************** protected readonly Action _stackCreationAction; protected readonly Action _stackDisposeAction; private int _refCount = 0; private object _objLock = new object(); // ****************************************************************** public LifeTrackerStack(Action stackCreationAction = null, Action stackDisposeAction = null) { _stackCreationAction = stackCreationAction; _stackDisposeAction = stackDisposeAction; } // ****************************************************************** /// <summary> /// Return a new LifeTracker to be used in a ''using'' block in order to ensure reliability /// </summary> /// <returns></returns> public LifeTracker GetNewLifeTracker() { LifeTracker lifeTracker = new LifeTracker(AddRef, RemoveRef); return lifeTracker; } // ****************************************************************** public int Count { get { return _refCount; } } // ****************************************************************** public void Reset() { lock (_objLock) { _refCount = 0; if (_stackDisposeAction != null) { _stackDisposeAction(); } } } // ****************************************************************** private void AddRef() { lock (_objLock) { if (_refCount == 0) { if (_stackCreationAction != null) { _stackCreationAction(); } } _refCount++; } } // ****************************************************************** private void RemoveRef() { bool shouldDispose = false; lock (_objLock) { if (_refCount > 0) { _refCount--; } if (_refCount == 0) { if (_stackDisposeAction != null) { _stackDisposeAction(); } } } } // ****************************************************************** } } using System; namespace HQ.Util.General { public delegate void ActionDelegate(); public class LifeTracker : IDisposable { private readonly ActionDelegate _actionDispose; public LifeTracker(ActionDelegate actionCreation, ActionDelegate actionDispose) { _actionDispose = actionDispose; if (actionCreation != null) actionCreation(); } private bool _disposed = false; public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this._disposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { _actionDispose(); } // Note disposing has been done. _disposed = true; } } } }

Y el uso de eso:

_busyStackLifeTracker = new LifeTrackerStack ( () => { this.IsBusy = true; }, () => { this.IsBusy = false; } );

Donde sea que tenga un largo trote, lo hago:

using (this.BusyStackLifeTracker.GetNewLifeTracker()) { // long job }

Esto funciona para mi. Espero que pueda ayudar a cualquiera! Eric


Simplemente estoy haciendo

Mouse.OverrideCursor = Cursors.Wait; try { // Long lasting stuff ... } finally { Mouse.OverrideCursor = null; }

De acuerdo con la documentación de Mouse.OverrideCursor Property

Para borrar el Cursor de anulación, establezca OverrideCursor en nulo.

La instrucción try-finally asegura que el cursor predeterminado se restaure en cualquier caso, ya sea que se produzca una excepción o que la parte try se quede con return o break (si está dentro de un bucle).


Tenga cuidado aquí porque jugar con el Cursor de espera puede causar algunos problemas con los hilos de STA. Asegúrate de que si usas esto, lo haces dentro de su propio hilo. Publiqué un ejemplo aquí Ejecutar dentro de una STA que usa esto para mostrar un WaitCursor mientras el archivo generado se está iniciando, y no explota (la aplicación principal) AFAICT.


Usé las respuestas aquí para construir algo que funcionó mejor para mí. El problema es que cuando termina el bloque de uso en la respuesta de Carlo, la interfaz de usuario podría estar todavía atada a datos. Puede haber datos de carga lenta o activación de eventos como resultado de lo que se hizo en el bloque. En mi caso, a veces transcurrieron varios segundos desde que el buscador desapareció hasta que la interfaz de usuario estuvo realmente lista. Lo resolví creando un método de ayuda que configura el waitcursor y también se encarga de configurar un temporizador que automáticamente establecerá el cursor cuando la interfaz de usuario esté lista. No puedo estar seguro de que este diseño funcione en todos los casos, pero funcionó para mí:

/// <summary> /// Contains helper methods for UI, so far just one for showing a waitcursor /// </summary> public static class UiServices { /// <summary> /// A value indicating whether the UI is currently busy /// </summary> private static bool IsBusy; /// <summary> /// Sets the busystate as busy. /// </summary> public static void SetBusyState() { SetBusyState(true); } /// <summary> /// Sets the busystate to busy or not busy. /// </summary> /// <param name="busy">if set to <c>true</c> the application is now busy.</param> private static void SetBusyState(bool busy) { if (busy != IsBusy) { IsBusy = busy; Mouse.OverrideCursor = busy ? Cursors.Wait : null; if (IsBusy) { new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, Application.Current.Dispatcher); } } } /// <summary> /// Handles the Tick event of the dispatcherTimer control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private static void dispatcherTimer_Tick(object sender, EventArgs e) { var dispatcherTimer = sender as DispatcherTimer; if (dispatcherTimer != null) { SetBusyState(false); dispatcherTimer.Stop(); } } }


Utilicé la solución de Olivier Jacot-Descombes, es muy simple y funciona bien. Gracias. actualización: incluso funciona bien sin utilizar un trabajador de hilos / fondo diferente.

Lo uso con backgroudworker, el cursor del mouse se ve muy bien cuando está ocupado trabajando y vuelve a la normalidad cuando termina el trabajo.

public void pressButtonToDoSomeLongTimeWork() { Mouse.OverrideCursor = Cursors.Wait; // before the long time work, change mouse cursor to wait cursor worker.DoWork += doWorkLongTimeAsync; worker.RunWorkerCompleted += worker_RunWorkerCompleted; worker.RunWorkerAsync(); //start doing some long long time work but GUI can update } private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //long time work is done(); updateGuiToShowTheLongTimeWorkResult(); Mouse.OverrideCursor = null; //return mouse cursor to normal }