display Hourglass when application is busy

前端 未结 9 1582
说谎
说谎 2020-11-28 03:01

For a view constructed using WPF, I want to change the mouse cursor to a hourglass when the application is busy and nonresponsive.

One solution is to add



        
相关标签:
9条回答
  • 2020-11-28 03:38

    I personnaly prefer to not see to mouse pointer switching many times from hourglass to arrow. To help prevent that behavior while calling embedded functions that take a while and each try to control the mouse pointer, I use a stack (counter) that I call a LifeTrackerStack. And only when the stack is empty (counter to 0) that I set back the hour glass to an arrow.

    I also use MVVM. I also prefer thread safe code.

    In my root class of model I declare my LifeTrackerStack that I either populate in childs model classes or use directly from child model classes when I have access to it from them.

    My life tracker have 2 states/actions:

    • Alive (counter > 0) => turn Model.IsBusy to true;
    • Done (counter == 0) => turn Model.IsBusy to false;

    Then in my view, I manually bind to my Model.IsBusy and do:

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

    This is my class 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;
                }
            }
        }
    }
    

    And the usage of it:

        _busyStackLifeTracker = new LifeTrackerStack
            (
                () =>
                {
                    this.IsBusy = true;
                },
                () =>
                {
                    this.IsBusy = false;
                }
            );
    

    Everywhere I have lengthy jog, I do:

            using (this.BusyStackLifeTracker.GetNewLifeTracker())
            {
                // long job
            }
    

    It works for me. Hope it could help any! Eric

    0 讨论(0)
  • 2020-11-28 03:41

    I know I'm late, I just changed the way I manage cursor Hourglass (busy state) of my application.

    This proposed solution is more complex than my first answer but I think it is more complete and better.

    I do not said I have an easy solution or complete one. But for me it is the best because it mostly fixes all the troubles I had managing the busy state of my application.

    Advantages:

    • Can manage "Busy" state from either the model and the view.
    • Able to manage busy state either if there is no GUI. Decoupled from GUI.
    • Thread safe (can be used from any thread)
    • Support Busy override (show arrow temporarily) when there is a Window (Dialog) that should be visible in the middle of a very long transaction.
    • Able to stack many operations with busy behavior that show a constant hourglass either if it is part a many little long sub tasks. Having an hourglass which would not change frequently from busy to normal to busy. A constant busy state if possible, by using a stack.
    • Support event subsciption with weak pointer because "Global" object instance is global (will never be Garbage Collected - it is rooted).

    The code is separated into few classes:

    • No GUI class: "Global" which manage Busy state and should be initialized at application start with the dispatcher. Because it is Global (singleton), I chosen to have weak NotifyPropertyChanged event in order to not keep a hard ref on anyone which want to be notify of any change.
    • A GUI class: AppGlobal which hook to Global and change Mouse appearance in accordance with Gloab.Busy state. It should be initialiazed with the dispatcher also at program start.
    • A GUI class to help Dialog(Window) to have proper mouse behavior when used into a long transaction where the mouse has been overriden to show an hourglass and want the regular arrow when the Dialog(Window) is in use.
    • The code also include some dependencies.

    This is the usage:

    Init:

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

    Prefered usage:

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

    Other usage:

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

    Auto Window cursor:

    public partial class DlgAddSignalResult : Window
    {
        readonly WindowWithAutoBusyState _autoBusyState = new WindowWithAutoBusyState();
    
        // ******************************************************************
        public DlgAddSignalResult()
        {
            InitializeComponent();
    
            Model = DataContext as DlgAddSignalResultModel;
    
            _autoBusyState.Init(this);
        }
    

    Code:

    // 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;
                }
            }
    
            // ******************************************************************
    
        }
    }
    
    0 讨论(0)
  • 2020-11-28 03:42

    We did a disposable class that changes the cursor for us when the app is going to take long, it looks like this:

    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
    }
    

    And we use it like this:

    using(new WaitCursor())
    {
        // very long task
    }
    

    Might not be the greatest design, but it does the trick =)

    0 讨论(0)
提交回复
热议问题