DI コンテナ Unity の利用(その5)

前回に引き続き DI コンテナ Unity の利用についてです。

前回は WPF の MVVM パターンでの Unity DI コンテナの利用について、DI コンテナの設定まで説明したので、その続きです。

次はビューモデル層です。
プロジェクトに「ViewModels」フォルダを作成し、ViewModels フォルダに InputViewModel クラスと MainViewModel クラスを作成します。

using System;
using System.Windows.Input;

using MakCraft.ViewModels;
using WpfMvvmUnityDi.Models;
using WpfMvvmUnityDi.Services;

namespace WpfMvvmUnityDi.ViewModels
{
    class InputViewModel : ModalViewModelBase
    {
        public InputViewModel(IBookService bookService)
        {
            System.Diagnostics.Trace.WriteLine("InputViewModel instance: Create!");

            _bookService = bookService;
        }

        private IBookService _bookService;

        private string _title;
        public string Title
        {
            get { return _title; }
            set { base.SetProperty(ref _title, value); }
        }

        private string _author;
        public string Author
        {
            get { return _author; }
            set { base.SetProperty(ref _author, value); }
        }

        private void addBookExecute()
        {
            var book = new Book { Title = Title, Author = Author };
            _bookService.AddBook(book);
            base.Result = true;
        }
        private ICommand _addBookCommand;
        public ICommand AddBookCommand
        {
            get
            {
                if (_addBookCommand == null)
                {
                    _addBookCommand = new RelayCommand(addBookExecute);
                }
                return _addBookCommand;
            }
        }

        protected override void Dispose(bool disposing)
        {
            System.Diagnostics.Trace.WriteLine("InputViewModel instance: Dispose!");

            (_bookService as IDisposable)?.Dispose();

            base.Dispose(disposing);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Windows.Input;

using MakCraft.ViewModels;
using WpfMvvmUnityDi.Models;
using WpfMvvmUnityDi.Services;

namespace WpfMvvmUnityDi.ViewModels
{
    class MainViewModel : ModalViewModelBase
    {
        public MainViewModel(IBookService bookService)
        {
            System.Diagnostics.Trace.WriteLine("MainViewModel instance: Create!");

            _bookService = bookService;
        }

        private IBookService _bookService;

        private IEnumerable<Book> _books;
        public IEnumerable<Book> Books
        {
            get { return _books; }
            set { base.SetProperty(ref _books, value); }
        }

        private object _modalKick;
        // モーダルダイアログの表示のキック用
        public object ModalKick
        {
            get { return _modalKick; }
            set { base.SetProperty(ref _modalKick, value); }
        }

        private void showInputWindowExecute()
        {
            base.DialogType = typeof(InputWindow);
            base.CommunicationDialog = null;
            base.DialogActionCallback = dialogResult =>
            {
                if (dialogResult.HasValue && dialogResult.Value)
                {
                    Books = _bookService.GetAllBooks();
                }
            };
            ModalKick = new object();
        }
        private ICommand _showInputWindowCommand;
        public ICommand ShowInputWindowCommand
        {
            get
            {
                if (_showInputWindowCommand == null)
                {
                    _showInputWindowCommand = new RelayCommand(showInputWindowExecute);
                }

                return _showInputWindowCommand;
            }
        }

        private void gcExecute()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
        private ICommand _gcCommand;
        public ICommand GcCommand
        {
            get
            {
                if (_gcCommand == null)
                {
                    _gcCommand = new RelayCommand(gcExecute);
                }
                return _gcCommand;
            }
        }

        protected override void Dispose(bool disposing)
        {
            System.Diagnostics.Trace.WriteLine("MainViewModel instance: Dispose!");

            (_bookService as IDisposable)?.Dispose();

            base.Dispose(disposing);
        }
    }
}

モーダルダイアログの操作を行うので、どちらのクラスも MakCraft.ViewModels.ModalViewModelBase クラスを継承させています。
InputViewModel のほうは、base.Result プロパティに true が設定されると、Window.DialogResult に true が設定されてウィンドウが閉じます(この部分の設定は XAML 側で行います)。

MainViewModel のほうは、入力画面表示ボタンのクリックで実行される showInputWindowExecute メソッドで、表示するウィンドウの型とコールバックを設定して、ModalKick プロパティに新しい object を設定することで、モーダルダイアログの表示をキックさせています(この部分の設定は XAML 側で行います)。
コールバックには、Window.DialogResult が true で返って来たときに、一覧表示用の Books プロパティにサービス層から再取得した本の一覧データを設定しています(コレクション変更を通知する System.Collections.ObjectModel.ObservableCollection<T> クラスを利用する方法もありますが、ここでは簡易な方法を用いています)。

最後にウィンドウです。最初は入力ウィンドウです。
プロジェクトのルートに「InputWindow」を作成します。
コードビハインドに UnityContainer の取得、InputViewModel のインスタンスの生成、DataContext の設定を記述します。

using Microsoft.Practices.Unity;
using System.Windows;

namespace WpfMvvmUnityDi
{
    /// <summary>
    /// InputWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class InputWindow : Window
    {
        public InputWindow()
        {
            var container = DiContainer.DIContainer.Container;
            this.DataContext = container.Resolve<ViewModels.InputViewModel>();

            InitializeComponent();
        }
    }
}

で、InputWindow.xaml です。

<Window x:Class="WpfMvvmUnityDi.InputWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:local="clr-namespace:WpfMvvmUnityDi"
        mc:Ignorable="d"
        WindowStartupLocation="CenterOwner" Name="Window"
        Title="InputWindow" Height="180" Width="360">
    <i:Interaction.Triggers>
        <!-- DialogResult セット用のトリガ -->
        <ei:PropertyChangedTrigger Binding="{Binding Result}">
            <ei:ChangePropertyAction
                TargetObject="{Binding ElementName=Window}" PropertyName="DialogResult" Value="{Binding Result}" />
        </ei:PropertyChangedTrigger>
    </i:Interaction.Triggers>

    <StackPanel FocusManager.FocusedElement="{Binding ElementName=TitleBox}">
        <TextBlock
            Margin="10" HorizontalAlignment="Center" FontSize="20"
            Text="本の情報を入力" />

        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="40" Text="タイトル:" />
            <TextBox Name="TitleBox" MinWidth="300" Text="{Binding Title}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="40" Text="作者:" />
            <TextBox MinWidth="300" Text="{Binding Author}" />
        </StackPanel>

        <Button
                    HorizontalAlignment="Right" Margin="10" Padding="20 5" Content="入力"
                    Command="{Binding AddBookCommand}" />
    </StackPanel>
</Window>

PropertyChangedTrigger でビューモデルの Result プロパティが変更されたときに Window の DialogResult プロパティに ビューモデルの Result プロパティの値を設定するようにしています。

次にメインウィンドウです。
InputWindow と同様にコードビハインドにビューモデルのインスタンスの取得と DataContext への設定を記述します。

using Microsoft.Practices.Unity;
using System.Windows;

namespace WpfMvvmUnityDi
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var container = DiContainer.DIContainer.Container;
            this.DataContext = container.Resolve<ViewModels.MainViewModel>();

            InitializeComponent();
        }
    }
}

