プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

Livetでウィンドウが閉じられるときに確認ダイアログを出し、その応答で別の処理をしたりキャンセルしたりする方法

サンプルコードは最後に載せています。
2015/05/17 サンプルコードをコールバックを使用しない内容に差し替え。

確認環境

背景

notepad.exeにて、テキストを編集した状態でウィンドウを閉じようとすると確認ダイアログが表示される。
確認ダイアログの結果によって、ウィンドウを閉じることがキャンセルされたり、閉じる前に別の操作(ここではファイルの保存)が行われる。
同じことをLivetを使ったWPFアプリケーションでも行いたい。

説明

LivetのViewのテンプレート中のコメントにあるように、WindowのCloseキャンセル処理に対応する場合はWindowCloseCancelBehaviorを使用します。
WindowCloseCancelBehaviorにはCanCloseのBindingだけでなく、キャンセルした時のコールバック用コマンド/メソッドを指定可能なので、それを利用します。
コールバック内で確認ダイアログを表示するメッセージを送信し、確認ダイアログのコールバック内で終了処理を行い、改めてWindowを閉じます。

なおサンプルコードのコメントで触れていますが、Closeメッセージの送信を直接行った場合は、InvalidOperationExceptionが発生しました。Messageは「Window が閉じている場合、Visibility を Visible に設定したり、Show、ShowDialog、Close、および WindowInteropHelper.EnsureHandle を呼び出したりすることはできません。」という内容です。
日本語だと今ひとつ意味が通りませんが、英語でMessageを出すと"Cannot set Visibility to Visible or call Show, ShowDialog, Close, or WindowInteropHelper.EnsureHandle while a Window is closing."という内容となり、どうやらWindow.Closingイベント中に何かしようとしたのが不味かったらしいです。
というわけで、BeginInvokeをかましてClosingイベントが完了した後にメッセージを送信すれば回避可能です。
(おや、けれどもInteractionMessageはWindow.Closingイベント中でも送信出来てるんですよね……。)

(ViewModelのメッセージを送信するだけのコールバック、View側でまとめてしまって、ViewModelから無くすことが出来れば嬉しい。)
-> Livetを駆使したMVVM風アプリ作成のメモ - Qiitaの「DirectInteractionMessage無し」の項を参考にして、コールバックを無くしました。 

サンプルコード

ViewModel

using System;
using Livet;
using Livet.Messaging;
using Livet.Messaging.Windows;

namespace LivetWPFApplicationTest.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        public void Initialize()
        {
            // 確認用
            CanClose = false;
        }

        #region CanClose変更通知プロパティ
        private bool _CanClose;
        public bool CanClose
        {
            get { return _CanClose; }
            set
            {
                if (_CanClose == value) { return; }

                _CanClose = value;
                RaisePropertyChanged();
            }
        }
        #endregion

        public void CloseCanceledCallback()
        {
            var message = new ConfirmationMessage(
                "終了してよろしいですか?",
                "確認",
                System.Windows.MessageBoxImage.Information,
                System.Windows.MessageBoxButton.OKCancel,
                "Confirm");

            // View側がメッセージを処理し終えるまでブロックされる
            Messenger.Raise(message);

            // 何もしなければ、WindowのCloseはキャンセルされたまま
            if (message.Response != true) { return; }

            // ここで何かをして、プログラムを終了する準備をする

            // 改めてWindowを閉じる
            CanClose = true;
            // ここで直接Close用のメッセージを発生させると、
            // InvalidOperatonExceptionが発生する
            DispatcherHelper.UIDispatcher.BeginInvoke((Action)(() =>
            {
                Messenger.Raise(new WindowActionMessage(WindowAction.Close, "WindowAction"));
            }));
        }
    }
}

View

<Window x:Class="LivetWPFApplicationTest.Views.MainWindow"
        xmlns="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:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:LivetWPFApplicationTest.Views"
        xmlns:vm="clr-namespace:LivetWPFApplicationTest.ViewModels"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <i:Interaction.Behaviors>
        <l:WindowCloseCancelBehavior CanClose="{Binding CanClose}"
                                     CloseCanceledCallbackMethodTarget="{Binding}"
                                     CloseCanceledCallbackMethodName="CloseCanceledCallback" />
    </i:Interaction.Behaviors>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize" />
        </i:EventTrigger>
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction />
        </i:EventTrigger>

        <l:InteractionMessageTrigger MessageKey="Confirm" Messenger="{Binding Messenger}">
            <l:ConfirmationDialogInteractionMessageAction />
        </l:InteractionMessageTrigger>

        <l:InteractionMessageTrigger MessageKey="WindowAction" Messenger="{Binding Messenger}">
            <l:WindowInteractionMessageAction />
        </l:InteractionMessageTrigger>
    </i:Interaction.Triggers>

    <Grid>
    </Grid>
</Window>