відображати Пісочний годинник, коли програма зайнята


90

Для подання, побудованого за допомогою WPF, я хочу змінити курсор миші на пісочний годинник, коли програма зайнята і не реагує.

Одним із рішень є додавання

 this.Cursor = Cursors.Wait;

до всіх місць, які можуть спричинити невідповідність інтерфейсу користувача. Але, очевидно, це не найкраще рішення. Мені цікаво, який найкращий спосіб досягти цього?

Чи можна досягти цього за допомогою стилів чи ресурсів?

Дякую,

Відповіді:


224

Ми зробили одноразовий клас, який змінює нам курсор, коли програма займе багато часу, це виглядає так:

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
}

Можливо, це не найкращий дизайн, але він робить трюк =)


3
Молодці з використанням IDisposable! Хороший спосіб переконатися, що ми завжди повертаємося до попереднього курсору.
Xavier Poinas

2
Я мав точно таку ж ідею деякий час тому. Але я обернув код як приватний клас усередині фасадного класу служб інтерфейсу користувача і повернув його екземпляри методом "ShowWaitCursor". Таким чином , ви повинні були зробити: using(uiServices.ShowWaitCursor()). Виглядає громіздко, але це полегшує модульне тестування.
Konamiman

трохи нетопічно: як правильно реалізувати розпорядження ... msdn.microsoft.com/en-us/library/ms244737.aspx
Майкл Сандер

2
@AnoushkaSeechurn WaitCursor () - це не метод, це конструктор. Я припускаю, що ви перейменували клас на щось інше, ніж "WaitCursor"?
Карло

1
@ JánosTigyi Мені подобається розділяти методи інтерфейсу у своєму регіоні. Питання в тому, чому хак не використовує регіон там? = P
Карло,

40

Я використовував відповіді тут, щоб побудувати щось, що краще для мене працювало. Проблема полягає в тому, що коли блок використання у відповіді Карло закінчується, інтерфейс може насправді бути зайнятий прив'язкою даних. В результаті того, що було зроблено в блоці, можуть бути завантажені ліниві дані чи події. У моєму випадку іноді знадобилося кілька секунд, коли курсор зник, поки інтерфейс фактично не був готовий. Я вирішив це, створивши допоміжний метод, який встановлює курсор очікування, а також дбає про налаштування таймера, який автоматично встановить курсор назад, коли інтерфейс буде готовий. Я не можу бути впевнений, що цей дизайн буде працювати у всіх випадках, але він працював для мене:

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

+1 дуже хороший код, як ви застосуєте те саме для C # Winforms. натомість: Cursor.Current = Cursors.WaitCursor; // зайнятий Cursor.Current = Cursors.Default;
Zeeshanef

+1 Дуже чистий, легкий та автоматизований дизайн. Якщо ви шукаєте рішення, зайняте програмою, використовуйте код TJ. Киньте його в клас Helper і використовуйте його таким чином перед запуском тривалої операції: Helpers.UiServices.SetBusyState ();
Аарон Рід

14

Я просто роблю

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

Відповідно до документації властивості Mouse.OverrideCursor

Щоб очистити курсор заміщення, встановіть для OverrideCursor значення null.

Оператор try-нарешті гарантує, що курсор за замовчуванням буде відновлений у будь-якому випадку, навіть коли виникає виняток або частина спроби залишається з returnабо break(якщо всередині циклу).


Тривале робоче завдання може викликати винятки з різних причин. Це хороший підхід
aggsol

6

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

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

В іншому випадку вам доведеться робити це превентивно, як підказує ваше запитання.


5

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

Я також використовую MVVM. Я також віддаю перевагу безпечному коду.

У своєму кореневому класі моделі я заявляю свій LifeTrackerStack, що я або заповнюю в класах дочірніх моделей, або використовую безпосередньо з дочірніх класів моделі, коли маю до них доступ до них.

Мій трекер життя має 2 стани / дії:

  • Живий (лічильник> 0) => перетворити Model.IsBusy на true;
  • Готово (counter == 0) => перетворити Model.IsBusy на false;

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

Це працює для мене. Сподіваюся, це може допомогти будь-якому! Ерік


@mins, велике спасибі. З таким приємним компліментом мені довелося опублікувати фактичне рішення, яке я використовую. Можливо, є кілька збоїв (я не можу включити всі свої фреймворки), які слід легко виправити (можливо, доведеться видалити якийсь код). Я додав нову відповідь із кращим кодом (на мою думку) та більш повною, яка підходить для більшої кількості ситуацій. Ви можете поглянути, якщо хочете. ;-)
Ерік Уелле

