お昼にこんな話題が…。
ユーザー レスポンスを非同期処理に見立てて await する手法だと Window.Closing をキャンセルできない問題、常に e.Cancel = true; しておいて、レスポンスで OK 来たら Application.Shutdown(); してしまえというアレな策が
もんもんと考えた結果以下のようなBehaviorが出来ました。実用に耐えうるか…?
using System; using System.ComponentModel; using System.Threading.Tasks; using System.Windows; using System.Windows.Interactivity; namespace WpfApplication4 { publicclass AsyncClosingBehavior : Behavior<Window> { // ClosingAsyncActionにnullが設定された場合に使用する値privatestaticreadonly Func<Task<bool>> FalseAction = () => Task.FromResult(false); // closingをCancelするかどうかprivatebool cancelClosing = true; // 現在closingイベントの処理中かどうかprivatebool processing; /// <summary>/// WindowのClosingイベントでキャンセルするかどうかを確認するための非同期処理を行うデリゲート/// </summary>public Func<Task<bool>> ClosingAsyncAction { get { return (Func<Task<bool>>)GetValue(ClosingAsyncActionProperty); } set { SetValue(ClosingAsyncActionProperty, value); } } publicstaticreadonly DependencyProperty ClosingAsyncActionProperty = DependencyProperty.Register( "ClosingAsyncAction", typeof(Func<Task<bool>>), typeof(AsyncClosingBehavior), new PropertyMetadata(FalseAction, null, CoerceClosingAsyncAction)); // nullだったらFalseActionprivatestaticobject CoerceClosingAsyncAction(DependencyObject d, object baseValue) { return baseValue ?? FalseAction; } protectedoverridevoid OnAttached() { base.OnAttached(); this.AssociatedObject.Closing += this.WindowClosing; } protectedoverridevoid OnDetaching() { this.Cleanup(); base.OnDetaching(); } // イベントの後片付けprivatevoid Cleanup() { this.AssociatedObject.Closing -= this.WindowClosing; } private async void WindowClosing(object sender, CancelEventArgs e) { e.Cancel = this.cancelClosing; // Windowを閉じる場合は何もしないif (!this.cancelClosing) { return; } // Closingの処理中の場合は何もしないif (this.processing) { return; } this.processing = true; // Closingイベント中にCloseすると例外が出るので一旦Closingイベントを確実に抜けて続きをやる await this.Dispatcher.InvokeAsync(async () => { try { // 非同期呼び出しでクローズをキャンセルするかどうか問い合わせるthis.cancelClosing = await this.ClosingAsyncAction(); if (!this.cancelClosing) { // キャンセルしない場合は閉じるthis.Cleanup(); this.AssociatedObject.Close(); } } finally { this.processing = false; } }); } } }
使う側はこんな風にTask
using System; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace WpfApplication4 { /// <summary>/// MainWindow.xaml の相互作用ロジック/// </summary>publicpartialclass MainWindow : Window { public MainWindow() { InitializeComponent(); } public Func<Task<bool>> ClosingAsyncAction { get { returnthis.ClosingAsync; } } private Task<bool> ClosingAsync() { var currentContent = this.Content; var source = new TaskCompletionSource<bool>(); var panel = new StackPanel(); var confirmMessage = new TextBlock { Text = "本当に閉じるの?" }; panel.Children.Add(confirmMessage); var ok = new Button { Content = "OK" }; var cancel = new Button { Content = "Cancel" }; panel.Children.Add(ok); panel.Children.Add(cancel); ok.Click += (_, __) => { this.Content = currentContent; source.SetResult(false); }; cancel.Click += (_, __) => { this.Content = currentContent; source.SetResult(true); }; this.Content = panel; return source.Task; } } }
んで、Windowにビヘイビアをこんな風においておきます。
<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:local="clr-namespace:WpfApplication4"x:Class="WpfApplication4.MainWindow"Title="MainWindow"Height="350"Width="525"Name="window"><i:Interaction.Behaviors><local:AsyncClosingBehavior ClosingAsyncAction="{Binding ClosingAsyncAction, ElementName=window}"/></i:Interaction.Behaviors><Grid><TextBlock HorizontalAlignment="Left"Margin="43,39,0,0"TextWrapping="Wrap"VerticalAlignment="Top"><Run Language="ja-jp"Text="着任"/></TextBlock></Grid></Window>
実行するとこんな感じ。
Windowを閉じようとするとこんな風になって
キャンセルすると閉じない。
OKすると、もちろん閉じる。
なんか、まだまだ良くできそうでもんもんとしてる上に実用するのか?と言われるとなんとなく自分ではしないような気がしつつメモ。