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ストアアプリのちゃんとした作り方を勉強したいなぁ。

C#小話 : MEFってスレッドセーフなの?

「MEFって何?」という方は

Managed Extensibility Framework (MEF)

とか

.NET: はじめての MEF - sardineの日記

を見ていただくとして。 早速タイトルの解答です。

Ans. スレッドセーフです。安心して別スレッドから呼び出したりできます。

調べてみたのです。

仕事でコードを書いているときに、「MEFで使っているメソッドって、別スレッドから動かしても元のスレッドに影響しないの?」と聞かれ、分からなかったので調べてみたのです。

答えはこちら。

MEF for Beginner (Part Creation Policy) - part 6 |

に書いてありました。

What is the default instantiation?

the default instantiation model of MEF is the singleton model.

Best practice

the best-practice is to use the Shared (singleton) model. this bring us to design consideration which suggest that you should design your exportable parts as stateless and thread safe, so they won’t be affected by multiple calls (maybe on different threads) upon the same instance.

デフォルトでシングルトンになってる。ステートレスでスレッドセーフ。 複数呼び出しがかかることを前提に設計していたのですね。素晴らしい。 これで、安心してMEFを非同期で使ったりできますね。

それにしても、まさかイスラエルのサイトまで探しにいくとは思わなかったよ。Microsoftのサイトだけどさ。