отображать песочные часы, когда приложение занято - PullRequest
86 голосов
/ 14 августа 2010

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

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

 this.Cursor = Cursors.Wait;

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

Можно ли добиться этого, используя стили или ресурсы?

Спасибо,

Ответы [ 8 ]

205 голосов
/ 14 августа 2010

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

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
}

Возможно, это не лучший дизайн, но он делает свое дело =)

37 голосов
/ 20 сентября 2011

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

    /// <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();
                }
        }
    }
6 голосов
/ 14 августа 2010

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

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

5 голосов
/ 26 марта 2018

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

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

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

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

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

3 голосов
/ 24 мая 2013

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

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

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

Мой жизненный трекер имеет 2 состояния / действия:

  • Alive (counter> 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
        }

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

3 голосов
/ 13 апреля 2012

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

1 голос
/ 08 апреля 2018

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

Я использую его с 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
}
1 голос
/ 28 марта 2013

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

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

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...