3

Будьте тут обережні, тому що возиння з курсором очікування може спричинити деякі проблеми з потоками STA. Переконайтеся, що якщо ви використовуєте цю річ, то робите це в рамках власної теми. Я розмістив тут приклад Запустити всередині 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

}

1

Я використовував рішення Олів'є Якота-Декомба, воно дуже просте і добре працює. Дякую. оновлення: воно навіть працює добре, не використовуючи інші потоки / фоновий працівник.

Я використовую його з 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
}

0

Я знаю, що запізнився, я просто змінив спосіб керування курсором Пісочний годинник (зайнятий стан) моєї програми.

Це запропоноване рішення є більш складним, ніж моя перша відповідь, але я думаю, що воно є більш повним і кращим.

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

Переваги:

  • Може керувати станом "Зайнято" як з моделі, так і з подання.
  • Здатний керувати зайнятим станом, якщо немає графічного інтерфейсу. Від'єднано від графічного інтерфейсу.
  • Нитка безпечна (може використовуватися з будь-якої нитки)
  • Підтримка заміни зайнятості (тимчасово показати стрілку), коли є вікно (діалогове вікно), яке повинно бути видно посередині дуже довгої транзакції.
  • Здатний складати багато операцій із зайнятою поведінкою, які демонструють постійний пісочний годинник, якщо це частина багатьох невеликих довгих підзавдань. Наявність пісочного годинника, який часто не мінявся б із зайнятого на звичайний зайнятий. Постійний зайнятий стан, якщо це можливо, за допомогою стека.
  • Підтримка субсипції подій із слабким покажчиком, оскільки екземпляр об'єкта "Глобальний" є глобальним (ніколи не буде зібраним сміттям - він викорінений).

Код розділений на кілька класів:

  • Немає класу графічного інтерфейсу: "Глобальний", який управляє станом зайнятості і повинен бути ініціалізований при запуску програми за допомогою диспетчера. Оскільки це глобальний (синглтон), я вирішив мати слабку подію NotifyPropertyChanged, щоб не дотримуватися жорстких рекомендацій щодо тих, хто хоче отримувати повідомлення про будь-які зміни.
  • Клас графічного інтерфейсу: AppGlobal, який підключається до глобального та змінює зовнішній вигляд миші відповідно до стану Gloab.Busy. Його слід ініціалізувати за допомогою диспетчера також при запуску програми.
  • Клас графічного інтерфейсу, який допомагає Dialog (Window) мати належну поведінку миші, коли використовується для тривалої транзакції, коли миша була замінена, щоб показати пісочний годинник і хоче звичайну стрілку, коли використовується Dialog (Window).
  • Код також включає деякі залежності.

Це використання:

В цьому:

