ダイアログ表示を行うビューモデル(その2)

ダイアログ表示を行うビューモデル(その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

ValidationViewModelBase

  • 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 thoughts on “ダイアログ表示を行うビューモデル(その2)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です