Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

非同期処理でWindowのClosingをキャンセルするかどうか決めたい

$
0
0

お昼にこんな話題が…。

もんもんと考えた結果以下のような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を返すメソッドを定義して、それをFunc>で返すプロパティを定義しておきます。

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>

実行するとこんな感じ。
f:id:okazuki:20130822215123j:plain
Windowを閉じようとするとこんな風になって
f:id:okazuki:20130822215201j:plain
キャンセルすると閉じない。
f:id:okazuki:20130822215236j:plain
OKすると、もちろん閉じる。


なんか、まだまだ良くできそうでもんもんとしてる上に実用するのか?と言われるとなんとなく自分ではしないような気がしつつメモ。


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>