質問: WPF でアプリ作ってると、この処理 view でやるのか viewmodel でやるのかどっちでやるのか迷うことがあります。 特に、別ウィンドウを開く、メッセージダイアログ、ファイルダイアログ、印刷、などの Windows 的なところに触れるもの。 データがほぼ viewmodel にあるので出来る限り viewmodel でやりたいのですが、ときどきコントロールのプロパティが get のみで Binding もできないようになっていて、 viewmodel にデータを持ってくるのが難しいことがあります。 それを取得するために view を viewmodel で持ったり、 view が viewmodel のプロパティにラムダ式代入したりすることになります。 逆にそれらを view でやろうとすると、こちらも引数に渡したいデータは viewmodel にしかなく、 DataContext から直接プロパティ見たりメソッド呼んで取得することになります。 どう書いても綺麗にいかないなー と思ってるのですが、楽にするためにこういう機能ある、とか こうすればいいとかありましたら教えてほしいです。
回答: ViewとかViewModelとか呼んでいるからには、MVVMを前提としていると解釈してよろしいですか? であれば、ViewとViewModelは疎結合にすべきであって、ViewがViewModelの型を知っていたり、ViewModelがViewを直接参照したりは極力避けるべきであるというのが僕の考えです。 ViewModelがダイアログや別のウィンドウを生成するというのも個人的には好きじゃないです。 Viewのコードビハインド(xaml.cs)を書きたがらない人もいるでしょうが、Viewを作るのはViewが担当するのが一番自然だと思います。 さて、実現方法ですが、ViewModelからViewへの必要な情報の受け渡し自体は、 View側に依存関係プロパティを作ってやって、そこにバインドすることで解決すると思います。 ViewModelを処理の起点としてViewを操作したいときも、同様にプロパティの値変更をトリガとする方法があります。 例として、ViewModelのプロパティ変更をトリガとして別ウィンドウを表示する場合、以下のようになります。 ``` public partial class MainWindow : Window { //子ウィンドウのDataContextプロパティ //(型をViewが知る必要はないのでobject) public object AWindowContext { get { return (object)GetValue(AWindowContextProperty); } set { SetValue(AWindowContextProperty, value); } } public static readonly DependencyProperty AWindowContextProperty = DependencyProperty.Register("AWindowContext", typeof(object), typeof(MainWindow), new PropertyMetadata(null, OnAWindowContextChanged)); private static void OnAWindowContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var me = d as MainWindow; //プロパティに新しいオブジェクトが設定されたのをトリガに //そのオブジェクトをDataContextとして子ウィンドウを表示する if (me.AWindowContext != null) { var wnd = new AWindow(); wnd.Owner = me; wnd.DataContext = me.AWindowContext; wnd.ShowDialog(); } } //コンストラクタ public MainWindow() { InitializeComponent(); //Binding設定 SetBinding(AWindowContextProperty, "AContext"); } } ``` ``` public class MainWindowViewModel : INotifyPropertyChanged { private AWindowContext _aContext; //ウィンドウを表示したいときはここにオブジェクトをセットする public AWindowContext AContext { get { return _aContext; } private set { if (ReferenceEquals(_aContext, value)) return; _aContext = value; //更新通知 var h = PropertyChanged; if (h != null) h(this, new PropertyChangedEventArgs("AContext")); } } public event PropertyChangedEventHandler PropertyChanged; } ``` 何かMVVM支援のフレームワークを使われているのであれば、添付ビヘイビアの仕組みを使うともう少し楽に書けると思います。 別の方法としては(これもMVVMフレームワーク使用が前提になると思いますが)メッセンジャーパターンというのがあります。 これは、ViewModelからViewへの依頼内容をMessegeオブジェクトにパッケージングして、Messengerクラスに通達を依頼する実装パターンです。 これについては↓このあたりがわかりやすいかと思います。 http://tnakamura.hatenablog.com/entry/20110218/mvvm_messenger