2010-08-13 36 views
73

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

一种解决方案是

this.Cursor = Cursors.Wait; 

增加,可能造成用户界面无响应的所有地方。但显然这不是最好的解决方案。我想知道什么是实现这一目标的最佳方式?

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

感谢,

回答

176

我们做了一次性类,当应用程序是要多久,我们改变光标,它看起来像这样:

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 
} 

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

+2

做得好使用IDisposable的!确保我们总是返回到前一个光标的一种好方法。 – 2010-08-13 23:56:35

+2

前段时间我的想法完全一样。但是,我将代码作为私有类包装在UI服务外观类中,并通过“ShowWaitCursor”方法返回它的实例。所以你必须这样做:'使用(uiServices.ShowWaitCursor())'。看起来很麻烦,但它减轻了单元测试。 – Konamiman 2011-03-23 10:52:18

+0

有点offtopic:如何实现正确处置... http://msdn.microsoft.com/en-us/library/ms244737.aspx – 2013-11-17 11:38:12

6

最好办法是不造成用户界面无响应不断,卸载所有的工作给其他线程/任务合适。

除此之外,您在catch-22中很有趣:如果您添加了一种方法来检测ui是否是无响应的,则没有好方法来更改光标,因为您需要的地方要做到这一点(偶数线程)是无响应的......你也许可以通过标准的win32代码来改变整个窗口的光标,但是?

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

33

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

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

+1!在那里最干净的实现,在一行代码中完美工作。奖励! – Hannish 2013-01-02 10:56:04

+0

+1非常好的代码,你将如何实现相同的C#Winforms。相反:Cursor.Current = Cursors.WaitCursor; // busy Cursor.Current = Cursors.Default; – Zeeshanef 2013-10-12 16:11:31

+0

+1非常干净,简单和自动化的设计。如果您正在寻找应用程序繁忙的解决方案,请使用T.J.的代码。在Helper类中抛出并在运行长操作之前使用它: Helpers.UiServices.SetBusyState(); – 2015-11-14 09:53:28

3

这里要小心,因为与等待光标摆弄可能会导致一些问题,STA线程。确保如果你使用这个东西,你正在它自己的线程内完成它。 我在这里发布了一个示例Run inside an STA,它在生成的文件启动时使用它来显示WaitCursor,并且不会爆炸(主应用程序)AFAICT。

1

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

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 

} 
3

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

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

在我的根类模型,我宣布我的LifeTrackerStack,我无论是在孩子的模型类填充或当我从他们访问它直接从子模型类使用。

我生活跟踪器具有2个状态/操作:

  • ALIVE(计数器> 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 
     } 

这对我的作品。 希望它可以帮助任何! 埃里克

相关问题