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

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

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

 this.Cursor = Cursors.Wait;

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

Можно ли добиться этого с помощью стилей или ресурсов?

Спасибо,

Ответ 1

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

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

Я использовал ответы здесь, чтобы создать что-то, что сработало лучше для меня. Проблема в том, что, когда заканчивается использование блока в ответе Carlo, пользовательский интерфейс может быть по-прежнему занят привязкой данных. Возможно, в результате того, что было сделано в блоке, могут быть запущены ленивые данные или события. В моем случае это иногда занимало несколько секунд, так как ожидающий курсор исчез до того момента, когда пользовательский интерфейс был фактически готов. Я решил это, создав вспомогательный метод, который устанавливает 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();
                }
        }
    }

Ответ 3

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

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

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

Чтобы очистить курсор переопределения, установите для параметра OverrideCursor значение null.

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

Ответ 4

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

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

В противном случае вам придется делать это заранее, как ваш вопрос.

Ответ 5

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

Ответ 6

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

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

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

У моей жизни трекер есть 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));
        }
    }

Это мой класс 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
        }

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

Ответ 7

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

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

}

Ответ 8

Я использовал решение Olivier Jacot-Descombes, это очень просто и хорошо работает. Благодарю. update: он работает даже без использования другого потока/фона рабочего.

Я использую его с 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
}