もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f
■連打防止関連
ボタン押したときに時間がかかる処理をawaitでやるときに、ボタン連打を防止したい
→https://qiita.com/tera1707/items/a6f11bd3bf2dbf97dd40
ボタン押したときに時間がかかる処理をawaitでやるときに、ボタン連打を防止したい その2
→https://qiita.com/tera1707/items/946116bf32d0f1203006
やりたいこと
以前、掲題の内容をやりたくて、prismのDelegateCommand
クラスを使って連打防止をやってみた。
ただそのときのやり方だと、同じようなことを複数のボタンでやろうとしたときに、同じようなフラグを何個も作らないといけなくなるため、もう少しマシなやり方を探していたところ、その記事に、@unidentifiedexeさんに良いやり方のコメントを頂いた。
コードのサンプルも書いて頂いて、そのまま使えそうな感じだったのだが、一応自分でも理解しておきたいということで、練習がてらコードを纏めてみたい。
サンプルコード
WPFの画面(xaml)、ViewModelと今回作成したコマンドのクラスのコードは下記の通り。(コードビハインドは省略)
<Windowx: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><ButtonContent="ボタン1"FontSize="100"Command="{Binding VmMyCommand1}"Margin="20"/><ButtonContent="ボタン2"FontSize="100"Command="{Binding VmMyCommand2}"Margin="20"/></StackPanel></Window>
usingSystem.ComponentModel;usingSystem.Diagnostics;usingSystem.Threading.Tasks;namespaceWpfApp1{classViewModel:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;protectedvoidOnPropertyChanged(stringpropertyName)=>PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));// ボタン押された時のCommandpublicUnRepeatableAsyncCommandVmMyCommand1{get;privateset;}publicUnRepeatableAsyncCommandVmMyCommand2{get;privateset;}publicViewModel(){VmMyCommand1=newUnRepeatableAsyncCommand(MyAsyncFunc);VmMyCommand1.CanExecuteChanged+=((sender,e)=>Debug.WriteLine("CanExecuteChanged1"));VmMyCommand2=newUnRepeatableAsyncCommand(MyAsyncFunc,MyCanExecute);VmMyCommand2.CanExecuteChanged+=((sender,e)=>Debug.WriteLine("CanExecuteChanged2"));}// 実験用 押したときに2秒かかる処理実施publicasyncTaskMyAsyncFunc(){Debug.WriteLine("押された");awaitTask.Delay(2000);Debug.WriteLine("処理完了");}publicboolMyCanExecute(){returntrue;}}}
usingSystem;usingSystem.Threading.Tasks;usingSystem.Windows.Input;namespaceWpfApp1{publicclassUnRepeatableAsyncCommand:ICommand{Func<Task>execute;Func<bool>canExecute;publiceventEventHandlerCanExecuteChanged;// 処理中フラグprivateboolisExecuting=false;// 本クラスを使う側が設定するCanExecuteに加え、処理中フラグのON/OFFを有効無効条件に加えるpublicboolCanExecute(objectparameter)=>(canExecute!=null)?(canExecute()&&!isExecuting):(!isExecuting);// 処理実行の前後に、無効化→有効化、の処理を追加するpublicasyncvoidExecute(objectparameter){isExecuting=true;CanExecuteChanged?.Invoke(this,EventArgs.Empty);awaitexecute();isExecuting=false;CanExecuteChanged?.Invoke(this,EventArgs.Empty);}publicUnRepeatableAsyncCommand(System.Func<Task>execute){this.execute=execute;}publicUnRepeatableAsyncCommand(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);
をコメントアウトすると、ボタンが無効にはなるのだが、見た目がグレーアウトしなくなる。
自分の理解が足りてない部分なのだが、ICommand
のCanExecuteChanged
イベントハンドラは、CanExecuteChangedにメソッドを入れておくと、WPFのフレームワークが、CanExecuteが変化したタイミングで勝手に入れたメソッドを呼んでくれる、というものではなかったか??
(つまり、自分でそのイベントハンドラを呼ぶようなものではないと思っていた)
サンプルコードのとおり、CanExecuteChanged?.Invoke(this, EventArgs.Empty);
をしてやると見た目も変わってくれるが、なぜそのような動きになるのか?が現状わかっていない...
(が、とりあえず動くものにはなったのでメモ代わりに残す...)