在应用程序忙时显示沙漏

对于使用WPF构build的视图,我想在应用程序繁忙且无响应时将鼠标光标更改为沙漏。

一个解决scheme是添加

this.Cursor = Cursors.Wait; 

到所有可能导致界面无响应的地方。 但显然这不是最好的解决scheme。 我想知道什么是达到这个目标的最好方法?

是否有可能通过使用样式或资源来实现这一点?

谢谢,

我们做了一个一次性的类,当应用程序需要很长时间时,为我们改变光标,它看起来像这样:

 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 } 

我们这样使用它:

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

可能不是最好的devise,但它的确有诀窍=)

我用这里的答案来构build对我更好的东西。 问题是,当Carlo的答案中的使用块完成时,用户界面可能仍然是忙于数据绑定。 可能会有延迟加载的数据或事件由于在块中完成而触发。 在我的情况下,有时从等待消失了几秒钟,直到UI实际上准备好了。 我通过创build一个设置waitcursor的帮助器方法解决了这个问题,并且还负责设置一个定时器,当UI准备就绪时会自动设置光标。 我不能确定这种devise在任何情况下都能正常工作,但对我来说却是奏效的:

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

最好的办法是不要让用户界面变得无法响应,把所有的工作都卸载到其他线程/任务上。

除此之外,你在catch-22中还是很有用的:如果你添加了一个方法来检测ui是否是无响应的,那么没有什么好的办法来改变光标,因为你需要做的地方偶数线程)是不响应的…你也许可以通过标准的win32代码来改变整个窗口的光标了吗?

否则,你必须事先做好,就像你的问题所暗示的那样。

在这里要小心,因为摆弄等待游标可能会导致STA线程的一些问题。 确保如果你在自己的线程中使用这个东西。 我在这里发布了一个例子运行在一个STA使用这个来显示一个WaitCursor而产生的文件启动时,并没有炸掉(主应用程序)AFAICT。

我personnaly更喜欢不看鼠标指针从沙漏到箭头多次切换。 为了帮助防止这种行为,同时调用需要一段时间的embedded式函数,并且每个都尝试控制鼠标指针,我使用一个称为LifeTrackerStack的堆栈(计数器)。 只有当堆栈是空的(反对0),我把沙漏后退到箭头。

我也使用MVVM。 我也更喜欢线程安全的代码。

在我的模型的根类中,我声明了我的LifeTrackerStack,可以将它填充到子模型类中,也可以直接从子模型类中使用它们。

我的人生追踪者有两个州/行动:

  • 活(counter> 0)=>将Model.IsBusy设置为true;
  • 完成(counter == 0)=>将Model.IsBusy设置为false;

然后在我看来,我手动绑定到我的Model.IsBusy并执行:

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

这是我的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; } } } } 

它的用法:

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

我长时间慢跑的地方是:

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

这个对我有用。 希望它可以帮助任何! 埃里克

更改光标并不意味着长时间运行任务完成后,应用程序不会响应鼠标和键盘事件。 为了避免用户的误导,我使用下面的类来删除应用程序消息队列中的所有键盘和鼠标消息。

 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 }