hiển thị Đồng hồ cát khi ứng dụng bận


90

Đối với chế độ xem được tạo bằng WPF, tôi muốn thay đổi con trỏ chuột thành đồng hồ cát khi ứng dụng đang bận và không phản hồi.

Một giải pháp là thêm

 this.Cursor = Cursors.Wait;

đến tất cả những nơi có thể khiến giao diện người dùng trở nên không phản hồi. Nhưng rõ ràng đây không phải là giải pháp tốt nhất. Tôi đang tự hỏi cách tốt nhất để đạt được điều này là gì?

Có thể đạt được điều này bằng cách sử dụng Kiểu hoặc Tài nguyên không?

Cảm ơn,

Câu trả lời:


224

Chúng tôi đã tạo một lớp dùng một lần thay đổi con trỏ cho chúng tôi khi ứng dụng sẽ mất nhiều thời gian, nó trông như thế này:

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
}

Và chúng tôi sử dụng nó như thế này:

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

Có thể không phải là thiết kế tuyệt vời nhất, nhưng nó có thủ thuật =)


3
Làm rất tốt khi sử dụng IDisposable! Một cách tốt để đảm bảo chúng ta luôn quay lại con trỏ trước đó.
Xavier Poinas

2
Tôi đã có cùng một ý tưởng cách đây một thời gian. Nhưng tôi đã bọc mã dưới dạng một lớp riêng tư bên trong một lớp mặt tiền dịch vụ giao diện người dùng và trả về các phiên bản của nó thông qua phương thức "ShowWaitCursor". Vì vậy, bạn phải làm: using(uiServices.ShowWaitCursor()). Trông cồng kềnh nhưng nó giúp giảm bớt thử nghiệm đơn vị.
Konamiman

một chút Offtopic: làm thế nào để thực hiện một cách chính xác dispose ... msdn.microsoft.com/en-us/library/ms244737.aspx
Michael Sander

2
@AnoushkaSosystemurn WaitCursor () không phải là một phương thức, nó là một phương thức khởi tạo. Tôi đoán bạn đã đổi tên lớp thành một thứ khác ngoài 'WaitCursor'?
Carlo

1
@ JánosTigyi Tôi thích tách các phương thức giao diện trong khu vực của riêng họ. Câu hỏi đặt ra là, tại sao vụ hack không sử dụng một vùng ở đó? = P
Carlo

40

Tôi đã sử dụng các câu trả lời ở đây để xây dựng thứ gì đó phù hợp hơn với tôi. Vấn đề là khi khối using trong câu trả lời của Carlo kết thúc, giao diện người dùng có thể thực sự vẫn đang bận xử lý dữ liệu. Có thể có dữ liệu tải chậm hoặc sự kiện kích hoạt do những gì đã được thực hiện trong khối. Trong trường hợp của tôi, đôi khi mất vài giây từ waitcursor biến mất cho đến khi UI thực sự sẵn sàng. Tôi đã giải quyết nó bằng cách tạo một phương thức trợ giúp đặt waitcursor và cũng đảm nhận việc thiết lập bộ đếm thời gian sẽ tự động đặt lại con trỏ khi UI sẵn sàng. Tôi không thể chắc chắn rằng thiết kế này sẽ hoạt động trong mọi trường hợp, nhưng nó phù hợp với tôi:

    /// <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 đoạn mã rất hay, bạn sẽ triển khai như thế nào cho C # Winforms. thay vào đó: Cursor.Current = Cursors.WaitCursor; // bận Cursor.Current = Cursors.Default;
Zeeshanef

+1 Thiết kế rất sạch sẽ, dễ dàng và tự động. Nếu bạn đang tìm kiếm giải pháp ứng dụng bận, hãy sử dụng mã của TJ. Quăng nó vào một lớp Người trợ giúp và sử dụng nó như thế này trước khi chạy một hoạt động dài: Helpers.UiServices.SetBusyState ();
Aaron Reed

14

Tôi chỉ đơn giản là làm

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

