WPF:コマンド実行中は非活性となり、連続実行を抑止するボタン
わざわざIsEnabledプロパティをバインドしてコマンドにasync/awaitを書いて、連続クリックをロックで止めて…と書く画面に実装するのが面倒だと思い、作ってみました。
public class AsyncButton : Button { /// <summary> /// Commandに値を入れられないようスコープを変更 /// </summary> private new ICommand Command { get { return base.Command; } set { base.Command = value; } } /// <summary> /// 非同期実行用コマンド /// </summary> public ICommand AsyncCommand { get { return (ICommand)GetValue(AsyncCommandProperty); } set { SetValue(AsyncCommandProperty, value); } } /// <summary> /// キー連打による連続実行を止めるためのロックオブジェクト /// </summary> private object lockObject { get; set; } // Using a DependencyProperty as the backing store for AsyncCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty AsyncCommandProperty = DependencyProperty.Register("AsyncCommand", typeof(ICommand), typeof(AsyncButton), new PropertyMetadata(null,OnAsyncCommandChanged)); private static void OnAsyncCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { AsyncButton btn = d as AsyncButton; //クリックイベントでEnterキー押下時などもハンドル可能なので、こちらを利用 btn.Click += Btn_Click; } private static void Btn_Click(object sender, RoutedEventArgs e) { AsyncButton btn = sender as AsyncButton; ExecAsyncCommand(btn, btn.AsyncCommand, btn.CommandParameter); } private static async void ExecAsyncCommand(AsyncButton btn, ICommand command , object parameter) { if (btn.lockObject == null) { btn.lockObject = new object(); } else { return; } await Task.Run(() => { lock (btn.lockObject) { btn.Dispatcher.Invoke(() => btn.IsEnabled = false); command.Execute(parameter); btn.Dispatcher.Invoke(() => btn.IsEnabled = true); } btn.lockObject = null; }); }
使うときは、こんな感じで。
View
<local:AsyncButton AsyncCommand="{Binding MyCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Test" />
ViewModel
public class MyViewModel { private RelayCommand _myCommand = null; public RelayCommand MyCommand { get { return _myCommand ?? (_myCommand = new RelayCommand(() => { //重い処理 Thread.Sleep(10000); })); } } }