본문 바로가기
C#/WPF

[C# WPF] 연속해서 여러 번 클릭하는 것을 방지해보자.

by Falto 2023. 1. 20.

다음과 같은 코드가 있다.

    public partial class MainWindow : Window
    {
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread.Sleep(500);
            Title += "a";
        }
    }

Title을 수정하는 코드가 0초가 걸린다고 하면 이 버튼은 클릭을 할 때마다 0.5초가 걸리는 작업을 한다. 근데 클릭을 한 번 하고 나서 0.5초의 작업을 하는 동안 한 번 더 클릭하면 클릭이 예약이 되어버린다. 버튼을 눌렀더니 0.5초 동안 무응답이길래 한 번 더 눌렀는데 1초를 기다려야 하는 셈이다. 성질 급한 사람은 3번 이상 누를지도 모른다. 그럼 무응답인 시간은 계속 길어지고, 스트레스는 증가하고, 버튼은 계속 누르고... 악순환이 반복된다. 이걸 방지하려면 어떻게 해야 할까? 여러가지 해결책이 있겠지만, 이 글에서는 0.5초 동안 버튼을 눌렀을 때의 클릭 이벤트를 무시하는 방법으로 가기로 했다.

가장 먼저 떠오르는 건 flag다.

        bool flag = true;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (flag)
            {
                flag = false;
                Thread.Sleep(500);
                Title += "a";
                flag = true;
            }
        }

작업은 flag가 true일 때만 시작되게 하고, 작업이 시작하면 flag를 false로 해 놓고, 작업이 끝나면 flag를 true로 해 놓는 식이다. 하지만 이건 해결책이 되지 못 한다. 작업이 모두 끝나고 나면 flag가 true가 되어버려서 처음의 코드와 아무런 차이가 없기 때문이다. 그렇다면 flag = true;를 조금 더 늦게 호출되게 하는 건 어떨까?

        bool flag = true;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (flag)
            {
                flag = false;
                Thread.Sleep(500);
                Title += "a";
                new Thread(()=> { Thread.Sleep(1); flag = true; }).Start();
            }
        }

Thread.Sleep(1)을 넣어서 flag가 true가 되는 시간을 지연시켰다. 0.001초면 그동안 밀려있던 클릭 이벤트가 전부 다 처리되고도 남을 시간이다. 해결 끝! 가장 좋은 것은 시간 복잡도 자체를 줄이는 것이겠지만, 불가피한 경우 저렇게 해야 한다.

아래처럼 IsEnabled를 이용하는 방법도 있다. 

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            IsEnabled = false;
            Thread.Sleep(500);
            Title += "a";
            new Thread(() => { Thread.Sleep(1); Dispatcher.Invoke(() => { IsEnabled = true; }); }).Start();
        }

Dispatcher.Invoke()를 안 써주면 System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it. 라는 오류 메시지를 보게 될 것이다.

아래처럼 비동기(async)를 이용하는 방법도 있다. 사실상 따로 스레드를 만드는 거나 다름없긴 하다. 자기가 선호하는 방법으로 하면 된다.

주의사항: MMDevice나 UsbCamera 등등 하나의 스레드에서 생성되면 다른 스레드에서 접근하는 게 비정상적으로 취급되는 경우가 몇몇 있다. 멀티스레드 프로그램을 만들 때는 Thread-Safe한지 체크해줘야 한다.

        private async void RefreshButton_Click(object sender, RoutedEventArgs e)
        {
            await Task.Run(() =>
            {
                Dispatcher.Invoke(delegate
                {
                    refreshButton.IsEnabled = false;
                });
                InitDevicesComboBox();
                Dispatcher.Invoke(delegate
                {
                    refreshButton.IsEnabled = true;
                });
            });
        }

'C# > WPF' 카테고리의 다른 글

RelativeSource Binding  (0) 2023.03.01
DataContext Binding  (0) 2023.03.01
[C# WPF] Binding  (0) 2023.02.18
[C# WPF] super mario 63의 cheat를 구현해 보자.  (0) 2023.02.03
[C# WPF] Ctrl Z 감지하기  (0) 2023.01.09

댓글