[c#] 응용 프로그램이 사용 중일 때 모래 시계 표시

WPF를 사용하여 구성된 뷰의 경우 응용 프로그램이 사용 중이거나 응답이 없을 때 마우스 커서를 모래 시계로 변경하고 싶습니다.

한 가지 해결책은

 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
}

최고의 디자인은 아니지만 트릭을 수행합니다 =)


답변

나는 여기에 더 나은 것을 만들기 위해 여기에 대한 답변을 사용했습니다. 문제는 Carlo의 답변에서 사용 블록이 완료되면 UI가 실제로 여전히 데이터 바인딩 중일 수 있다는 것입니다. 블록에서 수행 된 작업의 결과로 지연로드 된 데이터 또는 이벤트가 발생할 수 있습니다. 제 경우에는 UI가 실제로 준비 될 때까지 waitcursor가 사라지고 몇 초가 걸렸습니다. 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 Property 의 문서에 따르면

재정의 커서를 지우려면 OverrideCursor를 null로 설정합니다.

try-finally 문은 예외가 발생하거나 try-part가 return또는 break(루프 내에있는 경우) 가 남아있는 경우에도 기본 커서가 어떤 경우에도 복원되도록합니다 .


답변

최고의 UI를 적절하게 다른 스레드 / 작업에 모든 작업을 오프로드, 이제까지 비 응답하지 못하도록 방법은 없습니다하는 것입니다.

그 외에는 일종의 catch-22에 있습니다 .UI가 응답하지 않음을 감지하는 방법을 추가했다면 커서를 변경해야하는 좋은 방법이 없습니다. 짝수 스레드)가 응답하지 않습니다 … 전체 창에 대한 커서를 변경하기 위해 표준 win32 코드로 핀 보킹 할 수 있습니다.

그렇지 않으면 질문에서 알 수 있듯이 선제 적으로 수행해야합니다.


답변

나는 개인적으로 모래 시계에서 화살표로 마우스 포인터가 여러 번 전환되는 것을 보지 않는 것을 선호합니다. 시간이 걸리고 각각 마우스 포인터를 제어하는 ​​임베디드 함수를 호출하는 동안 이러한 동작을 방지하기 위해 LifeTrackerStack이라고하는 스택 (카운터)을 사용합니다. 그리고 스택이 비었을 때 (0으로 카운터) 모래 시계를 화살표로 되돌 렸습니다.

MVVM도 사용합니다. 또한 스레드 안전 코드를 선호합니다.

내 모델의 루트 클래스에서 내 LifeTrackerStack을 선언하여 자식 모델 클래스에 채우거나 자식 모델 클래스에서 액세스 할 수있는 경우 자식 모델 클래스에서 직접 사용합니다.

내 라이프 트래커에는 두 가지 상태 / 동작이 있습니다.

  • Alive (카운터> 0) => Model.IsBusy를 true로 설정합니다.
  • 완료 (카운터 == 0) => Model.IsBusy를 false로 설정합니다.

그런 다음 내 관점에서 수동으로 Model.IsBusy에 바인딩하고 다음을 수행합니다.

void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == "IsBusy")
    {
        if (this._modelViewAnalysis.IsBusy)
        {
            if (Application.Current.Dispatcher.CheckAccess())
            {
                this.Cursor = Cursors.Wait;
            }
            else
            {
                Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = Cursors.Wait));
            }
        }
        else
        {
            Application.Current.Dispatcher.Invoke(new Action(() => this.Cursor = null));
        }
    }

이것은 내 클래스 LifeTrackerStack입니다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace HQ.Util.General
{
    /// <summary>
    /// Usage is to have only one event for a recursive call on many objects
    /// </summary>
    public class LifeTrackerStack
    {
        // ******************************************************************
        protected readonly Action _stackCreationAction;
        protected readonly Action _stackDisposeAction;
        private int _refCount = 0;
        private object _objLock = new object();
        // ******************************************************************
        public LifeTrackerStack(Action stackCreationAction = null, Action stackDisposeAction = null)
        {
            _stackCreationAction = stackCreationAction;
            _stackDisposeAction = stackDisposeAction;
        }

        // ******************************************************************
        /// <summary>
        /// Return a new LifeTracker to be used in a 'using' block in order to ensure reliability
        /// </summary>
        /// <returns></returns>
        public LifeTracker GetNewLifeTracker()
        {
            LifeTracker lifeTracker = new LifeTracker(AddRef, RemoveRef);

            return lifeTracker;
        }

        // ******************************************************************
        public int Count
        {
            get { return _refCount; }
        }

        // ******************************************************************
        public void Reset()
        {
            lock (_objLock)
            {
                _refCount = 0;
                if (_stackDisposeAction != null)
                {
                    _stackDisposeAction();
                }
            }
        }

        // ******************************************************************
        private void AddRef()
        {
            lock (_objLock)
            {
                if (_refCount == 0)
                {
                    if (_stackCreationAction != null)
                    {
                        _stackCreationAction();
                    }
                }
                _refCount++;
            }
        }

        // ******************************************************************
        private void RemoveRef()
        {
            bool shouldDispose = false;
            lock (_objLock)
            {
                if (_refCount > 0)
                {
                    _refCount--;
                }

                if (_refCount == 0)
                {
                    if (_stackDisposeAction != null)
                    {
                        _stackDisposeAction();
                    }
                }
            }
        }

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


using System;

namespace HQ.Util.General
{
    public delegate void ActionDelegate();

    public class LifeTracker : IDisposable
    {
        private readonly ActionDelegate _actionDispose;
        public LifeTracker(ActionDelegate actionCreation, ActionDelegate actionDispose)
        {
            _actionDispose = actionDispose;

            if (actionCreation != null)
                actionCreation();
        }

        private bool _disposed = false;
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    _actionDispose();
                }

                // Note disposing has been done.
                _disposed = true;
            }
        }
    }
}

그리고 그것의 사용법 :

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

긴 조깅이있는 곳에서는 다음을 수행합니다.

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

그것은 나를 위해 작동합니다. 도움이 될 수 있기를 바랍니다! 에릭


답변

Wait Cursor를 조작하면 STA 스레드에 문제가 발생할 수 있으므로 여기서주의하십시오. 이 작업을 사용하는 경우 자체 스레드 내에서 수행하고 있는지 확인하십시오. 여기에 Run inside an STA 예제를 게시했습니다. 이를 사용하여 생성 된 파일이 시작되는 동안 WaitCursor를 표시하고 AFAICT를 폭발시키지 않습니다.


답변

커서를 변경해도 장기 실행 작업이 완료된 후 응용 프로그램이 마우스 및 키보드 이벤트에 응답하지 않는 것은 아닙니다. 사용자의 오해를 방지하기 위해 응용 프로그램 메시지 대기열에서 모든 키보드 및 마우스 메시지를 제거하는 아래 클래스를 사용합니다.

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

}