ダイアログ表示を行うビューモデル(その2)の続きです。
前回ビヘイビア関係まで書いたので、今回はビューモデル関係です。
このライブラリを使ったアプリケーション例は次回書く予定です。
プロジェクトに「ViewModels」フォルダを作成します。
ViewModels フォルダに「IReceiveFinished」インターフェイスを作成します。
namespace MakCraft.ViewModels
{
/// <summary>
/// 画面遷移完了時の操作に用いるインターフェイスです。
/// </summary>
interface IReceiveFinished
{
/// <summary>
/// 画面遷移操作完了時に実行されるメソッド
/// </summary>
void OnFinished(ITransContainer container);
}
}
続けて「ITransContainer」インターフェイスを作成します。
namespace MakCraft.ViewModels
{
/// <summary>
/// 画面遷移の際のデータコンテナのインターフェイス
/// </summary>
public interface ITransContainer
{
/// <summary>
/// 遷移を区別するためのキーを取得します。
/// 一つのビューモデルで複数の画面遷移を持つ場合の処理の分岐用
/// </summary>
string Key { get; }
/// <summary>
/// 遷移動作の開始元ビューモデルを取得します。
/// </summary>
TransitionViewModelBase TransStartViewModel { get; }
/// <summary>
/// 前画面のビューモデルを取得・設定します。
/// </summary>
TransitionViewModelBase PreviousViewModel { get; set; }
}
}
続けて「IWindowCloseCommand」インターフェイスを作成します。
namespace MakCraft.ViewModels
{
/// <summary>
/// ウィンドウを閉じるためのビューモデルのインターフェイスです。
/// </summary>
public interface IWindowCloseCommand
{
/// <summary>
/// ウィンドウがクローズできる状態かを返します。
/// </summary>
bool CanCloseWindow { get; }
/// <summary>
/// ビューモデルからウィンドウへ Close を通知するメソッドです。
/// </summary>
void WindowClose();
}
}
続けて「DialogViewModelBase」クラスを作成します。
データ検証とダイアログ表示の基本機能を提供するビューモデルの基底クラスです。
using System;
using System.Windows;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.ViewModels
{
/// <summary>
/// データ検証とダイアログ表示の基本機能を提供するビューモデルの基底クラスです。
/// </summary>
public abstract class DialogViewModelBase : ValidationViewModelBase, IDialogTransferContainer
{
public DialogViewModelBase() { }
/// <summary>
/// 作成する Dialog に渡すデータを取得・設定します。
/// View 側で DialogTransferDataAction の Parameter にバインドしてください。
/// </summary>
public object CommunicationDialog { get; protected set; }
/// <summary>
/// 表示するカスタムダイアログボックスの型の情報
/// View 側で DialogTransferDataAction の DialogType にバインドしてください。
/// </summary>
public Type DialogType { get; protected set; }
#region IDialogTransferContainer
private object _container;
/// <summary>
/// ウィンドウ作成元から渡されたデータの受取用
/// </summary>
public virtual object Container
{
get { return _container; }
set
{
_container = value;
OnContainerReceived(_container);
}
}
#endregion IDialogTransferContainer
#region DialogTransferContainer
/// <summary>
/// ダイアログが閉じられた後に実行するコールバックを取得・設定します。
/// View 側で DialogTransferDataAction の ActionCallBack にバインドしてください。
/// </summary>
public Action<bool?> DialogActionCallback { get; protected set; }
/// <summary>
/// ダイアログ表示で生成されたダイアログのビューモデルへの参照を取得・設定します。
/// (ダイアログで設定された値の参照用)
/// View 側で DialogTransferDataAction の ResultViewModel にバインドしてください。
/// </summary>
public object ResultViewModel { get; set; }
/// <summary>
/// ウィンドウ作成元からのデータを受け取った際に行う処理
/// </summary>
/// <param name="container"></param>
protected virtual void OnContainerReceived(object container)
{
}
#endregion DialogTransferContainer
#region MessageDialogAction
/// <summary>
/// MessageDialogActionに渡すパラメーター
/// View 側で PropertyChangedTrigger の Binding と MessageDialogAction の Parameter にバインドしてください。
/// </summary>
public MessageDialogActionParameter MessageDialogActionParam { get; set; }
/// <summary>
/// MessageDialogActionの実行後に呼ばれるCallBack
/// View 側で MessageDialogAction の ActionCallBack にバインドしてください。
/// </summary>
public Action<MessageBoxResult> MessageDialogActionCallback { get; set; }
#endregion MessageDialogAction
}
}
続けて「MessageDialogActionParameter」クラスを作成します。
MessageDialogAction へ渡すパラメーターのクラスです。
using System.Windows;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.ViewModels
{
/// <summary>
/// MessageDialogAction へ渡すパラメーター
/// IsDialog が false のときには Button の設定は反映されません。
/// </summary>
public class MessageDialogActionParameter : IMessageDialogActionParameter
{
/// <summary>
/// MessageBoxに表示するメッセージ
/// </summary>
public string Message { get; protected set; }
/// <summary>
/// MessageBox に表示するタイトル
/// </summary>
public string Caption { get; protected set; }
/// <summary>
/// MessageBox に表示するボタン
/// </summary>
public MessageBoxButton Button { get; protected set; }
/// <summary>
/// true:ダイアログ(ユーザ応答を処理する)、false:メッセージ
/// </summary>
public bool IsDialog { get; protected set; }
/// <summary>
/// MessageDialogActionへ 渡すパラメーター(メッセージ表示用)
/// ボタン表示は OK のみ
/// </summary>
/// <param name="message"></param>
/// <param name="caption"></param>
public MessageDialogActionParameter(string message, string caption)
: this(message, caption, MessageBoxButton.OK, false) { }
/// <summary>
/// MessageDialogActionへ 渡すパラメーター(ダイアログ表示用)
/// </summary>
/// <param name="message"></param>
/// <param name="caption"></param>
/// <param name="button"></param>
public MessageDialogActionParameter(string message, string caption, MessageBoxButton button)
: this(message, caption, button, true) { }
/// <summary>
/// MessageDialogActionへ 渡すパラメーター
/// </summary>
/// <param name="message"></param>
/// <param name="caption"></param>
/// <param name="button"></param>
/// <param name="isDialog"></param>
public MessageDialogActionParameter(string message, string caption, MessageBoxButton button, bool isDialog)
{
Message = message;
Caption = caption;
Button = button;
IsDialog = isDialog;
}
}
}
続けて「ModalViewModelBase」クラスを作成します。
データ検証とモーダルダイアログ表示機能を持つビューモデルの基底クラスです。
using System.Windows.Input;
namespace MakCraft.ViewModels
{
/// <summary>
/// データ検証とモーダルダイアログ表示機能を持つビューモデルの基底クラスです。
/// </summary>
public abstract class ModalViewModelBase : DialogViewModelBase
{
public ModalViewModelBase() { }
private bool? _result;
/// <summary>
/// View 側の DialogResult セット用の PropertyChangedTrigger へバインドします。
/// </summary>
public bool? Result
{
get { return _result; }
set
{
_result = value;
base.RaisePropertyChanged(() => Result);
}
}
private void okExecute()
{
// Result プロパティに true をセットすることで Window のトリガが起動し、Window の DialogResult プロパティに値をセットする
Result = true;
}
/// <summary>
/// OkCommand の 有効/無効 を返します。
/// データ検証エラーの有無を返します。データ検証エラーを用いないで判断したい場合はオーバーライドしてください。
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
protected virtual bool OkCanExecute(object param)
{
return base.IsValid;
}
private ICommand _okCommand;
/// <summary>
/// OK ボタン用のコマンドです。
/// ボタンの有効・無効をコントロールするため、コマンドで Window の DialogResult を設定します。
/// </summary>
public ICommand OkCommand
{
get
{
if (_okCommand == null)
_okCommand = new RelayCommand(okExecute, OkCanExecute);
return _okCommand;
}
}
}
}
続けて「TransitionContainerBase」クラスを作成します。
画面遷移の際のデータコンテナの基底クラスです。
abstract 指定をしていないので、画面遷移で引き渡すデータがない場合には(そのようなケースが有るか不明ですが)、このクラスをインスタンス化することもできます。
using System;
namespace MakCraft.ViewModels
{
/// <summary>
/// 画面遷移の際のデータコンテナの基底クラス
/// </summary>
public class TransitionContainerBase : ITransContainer
{
/// <summary>
/// 画面遷移のキー及び遷移開始元ビューモデルを設定して画面遷移の際のデータコンテナを作成します。
/// </summary>
/// <param name="key"></param>
/// <param name="viewModel"></param>
public TransitionContainerBase(string key, TransitionViewModelBase viewModel)
{
if (viewModel == null) throw new ArgumentException(
"viewmodel に null は指定できません。");
_key = key;
_transStartViewmodel = viewModel;
PreviousViewModel = viewModel;
}
#region ITransContainer
private readonly string _key;
/// <summary>
/// 遷移を区別するためのキーを取得します。
/// 一つのビューモデルで複数の画面遷移を持つ場合の処理の分岐用
/// </summary>
public string Key
{
get { return _key; }
}
private readonly TransitionViewModelBase _transStartViewmodel;
/// <summary>
/// 遷移動作の開始元ビューモデルを取得します。
/// </summary>
public TransitionViewModelBase TransStartViewModel
{
get { return _transStartViewmodel; }
}
/// <summary>
/// 前画面のビューモデルを取得・設定します。
/// </summary>
public TransitionViewModelBase PreviousViewModel { get; set; }
#endregion
}
}
続けて「TransitionViewModelBase」クラスを作成します。
データ検証と画面遷移及び表示状態設定機能を持つビューモデルの基底クラスです。
using System;
using System.Windows.Input;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.ViewModels
{
/// <summary>
/// データ検証と画面遷移及び表示状態設定機能を持つビューモデルの基底クラスです。
/// </summary>
public abstract class TransitionViewModelBase : DialogViewModelBase, IWindowCloseCommand, IViewModelStatus, IReceiveFinished
{
public TransitionViewModelBase() { }
private WindowAction _windowAction;
/// <summary>
/// ウィンドウの表示状態を取得・設定します。
/// </summary>
public WindowAction DisplayMode
{
get { return _windowAction; }
protected set
{
_windowAction = value;
base.RaisePropertyChanged(() => DisplayMode);
}
}
/// <summary>
/// 前画面のビューモデルを取得します。
/// </summary>
protected TransitionViewModelBase PreviousViewModel { get; private set; }
/// <summary>
/// ウィンドウ作成元からのデータ受け取り用プロパティ
/// </summary>
public override object Container
{
get
{
return base.Container;
}
set
{
var container = value as ITransContainer;
if (container == null) throw new InvalidCastException(
"渡されたコンテナが ITransContainer インターフェイスを実装していません。");
if (container.PreviousViewModel == null) throw new InvalidOperationException(
"渡されたコンテナに前画面のビューモデルが設定されていません。");
// 前画面のビューモデルを保存
PreviousViewModel = container.PreviousViewModel;
base.Container = value;
}
}
/// <summary>
/// ウィンドウが閉じることの出来る状態かどうかを返します。
/// 仮想メソッドは常に 'true' を返します。制御が必要な場合はオーバーライドしてください。
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
protected virtual bool WindowCloseCanExecute(object param)
{
return true;
}
private ICommand _windowCloseCommand;
/// <summary>
/// ウィンドウを閉じるコマンド
/// </summary>
public ICommand WindowCloseCommand
{
get
{
if (_windowCloseCommand == null)
_windowCloseCommand = new RelayCommand(WindowClose, WindowCloseCanExecute);
return _windowCloseCommand;
}
}
/// <summary>
/// ウィンドウがクローズされた際の操作
/// </summary>
protected virtual void OnWindowClosed()
{
if (Container != null || Container as ITransContainer == null)
{
var container = Container as ITransContainer;
if (CurrentStatus == ViewModelStatus.Completed)
{
if (container.TransStartViewModel != PreviousViewModel &&
PreviousViewModel != ViewModelUtility.GetMainWindowViewModel())
{ // 直前の画面が遷移の開始元でなくなおかつ 直前の画面が MainWindow でなければ閉じる
PreviousViewModel.DisplayMode = WindowAction.Close;
}
else
{ // 直前の画面が遷移の開始元 または 直前の画面が MainWindow のときは直前の画面を表示する
PreviousViewModel.DisplayMode = WindowAction.Show;
// 遷移の開始元の画面遷移完了時の処理をキック
container.TransStartViewModel.OnFinished(container);
}
}
else
{
PreviousViewModel.DisplayMode = WindowAction.Show;
}
}
}
private ICommand _windowClosedCommand;
/// <summary>
/// ウィンドウがクローズされた際の操作コマンド
/// ウィンドウの Closed イベントが発生した際に呼び出されるようにしてください。
/// </summary>
public ICommand WindowClosedCommand
{
get
{
if (_windowClosedCommand == null)
_windowClosedCommand = new RelayCommand(OnWindowClosed);
return _windowClosedCommand;
}
}
/// <summary>
/// 一連の画面遷移の完了を設定します。
/// </summary>
protected virtual void TransitionComplete()
{
CurrentStatus = ViewModelStatus.Completed;
WindowClose();
}
#region IWindowCloseCommand
/// <summary>
/// ウィンドウを閉じることが可能かを取得します。
/// </summary>
public bool CanCloseWindow
{
get { return WindowCloseCanExecute(null); }
}
/// <summary>
/// ビューモデルからウィンドウへ Close を通知するメソッドです。
/// </summary>
public virtual void WindowClose()
{
// コンテナの前画面情報をクリア
if (Container != null)
{
var container = Container as ITransContainer;
container.PreviousViewModel = null;
}
// Window へ Close を通知する。
DisplayMode = WindowAction.Close;
}
#endregion IWindowCloseCommand
#region IViewModelStatus
// ステータスの初期値を Halfway にしておく(Window 作成・表示は ModelessDialogTransferDataAction が行うため)
// 状態変更時の操作は DisplayModeAction が行う
private ViewModelStatus _modelStatus = ViewModelStatus.Halfway;
/// <summary>
/// ビューモデルの処理状態を取得・設定します。
/// </summary>
public ViewModelStatus CurrentStatus
{
get { return _modelStatus; }
set { _modelStatus = value; }
}
#endregion IViewModelStatus
#region IReceiveFinished
/// <summary>
/// 画面遷移完了時に実行する処理です。
/// </summary>
public virtual void OnFinished(ITransContainer container)
{
}
#endregion IReceiveFinished
}
}
続けて「ViewModelUtility」クラスを作成します。
ViewModel 関連のユーティリティクラスです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace MakCraft.ViewModels
{
/// <summary>
/// ViewModel 関連のユーティリティクラス
/// </summary>
public static class ViewModelUtility
{
/// <summary>
/// MainWindow となっている Window の ViewModel を返します。
/// </summary>
/// <returns></returns>
public static ViewModelBase GetMainWindowViewModel()
{
var viewModel = Application.Current.MainWindow.DataContext;
var result = viewModel as ViewModelBase;
if (result == null) throw new InvalidCastException(
"MainWindow の ViewModel が ViewModelBase から派生していません。");
return result;
}
/// <summary>
/// 指定されたビューモデルのインスタンスの数を返します。
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static int Count(Type type)
{
if (!type.IsSubclassOf(typeof(ViewModelBase))) throw new ArgumentException(
string.Format("引数の型が ViewModelBase の派生クラスになっていません(引数の型:{0})。", type.Name));
var result = 0;
targetViewModelDoAction(type, n => ++result);
return result;
}
/// <summary>
/// 指定されたビューモデルのインスタンスの一覧を返します。
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IReadOnlyList<ViewModelBase> GetViewModels(Type type)
{
if (!type.IsSubclassOf(typeof(ViewModelBase))) throw new ArgumentException(
string.Format("引数の型が ViewModelBase の派生クラスになっていません(引数の型:{0})。", type.Name));
var result = new List<ViewModelBase>();
targetViewModelDoAction(type, n => result.Add(n));
return result;
}
/// <summary>
/// 指定されたビューモデルのインスタンスの IWindowCloseCommand インターフェイス の
/// WindowClose メソッドを実行します。
/// </summary>
/// <returns></returns>
public static void CloseViewModels(Type type)
{
if (!type.IsSubclassOf(typeof(ViewModelBase))) throw new ArgumentException(
string.Format("引数の型が ViewModelBase の派生クラスになっていません(引数の型:{0})。", type.Name));
if (Count(type) == 0) return;
var list = GetViewModels(type);
if (list.First() as IWindowCloseCommand == null)
{
throw new InvalidCastException(
"オブジェクトは IWindowCloseCommand インターフェイスを実装していません。: " + type.ToString());
}
// ウィンドウが閉じることのできる状態か確認
if (!isReadyCloseWindows(list))
{
throw new WindowPendingProcessException(
string.Format("ViewModel:{0} が閉じることの出来る状態にありません。", type.Name));
}
// ウィンドウを閉じる
foreach (var n in list)
{
(n as IWindowCloseCommand).WindowClose();
}
}
/// <summary>
/// すべてのウィンドウが閉じることが可能か確認します。
/// </summary>
public static bool IsReadyCloseAllWindows
{
get
{
var list = new List<ViewModelBase>();
targetViewModelDoAction(typeof(ViewModelBase), n => list.Add(n), true);
return isReadyCloseWindows(list);
}
}
// リストで渡されたウィンドウが閉じることが可能か確認します。
private static bool isReadyCloseWindows(IReadOnlyList<ViewModelBase> list)
{
var result = true;
foreach (var n in list)
{
var vm = n as IWindowCloseCommand;
if (vm == null) throw new InvalidCastException(
"ViewModel は IWindowCloseCommand インターフェイスを実装していません。: " + n.GetType().Name);
if (!vm.CanCloseWindow)
{
result = false;
break;
}
}
return result;
}
// Application クラスで管理しているインスタンス化された Window のコレクションからビューモデルを取得して、action の処理を行います。
private static void targetViewModelDoAction(Type type, Action<ViewModelBase> action, bool isSubClass = false)
{
foreach (var n in Application.Current.Windows)
{
var window = n as Window;
if (window.DataContext == null) continue;
var vmType = window.DataContext.GetType();
var cond = isSubClass ? vmType == type || vmType.IsSubclassOf(typeof(ViewModelBase)) :
vmType == type;
if (cond)
{
var viewModel = window.DataContext as ViewModelBase;
if (viewModel != null)
{
action(viewModel);
}
}
}
}
}
}
最後に「WindowPendingProcessException」クラスを作成します。
ウィンドウを閉じようとした際にビューモデルが処理途中等で閉じることができない場合にスローされる例外です。
using System;
using System.Runtime.Serialization;
namespace MakCraft.ViewModels
{
/// <summary>
/// ウィンドウを閉じようとした際にビューモデルが処理途中等で閉じることができない場合にスローされる例外
/// </summary>
[Serializable]
public class WindowPendingProcessException : Exception
{
public WindowPendingProcessException() : base() { }
public WindowPendingProcessException(string message) : base(message) { }
public WindowPendingProcessException(string message, Exception inner) : base(message, inner) { }
public WindowPendingProcessException(SerializationInfo info, StreamingContext context) { }
}
}
以上でライブラリが出来ました。
アプリケーション例を含めた TransitionViewModelBase の ZIP ファイルを ViewModelBase のページからダウンロードできるようにしたので、ファイルがほしい方はそちらから入手してください。
次回は、このライブラリを使ったアプリケーション例を書く予定です。
「ダイアログ表示を行うビューモデル(その3)」への2件のフィードバック