应用程序繁忙时显示沙漏

sea*_*717 88 c# wpf

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

一种解决方案是添加

 this.Cursor = Cursors.Wait;
Run Code Online (Sandbox Code Playgroud)

到所有可能导致UI无响应的地方.但显然这不是最好的解决方案.我想知道实现这个目标的最佳途径是什么?

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

谢谢,

Car*_*rlo 211

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

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
}
Run Code Online (Sandbox Code Playgroud)

我们这样使用它:

using(new WaitCursor())
{
    // very long task
}
Run Code Online (Sandbox Code Playgroud)

可能不是最伟大的设计,但它的诀窍=)

  • 使用IDisposable做得好!确保我们始终返回上一个游标的好方法. (3认同)
  • 我前段时间的想法完全相同.但是我将代码作为私有类包装在UI服务外观类中,并通过"ShowWaitCursor"方法返回它的实例.所以你必须这样做:`using(uiServices.ShowWaitCursor())`.看起来很麻烦,但它简化了单元测试. (2认同)
  • @AnoushkaSeechurn WaitCursor()不是一个方法,它是一个构造函数.我猜你把这个课改名为'WaitCursor'以外的东西? (2认同)

T.J*_*aer 38

我用这里的答案来构建一些对我来说更好的东西.问题是,当Carlo的答案中的使用块完成时,UI实际上可能仍然忙于数据绑定.由于在块中执行的操作可能会触发延迟加载的数据或事件.在我的情况下,有时从等待标记消失几秒钟,直到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();
                }
        }
    }
Run Code Online (Sandbox Code Playgroud)


Oli*_*bes 8

我只是在做

Mouse.OverrideCursor = Cursors.Wait;
try {
    // Long lasting stuff ...
} finally {
    Mouse.OverrideCursor = null;
}
Run Code Online (Sandbox Code Playgroud)

根据Mouse.OverrideCursor Property的文档

要清除覆盖Cursor,请将OverrideCursor设置为null.

try-finally语句确保在任何情况下都恢复默认光标,无论是发生异常还是尝试部分保留returnbreak(如果在循环内).


Joh*_*ner 6

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

除此之外,你很喜欢catch-22:如果你确实添加了一种检测ui无响应的方法,那么就没有好办法改变光标,因为你需要这样做的地方(偶数线程)是无响应的...你可能能够解决标准的win32代码来改变整个窗口的光标吗?

否则,你必须先发制人,就像你的问题所暗示的那样.


Eri*_*let 5

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

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

在我的模型根类中,我声明了 LifeTrackerStack,当我可以从它们访问它时,我要么填充到子模型类中,要么直接从子模型类中使用。

我的生活追踪器有 2 个状态/动作:

  • 活着(计数器 > 0)=> 将 Model.IsBusy 变为 true;
  • 完成(计数器 == 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));
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我的类 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以及它的用法:

    _busyStackLifeTracker = new LifeTrackerStack
        (
            () =>
            {
                this.IsBusy = true;
            },
            () =>
            {
                this.IsBusy = false;
            }
        );
Run Code Online (Sandbox Code Playgroud)

在我长时间慢跑的任何地方,我都会:

        using (this.BusyStackLifeTracker.GetNewLifeTracker())
        {
            // long job
        }
Run Code Online (Sandbox Code Playgroud)

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