以下で、ユーザーコントロールのViewModelへどのようにアクセスすれば良いのかということを考えていました。
最近MVVMが面白い
http://blogs.wankuma.com/unyora/archive/2009/06/10/174828.aspx
考え方がある程度まとまったので、整理の意味も込めて以下に書いてみます。
MVVMのパターンを見ていると、ViewとViewModelをXamlだけで結び付けているケースがよくあります。これはこれでWPFの優れた仕組みだと思うのですが、実際にアプリケーションを組む場合、これだと疎すぎて扱いにくい場合が多々あります。
例えば伝票画面に伝票ユーザーコントロールを配置した場合を考えてみます。
伝票画面がPaymentSlipWindow、伝票ユーザーコントロールがPaymentSlipです。
それぞれのViewModelをPaymentSlipWindowViewModel、PaymentSlipViewModelとします。
以下に、PaymentSlipのDataContextに、XamlによってPaymentSlipViewModelをセットしている例を示します。
▼PaymentSlip
<UserControl x:Class="WPFPaymentSlipTest.View.PaymentSlip"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" Background="AliceBlue"
xmlns:local="clr-namespace:WPFPaymentSlipTest.ViewModel"
xmlns:local2="clr-namespace:WPFPaymentSlipTest.View"
DataContext="{DynamicResource PaymentSlipViewModelKey}">
・
・
・
▼リソース
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WPFPaymentSlipTest.ViewModel"
xmlns:view="clr-namespace:WPFPaymentSlipTest.View"
>
<viewModel:PaymentSlipViewModel x:Key="PaymentSlipViewModelKey" />
</ResourceDictionary>
▼PaymentSlipWindow
<UserControl x:Class="WPFPaymentSlipTest.View.PaymentSlipWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFPaymentSlipTest.View"
xmlns:model="clr-namespace:WPFPaymentSlipTest.ViewModel"
Height="500" Width="376">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="300" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:PaymentSlip Grid.Row="0" x:Name="PaymentSlip" />
<Button Grid.Row="1" Height="23" Margin="133,12,0,0" Name="bttn_update"
VerticalAlignment="Top" HorizontalAlignment="Left" Width="72"
Command="{Binding Path=UpdCommand}">更新</Button>
</Grid>
</UserControl>
この状態で、更新ボタンを押してUpdCommandを呼びます。UpdCommandでは、PaymentSlipViewModelが持っているUpdateメソッドを呼んで、伝票データを更新したいのです。
ところが、UpdCommandが定義してあるPaymentSlipWindowViewModelからPaymentSlipViewModelのインスタンスを取得することができませんので、Updateメソッドを呼ぶことができません。なぜなら、PaymentSlipとPaymentSlipViewModelはPaymentSlipWindowのXamlで結びつけられており、PaymentSlipWindowViewModelはそれを一切知ることができないからです。
この場合、PaymentSlipWindowのLoadedイベントでPaymentSlipWindowViewModelに自分のインスタンスを渡すという解決策も考えられます。このようにすれば、PaymentSlipWindowViewModelからPaymentSlipWindowのインスタンスが得られ、そこからPaymentSlipのDataContext経由でPaymentSlipViewModelのインスタンスを得ることができるからです。
しかし、この場合はPaymentSlipのDataContextに、PaymentSlipWindowViewModelからコードでPaymentSlipViewModelをセットしてあげるとスムーズに行きます。このようにしておくと、PaymentSlipWindowViewModelはPaymentSlipViewModeを把握していますので、簡単にPaymentSlipViewModelのUpdateメソッドを呼ぶことができます。
▼PaymentSlipWindow
<UserControl x:Class="WPFPaymentSlipTest.View.PaymentSlipWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFPaymentSlipTest.View"
xmlns:model="clr-namespace:WPFPaymentSlipTest.ViewModel"
Height="500" Width="376">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="300" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:PaymentSlip Grid.Row="0" x:Name="PaymentSlip" DataContext="{Binding Path=PaymentSlips}" />
<Button Grid.Row="1" Height="23" Margin="133,12,0,0" Name="bttn_update"
VerticalAlignment="Top" HorizontalAlignment="Left" Width="72"
Command="{Binding Path=UpdCommand}">更新</Button>
</Grid>
</UserControl>
▼PaymentSlipWindowViewModel
class PaymentSlipWindowViewModel : ViewModelBase
{
private PaymentSlipViewModel _paymentSlips;
public PaymentSlipViewModel PaymentSlips
{
get
{
if (_paymentSlips == null)
_paymentSlips = new PaymentSlipViewModel();
return _paymentSlips;
}
}
・
・
・
以上をまとめると、MVVMパターンでViewとViewModelをどう結び付けるかは自由ですが、XAMLだけで結び付けてしまうと疎結合が強すぎて、コードから扱いにくくなるということです。もちろん、DataTemplateによるViewとViewModelの結び付けなど、XAMLだけで結び付けると大変便利な場合もありますが、これがMVVMパターンの十分条件では無いということです。
そのことにようやく気がつきました。