[C#/WPF/prism] await에서 버튼을 누르는 데 시간이 걸리는 처리를 할 때 버튼의 연격을 방지해야 한다.

16961 단어 WPFPRISMC#

제비를 뽑다


■ 연계 방지
버튼을 눌렀을 때 시간 처리가 필요합니다. await가 진행될 때 버튼의 연격을 방지해야 합니다.
https://qiita.com/tera1707/items/a6f11bd3bf2dbf97dd40
버튼을 눌렀을 때 시간이 걸리는 처리가 await에서 진행될 때 버튼이 연결되는 것을 방지하기 위해 2
https://qiita.com/tera1707/items/946116bf32d0f1203006

하고 싶은 일


예전에는 타이틀 내용을 만들고 싶어서 prismDelegateCommand반을 사용해 연속타를 막았다.
다만, 당시의 방식대로라면 여러 버튼으로 같은 일을 하려고 할 때 같은 로고를 몇 개 만들어야 하기 때문에 더 좋은 방법을 찾던 중 그 기사에서 @unidentifiedexe씨가 좋은 방법에 대한 평을 줬다.
코드의 샘플도 적어 직접 사용할 수 있을 것 같지만 스스로 이해했으면 하는 마음에 연습과 함께 코드를 정리하려고 한다.

샘플 코드


WPF의 화면(xaml), ViewModel 및 이번에 제작된 명령 클래스의 코드는 다음과 같습니다.(코드 우선순위 생략)

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="800">
    <StackPanel>
        <Button Content="押すと2秒間処理をし、その間は自動で無効になるボタン" FontSize="25" Command="{Binding VmMyCommand1}" Margin="20"/>
        <Button Content="ボタン1の有効無効をVMのフラグで切り替えるボタン" FontSize="25" Command="{Binding VmMyCommand2}" Margin="20"/>
    </StackPanel>
</Window>

ViewModel.cs
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;

namespace WpfApp1
{
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        // ボタン押された時のCommand
        public UnRepeatableAsyncCommand VmMyCommand1 { get; private set; }
        public UnRepeatableAsyncCommand VmMyCommand2 { get; private set; }

        public bool MyCamExecuteFlag
        {
            get { return _myCamExecuteFlag; }
            set { _myCamExecuteFlag = value; OnPropertyChanged(nameof(MyCamExecuteFlag)); }
        }
        private bool _myCamExecuteFlag = true;

        public ViewModel()
        {
            // (ボタン1) 押したら2秒かかる処理を非同期で行って、その間は自動で無効になるボタン
            VmMyCommand1 = new UnRepeatableAsyncCommand(MyAsyncFunc, MyCanExecute);
            VmMyCommand1.CanExecuteChanged += ((sender, e) => Debug.WriteLine("CanExecuteChanged1"));

            // (ボタン2) ボタン1の有効無効をViewModelから切り替えるボタン
            VmMyCommand2 = new UnRepeatableAsyncCommand(async () =>
            {
                MyCamExecuteFlag = !MyCamExecuteFlag;   // CanExecuteで見るフラグ
                VmMyCommand1.RaiseCanExecuteChanged();  // ★CanExecuteが変化したことを使えないと、フラグ切り替えても有効無効変わらない!
            });
            VmMyCommand2.CanExecuteChanged += ((sender, e) => Debug.WriteLine("CanExecuteChanged2"));
        }

        // 実験用 押したときに2秒かかる処理実施
        public async Task MyAsyncFunc()
        {
            Debug.WriteLine("押された");
            await Task.Delay(2000);
            Debug.WriteLine("処理完了");
        }

        // フラグのON/OFFでボタンの有効無効を切り替える
        public bool MyCanExecute()
        {
            return MyCamExecuteFlag;
        }
    }
}

UnRepeatableAsyncCommand.cs
using System;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfApp1
{
    public class UnRepeatableAsyncCommand : ICommand
    {
        Func<Task> execute;
        Func<bool> canExecute;
        public event EventHandler CanExecuteChanged;

        // 処理中フラグ
        private bool isExecuting = false;

        public bool IsExecuting
        {
            get { return isExecuting; }
            set 
            { 
                isExecuting = value; 
                RaiseCanExecuteChanged();
            }
        }

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }

        // 本クラスを使う側が設定するCanExecuteに加え、処理中フラグのON/OFFを有効無効条件に加える
        public bool CanExecute(object parameter) => (canExecute != null) ? (canExecute() && !isExecuting) : (!isExecuting);

        // 処理実行の前後に、無効化→有効化、の処理を追加する
        public async void Execute(object parameter)
        {
            IsExecuting = true;
            await execute();
            IsExecuting = false; 
        }

        public UnRepeatableAsyncCommand(System.Func<Task> execute)
        {
            this.execute = execute;
        }

        public UnRepeatableAsyncCommand(System.Func<Task> execute, System.Func<bool> canExecute)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }
    }
}

UnRepeatableAsyncCommand 이번 제작의 연격 방지 Command 설치반.

남아 있는 의문


의문


코드로 작업
  • 버튼 누르기
  • 잘못된 버튼(회색 종료)
  • 2초 후
  • 버튼 유효성(회색조 제거)
  • 이런 동작이지만.
    연격 방지 명령 클래스public async void Execute(object parameter) 중 2곳CanExecuteChanged?.Invoke(this, EventArgs.Empty);에 대한 논평을 하면 단추가 무효지만 외관은 회색으로 변하지 않습니다.
    제가 이해가 부족한 부분이긴 한데.ICommandCanExecuteChanged 이벤트 처리 프로그램이Can Execute Changed에 미리 넣는 방법이 아니라면 WPF의 프레임워크는Can Execute가 변할 때 임의로 넣는 방법을 부르나요?
    (즉, 내가 그 이벤트 프로세서 같은 것을 부르는 것은 아니라고 생각한다)
    샘플 코드에서 보듯이 CanExecuteChanged?.Invoke(this, EventArgs.Empty);에 해주면 겉모습도 바뀌는데 왜 그런 동작이 됐을까요?현재 상황을 몰라...
    스케줄러:근데,움직일것으로변해버려서,노트 대신남긴건데...

    의문에 대한 대응(21/03/23 보충)


    albireo의 평론을 바탕으로 코드를 수정해 보았습니다.
  • 화면에 있는 버튼
  • 버튼 1(위쪽 버튼)의 유효 무효는 버튼 2(아래쪽 버튼)를 누르면 전환할 수 있습니다.
  • ViewModel에 있는 속성의 ONOFF로 전환
    (즉, 플래그의 변경 사항=CanExecute의 변경)
  • UnRepeatableAsyncCommand클래스, CanExecuteChanged()변경RaiseCanExecuteChanged() 구현 추가
  • View 모델에서Can Execute가 바뀔 부분에서 해당하는 Un Repeatable AsyncCommand의RaiseCanExecuteChanged()를 호출
  • 이렇게 하면 생각한 일은 대충 끝낼 수 있겠지...

    참고 자료


    CanExecuteChangd의 번거로운 문제를 조사해 봤습니다.
    https://qiita.com/204504bySE/items/0c7d5ac6913673dc10f5

    좋은 웹페이지 즐겨찾기