2010-08-13 73 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 
     } 

這對我的作品。 希望它可以幫助任何! 埃裏克