Theo tài liệu của Mouse.OverrideCursor Property

Để xóa Con trỏ ghi đè, đặt OverrideCursor thành null.

Câu lệnh try-last đảm bảo rằng con trỏ mặc định được khôi phục trong mọi trường hợp, ngay cả khi một ngoại lệ xảy ra hoặc phần try bị bỏ lại với returnhoặc break(nếu bên trong một vòng lặp).


Nhiệm vụ làm việc dài có thể tạo ra các ngoại lệ vì nhiều lý do khác nhau. Đây là một cách tiếp cận tốt
aggsol

6

Cách tốt nhất là không làm cho giao diện người dùng trở nên không phản hồi, giảm tải tất cả công việc cho các luồng / tác vụ khác nếu thích hợp.

Ngoài ra, bạn đang ở trong một điểm bắt đầu 22: nếu bạn đã thêm một cách để phát hiện rằng ui không phản hồi, không có cách nào tốt để thay đổi con trỏ, vì nơi bạn cần làm điều đó ( luồng chẵn) không phản hồi ... Tuy nhiên, bạn có thể chuyển sang mã win32 tiêu chuẩn để thay đổi con trỏ cho toàn bộ cửa sổ?

Nếu không, bạn phải làm điều đó trước, như câu hỏi của bạn gợi ý.


5

Tôi người phân tích không muốn nhìn thấy con trỏ chuột chuyển nhiều lần từ đồng hồ cát sang mũi tên. Để giúp ngăn chặn hành vi đó trong khi gọi các hàm được nhúng mất một lúc và mỗi hàm cố gắng điều khiển con trỏ chuột, tôi sử dụng một ngăn xếp (bộ đếm) mà tôi gọi là LifeTrackerStack. Và chỉ khi ngăn xếp trống (bộ đếm về 0) tôi mới đặt kính giờ thành mũi tên.

Tôi cũng sử dụng MVVM. Tôi cũng thích mã an toàn chủ đề.

Trong lớp mô hình gốc của tôi, tôi khai báo LifeTrackerStack của mình mà tôi đưa vào các lớp mô hình con hoặc sử dụng trực tiếp từ các lớp mô hình con khi tôi có quyền truy cập từ chúng.

Trình theo dõi cuộc sống của tôi có 2 trạng thái / hành động:

  • Alive (counter> 0) => biến Model.IsBusy thành true;
  • Done (counter == 0) => biến Model.IsBusy thành false;

Sau đó, theo quan điểm của tôi, tôi liên kết thủ công với Model.IsBusy của mình và thực hiện:

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

Đây là LifeTrackerStack lớp của tôi:

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

Và cách sử dụng nó:

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

Ở bất cứ nơi nào tôi phải chạy bộ dài, tôi làm:

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

Nó làm việc cho tôi. Hy vọng nó có thể giúp bất kỳ! Eric


@mins, Cảm ơn rất nhiều. Với một lời khen hay ho như vậy, tôi đã phải đăng giải pháp thực tế mà tôi đang sử dụng. Có thể có một số trục trặc (tôi không thể bao gồm tất cả khuôn khổ của mình) nên dễ sửa (có thể phải xóa một số mã). Tôi đã thêm một câu trả lời mới với mã tốt hơn (tôi nghĩ) và hoàn chỉnh hơn phù hợp với nhiều tình huống hơn. Bạn có thể xem qua nếu bạn muốn. ;-)
Eric Ouellet

3

Hãy cẩn thận ở đây vì thao tác với Con trỏ Chờ có thể gây ra một số vấn đề với các chuỗi STA. Đảm bảo rằng nếu bạn sử dụng điều này, bạn đang thực hiện nó trong chuỗi của chính nó. Tôi đã đăng một ví dụ ở đây Chạy bên trong một STA sử dụng điều này để hiển thị WaitCursor trong khi tệp được tạo ra đang khởi động và không bị nổ (ứng dụng chính) AFAICT.


1

