WPFを使用して作成されたビューの場合、アプリケーションがビジーで応答がないときに、マウスカーソルを砂時計に変更します。
1つの解決策は、
this.Cursor = Cursors.Wait;
UIが応答しなくなる可能性のあるすべての場所。しかし、明らかにこれは最善の解決策ではありません。これを達成するための最良の方法は何ですか?
スタイルまたはリソースを使用してこれを達成することは可能ですか?
おかげで、
回答:
アプリに時間がかかるときにカーソルを変更する使い捨てクラスを実行しました。次のようになります。
public class WaitCursor : IDisposable
{
private Cursor _previousCursor;
public WaitCursor()
{
_previousCursor = Mouse.OverrideCursor;
Mouse.OverrideCursor = Cursors.Wait;
}
#region IDisposable Members
public void Dispose()
{
Mouse.OverrideCursor = _previousCursor;
}
#endregion
}
そして、次のように使用します。
using(new WaitCursor())
{
// very long task
}
最高のデザインではないかもしれませんが、それはトリックを行います=)
using(uiServices.ShowWaitCursor())
。見た目は面倒ですが、単体テストが容易になります。
私はここでの回答を使用して、自分にとってより効果的なものを構築しました。問題は、Carloの回答のusingブロックが終了しても、UIが実際にデータバインディングでビジー状態になっている可能性があることです。ブロックで行われた処理の結果として、遅延読み込みされたデータまたはイベントが発生する可能性があります。私の場合、待機カーソルが消えてからUIが実際に準備できるまでに数秒かかることがありました。waitcursorを設定するヘルパーメソッドを作成し、UIの準備ができたときにカーソルを自動的に戻すタイマーの設定も処理することで解決しました。このデザインがすべてのケースで機能するかどうかはわかりませんが、私にとってはうまくいきました。
/// <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();
}
}
}
私は単にやっています
Mouse.OverrideCursor = Cursors.Wait;
try {
// Long lasting stuff ...
} finally {
Mouse.OverrideCursor = null;
}
Mouse.OverrideCursorプロパティのドキュメントによると
オーバーライドカーソルをクリアするには、OverrideCursorをnullに設定します。
try-finallyステートメントは、例外が発生したり、try-partがreturn
or break
(ループ内にある場合)のままになったりした場合でも、デフォルトのカーソルがどのような場合でも復元されるようにします。
私は個人的に、マウスポインターが砂時計から矢印に何度も切り替わるのを見ないようにしています。しばらく時間がかかり、それぞれがマウスポインターを制御しようとする組み込み関数を呼び出すときにその動作を防ぐために、LifeTrackerStackを呼び出すスタック(カウンター)を使用します。スタックが空(カウンターが0)の場合にのみ、砂時計を矢印に戻しました。
MVVMも使用しています。また、スレッドセーフコードの方が好きです。
モデルのルートクラスで、LifeTrackerStackを宣言します。これは、子モデルクラスに入力するか、子モデルクラスからアクセスできる場合は子モデルクラスから直接使用することです。
私のライフトラッカーには2つの状態/アクションがあります。
次に、私の見解では、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
}
わたしにはできる。それが何かを助けることを願っています!エリック
カーソルを変更しても、長時間実行されているタスクが終了した後、アプリケーションがマウスおよびキーボードのイベントに応答しないことはありません。ユーザーの誤解を避けるために、以下のクラスを使用して、すべてのキーボードメッセージとマウスメッセージをアプリケーションメッセージキューから削除します。
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
}
Olivier Jacot-Descombesのソリューションを使用しました。それは非常にシンプルで、うまく機能しています。ありがとう。更新:別のスレッド/バックグラウンドワーカーを使用しなくてもうまく機能します。
私は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
}
アプリケーションのカーソル砂時計(ビジー状態)の管理方法を変更しただけです。
この提案された解決策は私の最初の答えよりも複雑ですが、私はそれがより完全でより良いと思います。
簡単な解決策や完全な解決策があるとは言いません。しかし、私にとっては、アプリケーションのビジー状態を管理していたすべての問題をほとんど解決するので、それは最高です。
利点:
コードはいくつかのクラスに分かれています:
これは使用法です:
初期化:
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;
}
}
// ******************************************************************
}
}