Create a new world

きっと誰もが新しい世界を作り続けているんだよ。

WindowsストアアプリでMVVMを扱う。 MVVM Light Toolkit Messenger編

昨年の年末、コミックマーケットC85で出した

Far East Developer Review - DevLOVE Pub the booth

に寄稿した「MVVMで始めるWindowsストアアプリ」の続きになります。

(「MVVMで始めるWindowsストアアプリ」の記事が読めるFar East Developer Review - DevLOVE Pub the boothDevLOVE Pub the boothにて頒布しています。)

Messengerパターンを扱う

Messengerパターンは、ViewModelからViewに要求を送る場合にとるパターン(のよう)です。

Messageを詰めるオブジェクト(Messageオブジェクト)を用意し、ViewModelにてMessageオブジェクトを送ると、Messageオブジェクトを受け取るように定義したViewにて、Messageオブジェクトを受け取れる仕組みです。

(詳しくはこのあたりとか読んでみるといいかも。 いまさら聞けない「MVVM + Messenger パターン」超入門 - present

今回はViewModelから別のViewModelにMessageを飛ばし、受け取ったViewModelでMessageオブジェクトの中身を表示するやり方を書いてみます。

まずは、ViewModelLocator.csにViewModelの定義を追加します。

/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class ViewModelLocator
{
    static ViewModelLocator ()
    {
        ServiceLocator . SetLocatorProvider (() => SimpleIoc. Default );

        if (ViewModelBase . IsInDesignModeStatic )
        {
            SimpleIoc .Default . Register< IDataService , Design . DesignDataService >();
        }
        else
        {
            SimpleIoc .Default . Register< IDataService , DataService > ();
        }

        SimpleIoc .Default . Register< MainViewModel >();
        SimpleIoc .Default . Register< UserControlViewModel > ();
    }

    /// <summary>
    /// Gets the Main property.
    /// </summary>
    [ System .Diagnostics . CodeAnalysis. SuppressMessage ( "Microsoft.Performance" ,
        "CA1822:MarkMembersAsStatic" ,
        Justification = "This non-static member is needed for data binding purposes." )]
    public MainViewModel Main
    {
        get
        {
            return ServiceLocator . Current. GetInstance <MainViewModel > ();
        }
    }

    /// <summary>
    /// Gets the Main property.
    /// </summary>
    [ System .Diagnostics . CodeAnalysis. SuppressMessage ( "Microsoft.Performance" ,
        "CA1822:MarkMembersAsStatic" ,
        Justification = "This non-static member is needed for data binding purposes." )]
    public UserControlViewModel UserControl
    {
        get
        {
            return ServiceLocator . Current. GetInstance <UserControlViewModel > ();
        }
    }

    /// <summary>
    /// Cleans up all the resources.
    /// </summary>
    public static void Cleanup ()
    {
    }
}

Message送信元のViewにあるGridViewで選択されたItemをMessage受信先のUserControlで受け取り表示させます。 今回は手間を省きたかったので、受信先のUserControlは送信元のViewで表示するようにしています。

< Grid Grid.Row= "1" >
    < Grid.RowDefinitions >
        < RowDefinition Height = "Auto" />
    </ Grid.RowDefinitions >
    < Grid.ColumnDefinitions >
        < ColumnDefinition Width= "500" />
        < ColumnDefinition Width= "*" />
    </ Grid.ColumnDefinitions >
    < GridView x : Name= "ItemsGridView" Margin ="120, 0, 0, 0"Grid.Row ="0" Grid.Column ="0" ItemsSource="{ Binding Items} "  SelectedItem="{ Binding SelectedGridItem , Mode = TwoWay} " >
        < GridView.ItemTemplate >
            < DataTemplate>
                < TextBlock Text ="{ Binding Name } " Width = "250"Height= "60" FontSize ="40" />
            </ DataTemplate>
        </ GridView.ItemTemplate >
    </ GridView>
    < ml: UserControlView Grid.Row ="0" Grid.Column ="1" />
</ Grid >
< UserControl
    x :Class = "MvvmLight4.UserControlView"
    xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns :x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns :local = "using:MvvmLight4"
    xmlns :d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns :mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns :common = "using:MvvmLight4.Common"
    xmlns :ignore = "http://www.ignore.com"
    mc :Ignorable = "d ignore"
    d :DesignHeight = "300"
    d :DesignWidth = "400"
    DataContext ="{Binding UserControl , Source ={ StaticResourceLocator}} " >
    < Grid>
        < Grid.RowDefinitions >
            < RowDefinition Height = "50" />
        </ Grid.RowDefinitions >
        < Grid.ColumnDefinitions >
            < ColumnDefinition Width= "200" />
        </ Grid.ColumnDefinitions >
        < TextBlock Grid.Row = "0" Grid.Column = "0" Text ="{ Binding ReceiveItem . Name} " />
    </ Grid>
</ UserControl >

さて、ここからが本番。 MainPage.xamlで定義しているGridViewで選択されたItemをMessageとして送信していきます。

まずは、Messageオブジェクトの定義から。

public class ItemChangeMessage
{
    public ItemChangeMessage ( Item item )
    {
        Item = item;
    }

    public Item Item{ get ; private set; }
}

このItemChangeMessageを送信するMainViewModel.csはこんな感じ。

public class MainViewModel : ViewModelBase
{
    private readonly IDataService _dataService ;

    private List < Item> _items;
    private Item _selectedGridItem ;

    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel ( IDataService dataService )
    {
        _dataService = dataService;

        var items = new List < Item> ();
        items .Add ( new Item ("hogehoge" ));
        items .Add ( new Item ("hugahuga" ));

        Items = items;

        this .PropertyChanged += MainViewModel_PropertyChanged ;
    }

    void MainViewModel_PropertyChanged ( object sender , System . ComponentModel .PropertyChangedEventArgs e)
    {
        MessengerInstance . Send( new ItemChangeMessage ( _selectedGridItem ));
    }

    public List < Item> Items
    {
        get
        {
            return _items ;
        }
        set
        {
            if (_items != value )
            {
                _items = value;
                RaisePropertyChanged ( "Items" );
            }
        }
    }

    public Item SelectedGridItem
    {
        get
        {
            return _selectedGridItem ;
        }
        set
        {
            if (_selectedGridItem != value )
            {
                _selectedGridItem = value ;
                RaisePropertyChanged ( "SelectedGridItem" );
            }
        }
    }
}

GridViewで選択しているItemが変わる→SelectedGridItemが変わる→MainViewModelのプロパティが変わり、MainViewModel_PropertyChangedイベントが走る。→イベント内でMessageを送る。

という流れですね。

MainViewModelで送信したMessageを、UserControlViewModelで受けます。

public class UserControlViewModel : ViewModelBase
{
    private readonly IDataService _dataService ;

    private Item _item;

    public UserControlViewModel ( IDataService dataService )
    {
        _dataService = dataService;

        MessengerInstance . Register< ItemChangeMessage > (this ,this. GetItemChangeMessage );
    }

    public void Initialize()
    {
             
    }

    public void GetItemChangeMessage (ItemChangeMessage message)
    {
        ReceiveItem = message. Item ;       
    }

    public Item ReceiveItem
    {
        get { return _item; }
        set {
            if (_item != value )
            {
                _item = value;
                RaisePropertyChanged ( "ReceiveItem" );
            }
        }
    }
}

UserControlViewModel.csのコンストラクタで、ItemChangeMessageを受信したときに呼び出すメソッドを登録しています。 これで、MainViewModel.csからItemChangeMessageを送信したら、UserControlViewModelで受け取れるようになります。 受け取ったItemChangeMessageは、GetItemChangeMessageメソッドでMessageの中身を取り出し、プロパティに入れてViewに通知をしています。

これで、MVVM + Messengerパターンを動かすことができるようになりました。

もともと、「MVVMで作るWindowsストアアプリ」を執筆していたときは「Caliburn.micro使えないじゃんうわーん」だったのですが、どうもNuGet経由がダメだということかもしれないです。

なので、今度はCaliburn.microを使ってWindowsストアアプリを試してみようかなと思ってます。

その前に、Windowsストアアプリのちゃんとした作り方を勉強したいなぁ。