Thay đổi con trỏ không có nghĩa là ứng dụng sẽ không phản hồi các sự kiện chuột và bàn phím sau khi tác vụ chạy dài kết thúc. Để tránh người dùng viết nhầm, tôi sử dụng lớp bên dưới để xóa tất cả các thông báo bàn phím và chuột khỏi hàng đợi thông báo ứng dụng.

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

Tôi đã sử dụng giải pháp của Olivier Jacot-Descombes, nó rất đơn giản và hoạt động tốt. cảm ơn. cập nhật: nó thậm chí hoạt động tốt mà không cần sử dụng một luồng / nhân viên nền khác.

Tôi sử dụng nó với backgroudworker, con trỏ chuột trông rất tuyệt khi nó đang bận làm việc và trở lại bình thường khi công việc hoàn thành.

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

Tôi biết mình đến muộn, tôi vừa thay đổi cách quản lý con trỏ Đồng hồ cát (trạng thái bận) của ứng dụng.

Giải pháp được đề xuất này phức tạp hơn câu trả lời đầu tiên của tôi nhưng tôi nghĩ nó đầy đủ hơn và tốt hơn.

Tôi không nói rằng tôi có một giải pháp dễ dàng hay một giải pháp hoàn chỉnh. Nhưng đối với tôi nó là tốt nhất vì nó chủ yếu khắc phục tất cả những rắc rối mà tôi gặp phải khi quản lý trạng thái bận của ứng dụng của mình.

Ưu điểm:

  • Có thể quản lý trạng thái "Bận" từ cả mô hình và chế độ xem.
  • Có thể quản lý trạng thái bận nếu không có GUI. Được tách khỏi GUI.
  • Két an toàn (có thể được sử dụng từ bất kỳ chủ đề nào)
  • Hỗ trợ ghi đè Bận (hiển thị mũi tên tạm thời) khi có một Cửa sổ (Hộp thoại) hiển thị ở giữa một giao dịch rất dài.
  • Có thể xếp chồng nhiều hoạt động với hành vi bận rộn hiển thị đồng hồ cát không đổi hoặc nếu nó là một phần của nhiều nhiệm vụ phụ nhỏ dài. Có một chiếc đồng hồ cát sẽ không thay đổi thường xuyên từ bận rộn sang bình thường sang bận rộn. Trạng thái bận liên tục nếu có thể, bằng cách sử dụng ngăn xếp.
  • Hỗ trợ đăng ký sự kiện với con trỏ yếu vì cá thể đối tượng "Toàn cầu" là toàn cầu (sẽ không bao giờ được Thu gom rác - nó đã được root).

Mã được tách thành một số lớp:

  • Không có lớp GUI: "Toàn cầu" quản lý trạng thái Bận và phải được khởi tạo khi bắt đầu ứng dụng với trình điều phối. Bởi vì nó là Toàn cầu (singleton), tôi đã chọn sự kiện NotifyPropertyChanged yếu để không giữ một lời giới thiệu cứng nhắc về bất kỳ ai muốn được thông báo về bất kỳ thay đổi nào.
  • Một lớp GUI: AppGlobal kết nối với Toàn cầu và thay đổi giao diện Chuột phù hợp với trạng thái Gloab.Busy. Nó sẽ được khởi tạo với điều phối viên cũng khi bắt đầu chương trình.
  • Một lớp GUI để giúp Hộp thoại (Cửa sổ) có hành vi chuột thích hợp khi được sử dụng trong một giao dịch dài trong đó chuột đã được ghi đè để hiển thị hình đồng hồ cát và muốn có mũi tên thông thường khi Hộp thoại (Cửa sổ) được sử dụng.
  • Mã cũng bao gồm một số phụ thuộc.

Đây là cách sử dụng:

Trong đó:

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

Cách sử dụng ưu tiên:

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

Cách sử dụng khác:

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

Con trỏ cửa sổ tự động:

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

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

        Model = DataContext as DlgAddSignalResultModel;

        _autoBusyState.Init(this);
    }

Mã:

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

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

    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.