ダイアログ表示を行うビューモデル(その1)の続きです。
前回仕様を書いたので、プログラムに入ります。
今回は「プロジェクトの作成」「プロジェクトへの設定」「ビヘイビア関係のプログラム」を書きます。ビューモデル関係は次回書く予定です。
まずはプロジェクトの設定から。
「新しいプロジェクト」から「クラスライブラリ」を選択し、プロジェクトの名前を入力します(TransitionViewModelBase としています)。
対象のフレームワークですが、ビューモデルを ValidationViewModelBase から派生させるので、.NET Framework 4.5 になります。
また、以下に掲載するプログラムは、プロジェクトのプロパティの「既定の名前空間」が「MakCraft」となっていることが前提の記述となっています。
まずは必要な DLL ファイルへの参照を追加します。
- PresentationCore
- PresentationFramework
- System.Xaml
- WindowsBase
BlendSDK
C:\Program Files\Microsoft SDKs\Expression\Blend\.NETFramework\v4.5\Libraries にあります。
– Visual Studio 2012 では、Visual Studio 2012 Update2 がインストールされている必要があります。
- Microsoft.Expression.Interactions
- System.Windows.Interactivity
- MakCraft.ValidationViewModelBase
最初にビヘイビア関係から。
プロジェクトに「Behaviors」フォルダを作成し、Behaviors フォルダ下に「Interfaces」フォルダを作成します。
Interfaces フォルダに「IDialogTransferContainer」インターフェイスを作成します。
namespace MakCraft.Behaviors.Interfaces
{
/// <summary>
/// 生成元ウィンドウからのデータの受取用プロパティのインターフェイス
/// </summary>
public interface IDialogTransferContainer
{
/// <summary>
/// 生成元ウィンドウからのデータの受取用プロパティ
/// </summary>
object Container { get; set; }
}
}
続けて「IMessageDialogActionParameter」インターフェイスを作成します。
using System.Windows;
namespace MakCraft.Behaviors.Interfaces
{
/// <summary>
/// MessageDialogAction へ渡すパラメーターのインターフェイス
/// IsDialog が false のときには Button の設定は反映されません。
/// </summary>
public interface IMessageDialogActionParameter
{
/// <summary>
/// MessageBoxに表示するメッセージ
/// </summary>
string Message { get; }
/// <summary>
/// MessageBox に表示するタイトル
/// </summary>
string Caption { get; }
/// <summary>
/// MessageBox に表示するボタン
/// </summary>
MessageBoxButton Button { get; }
/// <summary>
/// true:ダイアログ(ユーザ応答を処理する)、false:メッセージ
/// </summary>
bool IsDialog { get; }
}
}
続けて「IViewModelStatus」インターフェイスを作成します。
namespace MakCraft.Behaviors.Interfaces
{
/// <summary>
/// 画面遷移を行うビューモデルの処理状況のプロパティのインターフェイス
/// </summary>
public interface IViewModelStatus
{
/// <summary>
/// 画面遷移を行うビューモデルの処理状況
/// </summary>
ViewModelStatus CurrentStatus { get; set; }
}
/// <summary>
/// 画面遷移を行うビューモデルの処理状況を表す列挙型です。
/// </summary>
public enum ViewModelStatus
{
/// <summary>
/// 未完了
/// </summary>
Halfway,
/// <summary>
/// 完了
/// </summary>
Completed
}
/// <summary>
/// 画面遷移を行うビューモデルへセットするウィンドウの状態
/// </summary>
public enum WindowAction
{
/// <summary>
/// 表示する
/// </summary>
Show,
/// <summary>
/// 非表示にする
/// </summary>
Hide,
/// <summary>
/// 閉じる
/// </summary>
Close
}
/// <summary>
/// ダイアログの表示種別
/// </summary>
public enum DialogModes
{
/// <summary>
/// モーダル ダイアログとして表示する。
/// </summary>
Modal,
/// <summary>
/// モードレス ダイアログとして表示する。
/// </summary>
Modeless
}
}
次に Behaviors フォルダに「DialogTransferDataAction」クラスを作成します。
ダイアログ ウィンドウを表示するアクションです。
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Input;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.Behaviors
{
/// <summary>
/// データを渡してダイアログ ウィンドウを表示するアクション
/// ダイアログ側のビューモデルにデータ受取り用の「public object Container」プロパティが必要
public class DialogTransferDataAction : TriggerAction<FrameworkElement>
{
/// <summary>
/// ダイアログウィンドウに渡すデータを格納
/// </summary>
public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register(
"Parameter", typeof(object), typeof(DialogTransferDataAction),
new UIPropertyMetadata(null)
);
public object Parameter
{
get { return (object)GetValue(ParameterProperty); }
set { SetValue(ParameterProperty, value); }
}
/// <summary>
/// 表示するダイアログのクラス名
/// </summary>
public static readonly DependencyProperty DialogTypeProperty = DependencyProperty.Register(
"DialogType", typeof(Type), typeof(DialogTransferDataAction),
new UIPropertyMetadata()
);
public Type DialogType
{
get { return (Type)GetValue(DialogTypeProperty); }
set { SetValue(DialogTypeProperty, value); }
}
/// <summary>
/// ダイアログの表示種別
/// </summary>
public static readonly DependencyProperty DialogModeProperty = DependencyProperty.Register(
"DialogMode", typeof(DialogModes), typeof(DialogTransferDataAction),
new UIPropertyMetadata()
);
public DialogModes DialogMode
{
get { return (DialogModes)GetValue(DialogModeProperty); }
set { SetValue(DialogModeProperty, value); }
}
/// <summary>
/// ダイアログを閉じた際に実行するコールバック
/// </summary>
public static readonly DependencyProperty ActionCallBackProperty = DependencyProperty.Register(
"ActionCallBack", typeof(Action<bool?>), typeof(DialogTransferDataAction),
new UIPropertyMetadata()
);
public Action<bool?> ActionCallBack
{
get { return (Action<bool?>)GetValue(ActionCallBackProperty); }
set { SetValue(ActionCallBackProperty, value); }
}
/// <summary>
/// 作成したウィンドウのビューモデルオブジェクトへの参照
/// ダイアログ側で設定したデータの参照用
/// </summary>
public static readonly DependencyProperty ResultViewModelProperty = DependencyProperty.Register(
"ResultViewModel", typeof(object), typeof(DialogTransferDataAction),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
public object ResultViewModel
{
get { return GetValue(ResultViewModelProperty); }
set { SetValue(ResultViewModelProperty, value); }
}
protected override void Invoke(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
// 指定された DialogType の確認
if (DialogType == null) throw new InvalidOperationException(
"DialogType が null のため、ダイアログを生成できません。");
if (!DialogType.IsSubclassOf(typeof(Window))) throw new InvalidOperationException(
string.Format("表示するように指定された {0} は Window の派生クラスではありません。", DialogType.Name));
// ダイアログの型からダイアログのインスタンスを作成
var window = Activator.CreateInstance(DialogType);
Mouse.OverrideCursor = null;
ResultViewModel = (window as Window).DataContext; // ビューモデルをプロパティへセット
// Parameter がある場合には ViewModel の Container へデータをセットする
if (Parameter != null)
{
var recievedViewModel = (window as Window).DataContext as IDialogTransferContainer;
if (recievedViewModel == null) throw new InvalidCastException(
string.Format("{0} のビューモデルが IDialogTransferContainer インターフェイスを実装していません。", DialogType.Name));
recievedViewModel.Container = Parameter;
}
if (DialogMode == DialogModes.Modal)
{
// モーダル ダイアログを表示する
if (ActionCallBack != null)
{
ActionCallBack((bool?)DialogType.InvokeMember("ShowDialog", System.Reflection.BindingFlags.InvokeMethod,
null, window, null));
}
else
{
DialogType.InvokeMember("ShowDialog", System.Reflection.BindingFlags.InvokeMethod,
null, window, null);
}
}
else
{
// モードレス ダイアログを表示する
DialogType.InvokeMember("Show", System.Reflection.BindingFlags.InvokeMethod,
null, window, null);
}
ResultViewModel = null; // 作成された ViewModel オブジェクトへの参照をクリアしておく。
}
}
}
続けて「DisplayModeAction」クラスを作成します。
モードレス ウィンドウの表示変更アクションです。
このアクションで、ウィンドウを非表示にするとビューモデルのステータスを「完了」へ、表示にすると「未完了」に変更しています。
using System;
using System.Windows;
using System.Windows.Interactivity;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.Behaviors
{
/// <summary>
/// モードレス ウィンドウの表示変更アクション
/// </summary>
public class DisplayModeAction : TriggerAction<FrameworkElement>
{
/// <summary>
/// 変更する表示状態
/// </summary>
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(
"DisplayMode", typeof(WindowAction), typeof(DisplayModeAction), new UIPropertyMetadata
{
DefaultValue = WindowAction.Show
});
public WindowAction DisplayMode
{
get { return (WindowAction)GetValue(DisplayModeProperty); }
set { SetValue(DisplayModeProperty, value); }
}
protected override void Invoke(object parameter)
{
if (AssociatedObject == null) return;
var window = Window.GetWindow(AssociatedObject);
switch (DisplayMode)
{
case WindowAction.Hide:
hide(window);
break;
case WindowAction.Show:
show(window);
break;
case WindowAction.Close:
window.Close();
break;
}
}
private static void hide(Window window)
{
// window が既に閉じられていたら何もしない
if (!windowContains(window)) return;
window.Hide();
// ViewModel のステータスを完了にする
var viewModel = window.DataContext as IViewModelStatus;
if (viewModel == null) throw new InvalidCastException(
string.Format("ViewModel が ITransitionViewModel インターフェイスを実装していません。",
window.DataContext.GetType().Name));
viewModel.CurrentStatus = ViewModelStatus.Completed;
}
private static void show(Window window)
{
// window が既に閉じられていたら何もしない
if (!windowContains(window)) return;
window.Show();
// ウィンドウのアクティブ化を試みます。再試行回数:5
var count = 0;
while (!window.Activate() && count < 4)
{
++count;
System.Diagnostics.Debug.WriteLine(string.Format("Retry Activate: {0}", count));
System.Threading.Thread.Sleep(50);
}
// ViewModel のステータスを未完了にする
var viewModel = window.DataContext as IViewModelStatus;
if (viewModel == null) throw new InvalidCastException(
string.Format("ViewModel が ITransitionViewModel インターフェイスを実装していません。",
window.DataContext.GetType().Name));
viewModel.CurrentStatus = ViewModelStatus.Halfway;
}
// window が閉じられていないかを返す
private static bool windowContains(Window window)
{
foreach (var n in Application.Current.Windows)
{
if (n == window) return true;
}
return false;
}
}
}
続けて「MessageDialogAction」クラスを作成します。
MessageBox を表示するアクションです。
using System;
using System.Windows;
using System.Windows.Interactivity;
using MakCraft.Behaviors.Interfaces;
namespace MakCraft.Behaviors
{
/// <summary>
/// MessageBox を表示するアクション
/// </summary>
public class MessageDialogAction : TriggerAction<FrameworkElement>
{
/// <summary>
/// メッセージボックスやダイアログを出すために必要となる情報を受け取る
/// </summary>
public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register(
"Parameter", typeof(IMessageDialogActionParameter), typeof(MessageDialogAction),
new UIPropertyMetadata()
);
public IMessageDialogActionParameter Parameter
{
get { return (IMessageDialogActionParameter)GetValue(ParameterProperty); }
set { SetValue(ParameterProperty, value); }
}
/// <summary>
/// ダイアログでの選択結果をViewModelに通知するコールバックメソッド
/// </summary>
public static readonly DependencyProperty ActionCallBackProperty = DependencyProperty.Register(
"ActionCallBack", typeof(Action<MessageBoxResult>), typeof(MessageDialogAction),
new UIPropertyMetadata(null)
);
public Action<MessageBoxResult> ActionCallBack
{
get { return (Action<MessageBoxResult>)GetValue(ActionCallBackProperty); }
set { SetValue(ActionCallBackProperty, value); }
}
protected override void Invoke(object obj)
{
if (Parameter.IsDialog)
ActionCallBack(MessageBox.Show(Parameter.Message, Parameter.Caption, Parameter.Button));
else
MessageBox.Show(Parameter.Message, Parameter.Caption);
}
}
}
ここまでで、ビヘイビア関係まで終わりました。
次回に続きます。
「ダイアログ表示を行うビューモデル(その2)」への2件のフィードバック