public partial class App : Application
{
    // ******************************************************************
    protected override void OnStartup(StartupEventArgs e)
    {
        Global.Init(Application.Current.Dispatcher);
        AppGlobal.Init(Application.Current.Dispatcher);

Бажане використання:

        using (Global.Instance.GetDisposableBusyState())
        {
        ...
        }

Інше використання:

// ******************************************************************
public DlgAddAggregateCalc()
{
    InitializeComponent();
    Model = DataContext as DlgAddAggregateCalcViewModel;
    this.Activated += OnActivated;
    this.Deactivated += OnDeactivated;
}

// ************************************************************************
private void OnDeactivated(object sender, EventArgs eventArgs)
{
    Global.Instance.PullState();
}

// ************************************************************************
private void OnActivated(object sender, EventArgs eventArgs)
{
    Global.Instance.PushState(false);
}

Курсор автоматичного вікна:

public partial class DlgAddSignalResult : Window
{
    readonly WindowWithAutoBusyState _autoBusyState = new WindowWithAutoBusyState();

    // ******************************************************************
    public DlgAddSignalResult()
    {
        InitializeComponent();

        Model = DataContext as DlgAddSignalResultModel;

        _autoBusyState.Init(this);
    }

Код:

// Copyright (c) 2008 Daniel Grunwald
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

/* Usage:
 * 
 * 
 * 
        public delegate void FileChangedHandler(object sender, FileSystemEventArgs e);

        [field: NonSerialized]
        private readonly SmartWeakEvent<FileChangedHandler> _weakFileChanged = new SmartWeakEvent<FileChangedHandler>();

        public event FileChangedHandler FileChanged
        {
            add
            {
                _weakFileChanged.Add(value);
            }
            remove
            {
                _weakFileChanged.Remove(value);
            }
        }
 *
 *
 */


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace HQ.Util.General.WeakEvent
{
    /// <summary>
    /// A class for managing a weak event.
    /// </summary>
    public sealed class SmartWeakEvent<T> where T : class
    {
        struct EventEntry
        {
            public readonly MethodInfo TargetMethod;
            public readonly WeakReference TargetReference;

            public EventEntry(MethodInfo targetMethod, WeakReference targetReference)
            {
                this.TargetMethod = targetMethod;
                this.TargetReference = targetReference;
            }
        }

        readonly List<EventEntry> eventEntries = new List<EventEntry>();

        // EO: Added for ObservableCollectionWeak
        public int CountOfDelegateEntry
        {
            get
            {
                RemoveDeadEntries();
                return eventEntries.Count;
            }
        }

        static SmartWeakEvent()
        {
            if (!typeof(T).IsSubclassOf(typeof(Delegate)))
                throw new ArgumentException("T must be a delegate type");
            MethodInfo invoke = typeof(T).GetMethod("Invoke");
            if (invoke == null || invoke.GetParameters().Length != 2)
                throw new ArgumentException("T must be a delegate type taking 2 parameters");
            ParameterInfo senderParameter = invoke.GetParameters()[0];
            if (senderParameter.ParameterType != typeof(object))
                throw new ArgumentException("The first delegate parameter must be of type 'object'");
            ParameterInfo argsParameter = invoke.GetParameters()[1];
            if (!(typeof(EventArgs).IsAssignableFrom(argsParameter.ParameterType)))
                throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");
            if (invoke.ReturnType != typeof(void))
                throw new ArgumentException("The delegate return type must be void.");
        }

        public void Add(T eh)
        {
            if (eh != null)
            {
                Delegate d = (Delegate)(object)eh;

                if (d.Method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
                    throw new ArgumentException("Cannot create weak event to anonymous method with closure.");

                if (eventEntries.Count == eventEntries.Capacity)
                    RemoveDeadEntries();
                WeakReference target = d.Target != null ? new WeakReference(d.Target) : null;
                eventEntries.Add(new EventEntry(d.Method, target));
            }
        }

        void RemoveDeadEntries()
        {
            eventEntries.RemoveAll(ee => ee.TargetReference != null && !ee.TargetReference.IsAlive);
        }

        public void Remove(T eh)
        {
            if (eh != null)
            {
                Delegate d = (Delegate)(object)eh;
                for (int i = eventEntries.Count - 1; i >= 0; i--)
                {
                    EventEntry entry = eventEntries[i];
                    if (entry.TargetReference != null)
                    {
                        object target = entry.TargetReference.Target;
                        if (target == null)
                        {
                            eventEntries.RemoveAt(i);
                        }
                        else if (target == d.Target && entry.TargetMethod == d.Method)
                        {
                            eventEntries.RemoveAt(i);
                            break;
                        }
                    }
                    else
                    {
                        if (d.Target == null && entry.TargetMethod == d.Method)
                        {
                            eventEntries.RemoveAt(i);
                            break;
                        }
                    }
                }
            }
        }

        public void Raise(object sender, EventArgs e)
        {
            int stepExceptionHelp = 0;

            try
            {
                bool needsCleanup = false;
                object[] parameters = {sender, e};
                foreach (EventEntry ee in eventEntries.ToArray())
                {
                    stepExceptionHelp = 1;
                    if (ee.TargetReference != null)
                    {
                        stepExceptionHelp = 2;
                        object target = ee.TargetReference.Target;
                        if (target != null)
                        {
                            stepExceptionHelp = 3;
                            ee.TargetMethod.Invoke(target, parameters);
                        }
                        else
                        {
                            needsCleanup = true;
                        }
                    }
                    else
                    {
                        stepExceptionHelp = 4;
                        ee.TargetMethod.Invoke(null, parameters);
                    }
                }
                if (needsCleanup)
                {
                    stepExceptionHelp = 5;
                    RemoveDeadEntries();
                }

                stepExceptionHelp = 6;
            }
            catch (Exception ex)
            {
                string appName = Assembly.GetEntryAssembly().GetName().Name;
                if (!EventLog.SourceExists(appName))
                {
                    EventLog.CreateEventSource(appName, "Application");
                    EventLog.WriteEntry(appName,
                        String.Format("Exception happen in 'SmartWeakEvent.Raise()': {0}. Stack: {1}. Additional int: {2}.", ex.Message, ex.StackTrace, stepExceptionHelp), EventLogEntryType.Error);
                }

                throw;
            }
        }
    }
}


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using System.Xml.Serialization;
using HQ.Util.General.Log;
using HQ.Util.General.WeakEvent;
using JetBrains.Annotations;

// using System.Windows.Forms;

// using Microsoft.CSharp.RuntimeBinder;

// ATTENTION: Can only be used with Framework 4.0 and up

namespace HQ.Util.General.Notification
{
    [Serializable]
    public class NotifyPropertyChangedThreadSafeAsyncWeakBase : INotifyPropertyChanged
    {
        // ******************************************************************
        [XmlIgnore]
        [field: NonSerialized]
        public SmartWeakEvent<PropertyChangedEventHandler> SmartPropertyChanged = new SmartWeakEvent<PropertyChangedEventHandler>();

        [XmlIgnore]
        [field: NonSerialized]
        private Dispatcher _dispatcher = null;

        // ******************************************************************
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                SmartPropertyChanged.Add(value);
            }
            remove
            {
                SmartPropertyChanged.Remove(value);
            }
        }

        // ******************************************************************
        [Browsable(false)]
        [XmlIgnore]
        public Dispatcher Dispatcher
        {
            get
            {
                if (_dispatcher == null)
                {
                    _dispatcher = Application.Current?.Dispatcher;
                    if (_dispatcher == null)                    
                    { 
                        if (Application.Current?.MainWindow != null)
                        {
                            _dispatcher = Application.Current.MainWindow.Dispatcher;
                        }
                    }
                }

                return _dispatcher;
            }
            set
            {
                if (_dispatcher == null && _dispatcher != value)
                {
                    Debug.Print("Dispatcher has changed??? ");
                }

                _dispatcher = value;
            }
        }

        // ******************************************************************
        [NotifyPropertyChangedInvocator]
        protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null)
        {
            try
            {
                if (Dispatcher == null || Dispatcher.CheckAccess()) // Can be null in case of Console app
                {
                    SmartPropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
                }
                else
                {
                    Dispatcher.BeginInvoke(
                        new Action(() => SmartPropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName))));
                }
            }
            catch (TaskCanceledException ex) // Prevent MT error when closing app...
            {
                Log.Log.Instance.AddEntry(LogType.LogException, "An exception occured when trying to notify.", ex);
            }
        }

