отображение песочных часов, когда приложение занято


для просмотра построенных с использованием WPF, я хочу изменить курсор мыши на песочные часы, когда приложение занято и не отвечает.

одним из решений является добавление

 this.Cursor = Cursors.Wait;

для всех мест, которые могут привести к тому, что пользовательский интерфейс станет невосприимчивым. Но очевидно, что это не лучшее решение. Мне интересно, каков наилучший способ достичь этого?

можно ли достичь этого с помощью стилей или ресурсы?

спасибо,

8 78

8 ответов:

мы сделали одноразовый класс, который меняет курсор для нас, когда приложение будет занимать много времени, это выглядит так:

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
}

может быть, не самый лучший дизайн, но он делает трюк =)

я использовал ответы здесь, чтобы построить что-то, что работало лучше для меня. Проблема в том, что когда блок using в ответе Карло заканчивается, пользовательский интерфейс может быть все еще занят привязкой данных. В результате того, что было сделано в блоке, могут быть запущены лениво загруженные данные или события. В моем случае это иногда занимало несколько секунд от waitcursor исчез, пока пользовательский интерфейс не был фактически готов. Я решил ее создать вспомогательный метод, который устанавливает waitcursor, а также заботится о создании таймер, который автоматически установит курсор обратно, когда пользовательский интерфейс будет готов. Я не могу быть уверен, что этот дизайн будет работать во всех случаях, но он работал для меня:

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

The лучшие способ состоял бы в том, чтобы не заставлять пользовательский интерфейс становиться невосприимчивым когда-либо, выгружая всю работу в другие потоки/задачи по мере необходимости.

кроме этого, вы kindof в catch-22: если вы добавили способ определить, что пользовательский интерфейс не реагирует, нет хорошего способа изменить курсор, так как место, которое вам нужно будет сделать (четный поток), не реагирует... Возможно, вы сможете перейти на стандартный код win32, чтобы изменить курсор для всего окно, правда?

в противном случае, вы должны были бы сделать это заранее, как предполагает ваш вопрос.

будьте осторожны здесь, потому что возня с курсором ожидания может вызвать некоторые проблемы с потоками STA. Убедитесь, что если вы используете эту вещь, что вы делаете это в своем собственном потоке. Я разместил пример здесь запуск внутри STA который использует это, чтобы показать WaitCursor, пока порожденный файл запускается, и не взрывается (основное приложение) AFAICT.

Я лично предпочитаю не видеть, чтобы указатель мыши переключался много раз с песочных часов на стрелку. Чтобы предотвратить такое поведение при вызове встроенных функций, которые занимают некоторое время и каждый пытается управлять указателем мыши, я использую стек (счетчик), который я называю LifeTrackerStack. И только когда стек пуст (счетчик до 0), что я поставил обратно песочные часы на стрелку.

Я также использую MVVM. Я также предпочитаю потокобезопасный код.

в моем корневом классе модели я объявляю свой LifeTrackerStack, который я либо заполняю в классах моделей childs, либо использую непосредственно из классов дочерних моделей, когда у меня есть доступ к ним.

my life tracker имеет 2 состояния / действия:

  • живой (счетчик > 0) = > модель поворота.IsBusy к истине;
  • готово (счетчик == 0) => модель поворота.IsBusy ложь;

затем, на мой взгляд, я вручную привязываюсь к своей модели.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
        }

это работает для меня. Надеюсь, что это может помочь любому! Эрик

Я просто делаю

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

согласно документации мышь.Свойство OverrideCursor

снимите флажок переопределить курсор, установить OverrideCursor к нулю.

оператор try-finally гарантирует, что курсор по умолчанию будет восстановлен в любом случае, будь то исключение или часть try останется с return или break (если внутри цикла).

изменение курсора не означает, что приложение не будет реагировать на события мыши и клавиатуры после длительных задач закончила. Чтобы избежать пропуска пользователя, я использую класс ниже, который удаляет все сообщения клавиатуры и мыши из очереди сообщений приложения.

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

}

я использовал решение Оливье Жако-Декомба, это очень просто и хорошо работает. спасибо. обновление:он даже хорошо работает без использования разных потоков / фонового рабочего.

Я использую его с backgroudworker, курсор мыши отлично смотрится, когда он занят работой и возвращается в нормальное состояние, когда работа выполнена.

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
}