私の一般的な質問は、タイトルが示すように、ViewModelの構築中、または後でLoadedイベント処理を介してデータをロードするのが最善ですか?
答えは、Loadedイベント処理を介した構築後だと思いますが、それがViewModelとViewの間でどのように最もきれいに調整されているのか疑問に思います。
これが私の状況と私が解決しようとしている特定の問題についての詳細です:
Unity forDIだけでなくMVVMLightフレームワークも使用しています。ネストされたビューがいくつかあり、それぞれが対応するViewModelにバインドされています。 ViewModelは、LaurentBugnionがMVVMLightに組み込んだViewModelLocatorのアイデアを介して、各ビューのルートコントロールDataContextにバインドされます。これにより、静的リソースを介してViewModelを検索し、依存性注入フレームワーク(この場合はUnity)を介してViewModelの有効期間を制御できます。また、Expression Blendで、ViewModelとそれらをバインドする方法に関するすべてを確認できます。
とにかく、ViewModelのObservableCollectionにデータバインドされたComboBoxを持つ親ビューがあります。 ComboBoxのSelectedItemも、ViewModelのプロパティに(双方向で)バインドされます。 ComboBoxの選択が変更されると、これは他のビューおよびサブビューで更新をトリガーするためです。現在、MVVMLightにあるメッセージングシステムを介してこれを実現しています。これはすべてうまく機能しており、ComboBoxでさまざまなアイテムを選択すると期待どおりに機能します。
ただし、ViewModelは、一連の初期化メソッド呼び出しを介して、構築時にデータを取得しています。これは、ComboBoxの最初のSelectedItemが何であるかを制御したい場合にのみ問題になるようです。現在、MVVM Lightのメッセージングシステムを使用して、ViewModelのSelectedItemプロパティのセッターが更新をブロードキャストし、他の関心のあるViewModelがコンストラクターでメッセージを登録するように設定しています。現在、構築時にViewModelを介してSelectedItemを設定しようとしているようですが、サブViewModelの構築と登録はまだ許可されていません。
ViewModel内でSelectedItemのデータロードと初期設定を調整する最もクリーンな方法は何でしょうか?私は本当に、ビューのコードビハインドを合理的な範囲でできるだけ少なくすることに固執したいと思っています。 ViewModelがデータの読み込みを認識し、データの読み込みを続行してセットアップフェーズを完了する方法が必要だと思います。
ご回答ありがとうございます。
イベントの場合は、MVVM LightToolkitのEventToCommandを使用する必要があります。これを使用して、任意のui要素の任意のイベントをrelaycommandにバインドできます。 EventToCommandに関する彼の記事をチェックしてください。
サンプルをダウンロードしてご覧ください。それは素晴らしい。その場合、コードビハインドは必要ありません。例は次のとおりです。
<Page x:Class="cubic.cats.Wpf.Views.SplashScreenView"
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:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;Assembly=GalaSoft.MvvmLight.Extras"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SplashScreenPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Label Content="This is test page" />
</Grid>
</Page>
表示モードは次のようになります
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand(() =>
{
string a = "put a break point here to see that it gets called after the view as been loaded";
});
}
}
ビューモデルにEventArgsを持たせたい場合は、PassEventArgsToCommandをtrueに設定するだけです。
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
ビューモデルは次のようになります
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand<MouseEventArgs> LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand<MouseEventArgs>(e =>
{
var a = e.WhateverParameters....;
});
}
}
次のソリューションは、すでに提供され受け入れられているソリューションと似ていますが、ビューモデルのコマンドを使用してデータをロードするのではなく、「通常の方法」を使用します。コマンドはユーザーアクションに適していると思います(コマンドは実行時に使用できる場合とできない場合があります)。そのため、通常のメソッド呼び出しを使用するだけでなく、ビューで対話トリガーを設定します。
私はこれを提案します:ビューモデルクラスを作成します。 DataContext
プロパティ内に作成することにより、ビューのxaml内でビューモデルクラスをインスタンス化します。
ビューモデルにデータを読み込むメソッドを実装します。例: LoadData
。ビューが読み込まれるときにこのメソッドが呼び出されるように、ビューを設定します。これは、ビューモデルのメソッドにリンクされているビューのインタラクショントリガーによって実行されます(「Microsoft.Expression.Interactions」および「System.Windows.Interactivity」への参照が必要です)。
ビュー(xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels"
xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
>
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
これにより、ビューがロードされるときに、実行時にViewModelのLoadData
メソッドが呼び出されます。ここにデータをロードします。
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT do complex stuff here
}
public void LoadData()
{
// Make a call to the repository class here
// to set properties of your view model
}
リポジトリ内のメソッドが非同期メソッドの場合、LoadData
メソッドも非同期にすることができますが、いずれの場合もこれは必要ありません。
ちなみに、私は通常、ビューモデルのコンストラクターにデータをロードしません。上記の例では、デザイナーがビューを表示すると、ビューモデルの(パラメーターなしの)コンストラクターが呼び出されます。ここで複雑なことを行うと、ビューを表示するときにデザイナでエラーが発生する可能性があります(同じ理由で、ビューコンストラクタで複雑なことは行いません)。
一部のシナリオでは、ビューモデルコンストラクターのコードが実行時に問題を引き起こす可能性があります。ビューモデルコンストラクターの実行時に、ビューオブジェクトの作成が完全に完了していないときに、ビューの要素にバインドされているビューモデルのプロパティを設定します。
では、わかりました。 :-)
ビヘイビアーを使用して、ViewModelのメソッドにバインドできます。
これがあなたを助けるリンクです。 http://expressionblend.codeplex.com/
XAMLをビューの分離コードのLoadedイベントハンドラーに宣言的にバインドすることにしました。これにより、ビューのルート要素UserControl DataContextを介して、ViewModelオブジェクトのメソッドが呼び出されます。
それはかなり単純で、率直で、クリーンなソリューションでした。 XAMLのICommandsでできるのと同じ宣言的な方法で、LoadedイベントをViewModelオブジェクトにバインドする方法を望んでいたと思います。
私はクリンガーに公式の回答クレジットを与えたかもしれませんが、彼は私の質問にコメントを投稿しましたが、回答ではありませんでした。だから私は少なくとも彼のコメントにワンアップを与えた。
親ウィンドウと子ウィンドウの間のメッセージを処理するときに、これと同じ問題が発生しました。 ViewModelLocatorクラスでビューモデルが作成される順序を変更するだけです。メッセージを送信するビューモデルの前に、メッセージに依存するすべてのビューモデルが作成されていることを確認してください。
たとえば、ViewModelLocatorクラスのコンストラクターでは次のようになります。
public ViewModelLocator()
{
if (s_messageReceiverVm == null)
{
s_messageReceiverVm = new MessageReceiverVM();
}
if (s_messageSenderVm == null)
{
s_messageSenderVm = new MessageSenderVM();
}
}