        // ******************************************************************
        [NotifyPropertyChangedInvocator]
        protected void NotifyPropertyChanged<T2>(Expression<Func<T2>> propAccess)
        {
            try
            {
                var asMember = propAccess.Body as MemberExpression;
                if (asMember == null)
                    return;

                string propertyName = asMember.Member.Name;

                if (Dispatcher == null || Dispatcher.CheckAccess()) // Can be null in case of Console app
                {
                    SmartPropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
                }
                else
                {
                    Dispatcher.BeginInvoke(new Action(() => SmartPropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName))));
                }
            }
            catch (TaskCanceledException ex) // Prevent MT error when closing app...
            {
                Log.Log.Instance.AddEntry(LogType.LogException, "An exception occured when trying to notify.", ex);
            }

        }



        // ******************************************************************
        protected bool UpdateField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                NotifyPropertyChanged(propertyName);
                return true;
            }
            return false;
        }

        // ******************************************************************
    }
}


using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using HQ.Util.General.Notification;

namespace HQ.Util.General
{
    /// <summary>
    /// This is the centralized calss to obtain the Dispatcher in order to make sure to get the appropriate one (the MainWindow dispatcher).
    /// If you need a specidifc thread dispatcher, please refer to System.Windows.Threading.Dispatcher or Dispatcher.CurrentDispatcher.
    /// This dispatcher should be set at the initialization (start) of any GUI executable: ex: Global.Instance.Dispatcher = Application.Current.Dispatcher;

    /// </summary>
    public class Global : NotifyPropertyChangedThreadSafeAsyncWeakBase
    {
        public delegate void IsBusyChangeHandler(bool isBusy);

        /// <summary>
        /// This event happen only the UI thread in low priority
        /// </summary>
        public event IsBusyChangeHandler IsBusyChange;

        private readonly ConcurrentStack<bool> _stackBusy = new ConcurrentStack<bool>();

