かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

MVVMでDataGridで選択されている行を新しいウィンドウに表示させる

MSDN フォーラムでMVVMの画面遷移についてという質問が上がってたので、Prismを使ってやってみました。
この例で使ってるInteractionRequestの使い方は、以下の記事を参照してください。

ViewModelの作成

今回は、ViewModelからViewへのプロパティの変更通知は必要ない小さなサンプルなので、あえてViewModelはINotifyPropertyChangedを実装していません。単純にめんどくさいからです。

まず、DataGridに表示させる行に対応するViewModelを作成します。

namespace SelectItemWindow
{
    // 画面遷移が主題なのでModelは省いてます
    public class PersonViewModel
    {
        public string Name { get; set; }
    }
}

そして、MainWindowに対応するViewModelを作ります。こいつが一番複雑ですね。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.ObjectModel;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Interactivity.InteractionRequest;

namespace SelectItemWindow
{
    public class MainWindowViewModel
    {
        // 今回はVMからVへはICollectionViewとして公開しようと思うのでCollectionViewSourceを使う
        private CollectionViewSource peopleSource = new CollectionViewSource();
        
        // DataGridに表示させるPersonViewModelのコレクション
        private ObservableCollection<PersonViewModel> people;

        // 子画面を表示させる処理をやるコマンド
        private DelegateCommand showSelectedItemCommand;

        // 子画面を表示するリクエストを投げる人
        private InteractionRequest<Notification> showPersonViewRequest = new InteractionRequest<Notification>();

        public MainWindowViewModel()
        {
            // ダミーデータを作ってCollectionViewSourceに設定する
            this.people = new ObservableCollection<PersonViewModel>(
                Enumerable.Range(1, 100)
                    .Select(i => new PersonViewModel { Name = "田中 太郎 No" + i }));
            this.peopleSource.Source = people;
        }

        // Viewに公開するICollectionView
        public ICollectionView PeopleView
        {
            get
            {
                return this.peopleSource.View;
            }
        }

        // ウィンドウ表示のためのリクエスト
        public InteractionRequest<Notification> ShowPersonViewRequest
        {
            get
            {
                return this.showPersonViewRequest;
            }
        }

        // Viewに操作を公開するためのコマンド
        public DelegateCommand ShowSelectedItemCommand
        {
            get
            {
                return this.showSelectedItemCommand = this.showSelectedItemCommand ??
                    new DelegateCommand(
                        ShowSelectedItem,
                        () => this.PeopleView.CurrentItem is PersonViewModel);
            }
        }

        private void ShowSelectedItem()
        {
            // 現在選択されているオブジェクトを取得
            var selectedItem = this.PeopleView.CurrentItem as PersonViewModel;
            // リクエストを投げる
            this.ShowPersonViewRequest.Raise(
                new Notification
                {
                    Title = "子画面[" + selectedItem.Name + "]",
                    Content = selectedItem
                });
        }

    }
}

TriggerActionの作成

Prismには悲しいかな汎用的なTriggerActionの実装はWPFには入ってないので、自分で作ります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Interactivity;
using System.Windows;
using Microsoft.Practices.Prism.Interactivity.InteractionRequest;

namespace SelectItemWindow
{
    public class ShowWindowAction : TriggerAction<FrameworkElement>
    {
        // 表示するWindowのタイプを指定する
        public Type WindowType
        {
            get { return (Type)GetValue(WindowTypeProperty); }
            set { SetValue(WindowTypeProperty, value); }
        }

        public static readonly DependencyProperty WindowTypeProperty =
            DependencyProperty.Register("WindowType", typeof(Type), typeof(ShowWindowAction), new UIPropertyMetadata(null));

        protected override void Invoke(object parameter)
        {
            var e = parameter as InteractionRequestedEventArgs;
            // Windowを作って表示させる
            CreateWindow(e.Context).Show();
        }

        private Window CreateWindow(Notification n)
        {
            if (this.WindowType == null)
            {
                // WindowTypeが設定されてなかったら普通にWindowを作成
                return new Window { Title = n.Title, DataContext = n.Content };
            }
            // WindowTypeが設定されてたら、そのWindowを作成
            var w = this.WindowType.GetConstructor(Type.EmptyTypes).Invoke(null) as Window;
            w.Title = n.Title;
            w.DataContext = n.Content;
            return w;
        }
    }
}

こういうのはプロジェクトでやるときは、共通部品作る人達が、もっと汎用的な形の部品作っておいてくれるようにするのがいいでしょうね。

Viewの作成

あとは、画面をさくさくと作っていくだけです。XAMLをばばっと示します。まずは、MainWindowです。

<Window x:Class="SelectItemWindow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:SelectItemWindow"
        Title="MainWindow" Height="350" Width="525" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:my="http://www.codeplex.com/prism">
    <Window.DataContext>
        <l:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <i:Interaction.Triggers>
            <!-- ViewModelから飛んできたリクエストを受け取ってWindowと表示させる -->
            <my:InteractionRequestTrigger SourceObject="{Binding Path=ShowPersonViewRequest}">
                <l:ShowWindowAction WindowType="{x:Type l:PersonView}" />
            </my:InteractionRequestTrigger>
        </i:Interaction.Triggers>
        <Button Content="子画面表示" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=ShowSelectedItemCommand}" />
        <DataGrid Margin="12,41,12,12" Name="dataGrid1" ItemsSource="{Binding Path=PeopleView}" />
    </Grid>
</Window>

次に、子画面のPersonView.xamlです。

<Window x:Class="SelectItemWindow.PersonView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:l="clr-namespace:SelectItemWindow"
        mc:Ignorable="d"
        Title="PersonView" Height="300" Width="300">
    <!-- DataContextは実行時にコードから設定されるのでデザイン時の
         DataContextを設定しておく。-->
    <d:DesignProperties.DataContext>
        <l:PersonViewModel />
    </d:DesignProperties.DataContext>
    <Grid>
        <TextBlock HorizontalAlignment="Left" Margin="12,12,0,0" Name="textBlock1" Text="名前:" VerticalAlignment="Top" />
        <TextBlock HorizontalAlignment="Left" Margin="54,12,0,0" Name="textBlock2" Text="{Binding Path=Name}" VerticalAlignment="Top" />
    </Grid>
</Window>

実行してみる

では実行してみましょう。
このようにDataGridにデータが表示されます。

DataGridの行を選んだ状態でボタンを押すと子画面が表示されます。

ソースは、以下からダウンロード出来ます。