で、MainWindow.xaml

<Window x:Class="WpfMvvmUnityDi.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:mb="clr-namespace:MakCraft.Behaviors;assembly=MakViewModelsBase"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfMvvmUnityDi"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Triggers>
        <ei:PropertyChangedTrigger Binding="{Binding ModalKick}">
            <mb:DialogTransferDataAction
                Parameter="{Binding CommunicationDialog}" DialogType="{Binding DialogType}" DialogMode="Modal"
                ActionCallBack="{Binding DialogActionCallback}" ResultViewModel="{Binding ResultViewModel}" />
        </ei:PropertyChangedTrigger>
    </i:Interaction.Triggers>

    <StackPanel>
        <TextBlock
            Margin="10" HorizontalAlignment="Center" FontSize="20" Text="MVVM での DI コンテナのテスト" />

        <GroupBox Header="本の一覧">
            <DataGrid
            ItemsSource="{Binding Books}" />
        </GroupBox>

        <Button
            Margin="10" Padding="20 5" HorizontalAlignment="Right"
            Content="入力画面表示"
            Command="{Binding ShowInputWindowCommand}" />

        <Button
            Margin="10" Padding="35 5" HorizontalAlignment="Right"
            Content="GC 実行"
            Command="{Binding GcCommand}" />
    </StackPanel>
</Window>

PropertyChangedTrigger でビューモデルの ModalKick プロパティが変更されたときに MakCraft.Behaviors.DialogTransferDataAction をキックして、Parameter に ビューモデルの CommunicationDialog(今回は使わない)、DialogType にビューモデルの DialogType、DialogMode に “Modal”、ActionCallBack に ビューモデルの DialogActionCallback、ResultViewModelにビューモデルの ResultViewModel(今回は使わない) をバインド | 設定することで、モーダルダイアログを表示し、操作結果を取得にするようにしています。

最後に App.xaml へのイベントハンドラの記述とコードビハインドへの処理内容の記述です。
DispatcherUnhandledException と Exit のイベントハンドラを登録しています。
App.xaml

<Application x:Class="WpfMvvmUnityDi.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfMvvmUnityDi"
             StartupUri="MainWindow.xaml"
             DispatcherUnhandledException="Application_DispatcherUnhandledException"
             Exit="Application_Exit">
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.xaml.cs

using System.Windows;

namespace WpfMvvmUnityDi
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            // ハンドリングできていない例外の発生を表示/記録する場合は、ここに記述する。
            MessageBox.Show("例外が発生したので終了します。", "例外が発生", MessageBoxButton.OK, MessageBoxImage.Stop);

            // アプリケーションを終了させる。
            e.Handled = true;
            Shutdown();
        }

        private void Application_Exit(object sender, ExitEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("DIContainer instance: Dispose!");
            DiContainer.DIContainer.Container.Dispose();
        }
    }
}

冒頭の全体の構成で記述したように、アプリケーション終了時に DI コンテナの Dispose メソッドを呼び出すようにしています。

これでアプリケーションをデバッグ実行して、入力ウィンドウを2回表示して、アプリケーションを終了させたときの出力ウィンドウへの表示は次のようになります(見やすくするために入力ウィンドウが閉じられた後に「GC実行」ボタンをクリックしガベージコレクションを強制的に行っています)。

DIContainer instance: Create!
Datalayer instance: Create!
BookService instance: Create!
MainViewModel instance: Create!
BookService instance: Create!
InputViewModel instance: Create!
InputViewModel instance: Dispose!
BookService instance: Dispose!
BookService instance: Create!
InputViewModel instance: Create!
InputViewModel instance: Dispose!
BookService instance: Dispose!
DIContainer instance: Dispose!
Datalayer instance: Dispose!
MainViewModel instance: Dispose!
BookService instance: Dispose!
プログラム '[13696] WpfMvvmUnityDi.vshost.exe' はコード 0 (0x0) で終了しました。

DataLayer クラスのインスタンスの生存期間は DI コンテナと同一で、BookService クラスのインスタンスはビューモデルのインスタンスが作られる都度、作成されているのがわかります。


1 thought on “DI コンテナ Unity の利用(その5)

コメントを残す

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