        // ******************************************************************
        public static void Init(Dispatcher dispatcher)
        {
            Instance.Dispatcher = dispatcher;
        }

        // ******************************************************************
        public static Global Instance = new Global();

        // ******************************************************************
        private Global()
        {
            _busyLifeTrackerStack = new LifeTrackerStack(() => PushState(), PullState);
        }

        // ******************************************************************
        /// <summary>
        /// Will set busy state temporary until object is disposed where 
        /// the state will be back to its previous state.
        /// </summary>
        /// <param name="isBusy"></param>
        /// <returns></returns>
        public LifeTracker GetDisposableBusyState(bool isBusy = true)
        {
            return new LifeTracker(() => PushState(isBusy), PullState);
        }

        // ******************************************************************
        private bool _isBusy;

        /// <summary>
        /// This property should be use by the GUI part in order to control the mouse cursor
        /// </summary>
        public bool IsBusy
        {
            get => _isBusy;

            private set
            {
                if (value == _isBusy) return;
                _isBusy = value;
                Dispatcher.BeginInvoke(new Action(() => IsBusyChange?.Invoke(_isBusy)), DispatcherPriority.ContextIdle);
                NotifyPropertyChanged();
            }
        }

        private readonly object _objLockBusyStateChange = new object();
        // ******************************************************************
        /// <summary>
        /// Always prefer usage of "Using(Global.Instance.GetDisposableBusyState())" whenever possible.
        /// Otherwise ensure to call Pull State to get back previous state of the cursor when action is 
        /// completed
        /// </summary>
        /// <param name="isBusy"></param>
        public void PushState(bool isBusy = true)
        {
            lock (_objLockBusyStateChange)
            {
                _stackBusy.Push(isBusy);
                IsBusy = isBusy;
            }
        }

        // ******************************************************************
        public void PullState()
        {
            lock (_objLockBusyStateChange)
            {
                _stackBusy.TryPop(out bool isBusy);

                if (_stackBusy.TryPeek(out isBusy))
                {
                    IsBusy = isBusy;
                }
                else
                {
                    IsBusy = false;
                }
            }
        }

        // ******************************************************************
        private readonly LifeTrackerStack _busyLifeTrackerStack = null;

        /// <summary>
        /// Only kept for historical reason / compatibility with previous code
        /// </summary>
        [Obsolete("Use direct 'using(Gloabl.Instance.GetDisposableBusyState(isBusy))' which is simpler")]
        public LifeTrackerStack BusyLifeTrackerStack
        {
            get { return _busyLifeTrackerStack; }
        }

        // ******************************************************************
        // private int _latestVersionExecuted = 0;
        private int _currentVersionRequired = 0;
        private readonly object _objLockRunOnce = new object();

        private readonly Dictionary<int, GlobalRunOncePerQueueData> _actionsToRunOncePerQueue =
            new Dictionary<int, GlobalRunOncePerQueueData>();

        private readonly int _countOfRequestInQueue = 0;

        /// <summary>
        /// It will record all action once per key and it
        /// once per Dispatcher queue roll over (on ContextIdle).
        /// When the dispatcher reach the DispatcherPriority.ContextIdle, it will
        /// run all action once.
        /// Thread safe... no action will be lost but can be run twice or more if
        /// some are added by other thread(s) at the same time one is executed.
        /// </summary>
        /// EO: sorry for the name but it is the best found
        /// <param name="key"></param>
        /// <param name="action"></param>
        public void RunOncePerQueueRollOnDispatcherThread(int key, Action action)
        {
            lock (_objLockRunOnce)
            {
                if (!_actionsToRunOncePerQueue.TryGetValue(key, out GlobalRunOncePerQueueData data))
                {
                    data = new GlobalRunOncePerQueueData(action);
                    _actionsToRunOncePerQueue.Add(key, data);
                }

                _currentVersionRequired++;
                data.VersionRequired = _currentVersionRequired;
            }

            if (_countOfRequestInQueue <= 1)
            {
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(ExecuteActions));
            }
        }

        // ******************************************************************
        private void ExecuteActions()
        {
            int versionExecute;

            List<GlobalRunOncePerQueueData> datas = null;
            lock (_objLockRunOnce)
            {
                versionExecute = _currentVersionRequired;
                datas = _actionsToRunOncePerQueue.Values.ToList();
            }

            foreach (var data in datas)
            {
                data.Action();
            }

            lock (_objLockRunOnce)
            {
                List<int> keysToRemove = new List<int>();

                foreach (var kvp in _actionsToRunOncePerQueue)
                {
                    if (kvp.Value.VersionRequired <= versionExecute)
                    {
                        keysToRemove.Add(kvp.Key);
                    }
                }

                keysToRemove.ForEach(k => _actionsToRunOncePerQueue.Remove(k));

                if (_actionsToRunOncePerQueue.Count == 0)
                {
                    // _latestVersionExecuted = 0;
                    _currentVersionRequired = 0;
                }
                else
                {
                    // _latestVersionExecuted = versionExecute;
                }
            }
        }

        // ******************************************************************
    }
}

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using HQ.Util.General;
using HQ.Util.General.Notification;
using Microsoft.VisualBasic.Devices;
using System.Windows;
using Mouse = System.Windows.Input.Mouse;
using System.Threading;

namespace HQ.Wpf.Util
{
    public class AppGlobal
    {
        // ******************************************************************
        public static void Init(Dispatcher dispatcher)
        {
            if (System.Windows.Input.Keyboard.IsKeyDown(Key.LeftShift) || System.Windows.Input.Keyboard.IsKeyDown(Key.RightShift))
            {
                var res = MessageBox.Show($"'{AppInfo.AppName}' has been started with shift pressed. Do you want to wait for the debugger (1 minute wait)?", "Confirmation", MessageBoxButton.YesNo,
                    MessageBoxImage.Exclamation, MessageBoxResult.No);

                if (res == MessageBoxResult.Yes)
                {
                    var start = DateTime.Now;

                    while (!Debugger.IsAttached)
                    {
                        if ((DateTime.Now - start).TotalSeconds > 60)
                        {
                            break;
                        }
                        Thread.Sleep(100);
                    }
                }
            }

            if (dispatcher == null)
            {
                throw new ArgumentNullException();
            }

            Global.Init(dispatcher);
            Instance.Init();
        }

        // ******************************************************************
        public static readonly AppGlobal Instance = new AppGlobal();

        // ******************************************************************
        private AppGlobal()
        {
        }

        // ******************************************************************
        private void Init()
        {
            Global.Instance.PropertyChanged += AppGlobalPropertyChanged;
        }

        // ******************************************************************
        void AppGlobalPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsBusy")
            {
                if (Global.Instance.IsBusy)
                {
                    if (Global.Instance.Dispatcher.CheckAccess())
                    {
                        Mouse.OverrideCursor = Cursors.Wait;
                    }
                    else
                    {
                        Global.Instance.Dispatcher.BeginInvoke(new Action(() => Mouse.OverrideCursor = Cursors.Wait));
                    }
                }
                else
                {
                    if (Global.Instance.Dispatcher.CheckAccess())
                    {
                        Mouse.OverrideCursor = Cursors.Arrow;
                    }
                    else
                    {
                        Global.Instance.Dispatcher.BeginInvoke(new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
                    }
                }
            }
        }

        // ******************************************************************
    }
}



using HQ.Util.General;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace HQ.Wpf.Util
{
    /// <summary>
    /// Ensure window cursor is "normal" (arrow) when visible.
    /// Usage: In Window class, define a member: OverrideCursorMtForWindow. Instantiate in constructor after Initialisation.
    /// </summary>
    public class WindowWithAutoBusyState
    {
        // ******************************************************************
        Window _window;
        bool _nextStateShoulBeVisible = true;

        // ******************************************************************
        public WindowWithAutoBusyState()
        {

        }

        // ******************************************************************
        public void Init(Window window)
        {
            _window = window;

            _window.Cursor = Cursors.Wait;
            _window.Loaded += (object sender, RoutedEventArgs e) => _window.Cursor = Cursors.Arrow;

            _window.IsVisibleChanged += WindowIsVisibleChanged;
            _window.Closed += WindowClosed;
        }

        // ******************************************************************
        private void WindowIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (_window.IsVisible)
            {
                if (_nextStateShoulBeVisible)
                {
                    Global.Instance.PushState(false);
                    _nextStateShoulBeVisible = false;
                }
            }
            else
            {
                if (!_nextStateShoulBeVisible)
                {
                    Global.Instance.PullState();
                    _nextStateShoulBeVisible = true;
                }
            }
        }

        // ******************************************************************
        private void WindowClosed(object sender, EventArgs e)
        {
            if (!_nextStateShoulBeVisible)
            {
                Global.Instance.PullState();
                _nextStateShoulBeVisible = true;
            }
        }

        // ******************************************************************

    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.