web-dev-qa-db-ja.com

MVVMを使用してWPFで新しいウィンドウを作成する最良の方法

隣の投稿で: ViewModelはどのようにフォームを閉じるべきですか? MVVMを使用してウィンドウを閉じる方法をビジョンに投稿しました。そして今、私は質問があります:それらを開く方法。

メインウィンドウ(メインビュー)があります。ユーザーが「表示」ボタンをクリックすると、「デモ」ウィンドウ(モーダルダイアログ)が表示されます。 MVVMパターンを使用してウィンドウを作成して開くための好ましい方法は何ですか? 2つの一般的なアプローチがあります。

最初の(おそらく最も単純な)。イベントハンドラ「ShowButton_Click」は、メインウィンドウの背後のコードに次のように実装する必要があります。

        private void ModifyButton_Click(object sender, RoutedEventArgs e)
        {
            ShowWindow wnd = new ShowWindow(anyKindOfData);
            bool? res = wnd.ShowDialog();
            if (res != null && res.Value)
            {
                //  ... store changes if neecssary
            }
        }
  1. 「表示」ボタンの状態を変更する必要がある場合(有効/無効)、ボタンの状態を管理するロジックを追加する必要があります。
  2. ソースコードは、「古いスタイルの」WinFormsおよびMFCソースに非常によく似ています。これが良いか悪いかはわかりません。アドバイスしてください。
  3. 私が逃した他の何か?

別のアプローチ:

MainWindowViewModelでは、コマンドのICommandインターフェイスを返す「ShowCommand」プロパティを実装します。コママン:

  • 「ShowDialogEvent」を発生させます。
  • ボタンの状態を管理します。

このアプローチはMVVMにより適していますが、追加のコーディングが必要になります。ViewModelクラスは「ダイアログを表示」できないため、MainWindowViewModelはMainWindow_Loadedメソッドにイベントハンドラーを追加する必要がある「ShowDialogEvent」、MainWindowView :

((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;

(ShowDialog-'ModifyButton_Click'メソッドに似ています。)

私の質問は次のとおりです。1.他のアプローチはありますか? 2.リストされているものの1つは良いと思うか、悪いと思うか。 (なぜ?)

他の考えは大歓迎です。

ありがとう。

55
Budda

私も最近この問題について考えていました。ここでは、プロジェクトで nity を「コンテナ」または依存性注入用として使用した場合のアイデアを示します。通常、App.OnStartup()をオーバーライドして、モデルを作成し、モデルを表示し、そこに表示して、それぞれに適切な参照を与えると思います。 Unityを使用して、コンテナーにモデルへの参照を与え、コンテナーを使用してビューを「解決」します。 Unityコンテナーはビューモデルを挿入するため、直接インスタンス化することはありません。ビューが解決したら、Show()を呼び出します。

私が見たビデオの例では、UnityコンテナはOnStartupのローカル変数として作成されました。 Appクラスで静的な読み取り専用パブリックプロパティとして作成した場合はどうなりますか?次に、それをメインビューモデルで使用して新しいウィンドウを作成し、新しいビューに必要なリソースを自動的に注入できます。 App.Container.Resolve<MyChildView>().ShowDialog();のようなもの。

テストでUnityコンテナへの呼び出しの結果を何らかの方法でモックできると思います。または、AppクラスでShowMyChildView()のようなメソッドを作成することもできます。これは基本的に上記で説明したことを行うだけです。 _bool?_を返すだけなので、App.ShowMyChildView()への呼び出しをモックするのは簡単かもしれません。

まあ、それは実際にnew MyChildView()を使用するよりも良いことではないかもしれませんが、それは私が持っていた小さなアイデアです。私はそれを共有すると思った。 =)

16
Benny Jobigan

一部のMVVMフレームワーク(たとえば MVVM Light )は Mediatorパターン を使用します。したがって、新しいウィンドウを開く(またはビューを作成する)には、いくつかのビュー固有のコードがメディエーターからのメッセージをサブスクライブし、ViewModelがそれらのメッセージを送信します。

このような:

サブスクリプション

Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
     // Instantiate new view depending on the message details
}

ViewModelで

Messenger.Default.Send(new DialogMessage(...));

私は、アプリケーションのUI部分が「存続」する限り、シングルトンクラスでサブスクリプションを行うことを好みます。要約すると、ViewModelは、「ビューを作成する必要があります」などのメッセージを渡し、UIはそれらのメッセージをリッスンし、それらに基づいて動作します。

ただし、「理想的な」アプローチはありません。

17
arconaut

私は少し遅れていますが、既存の答えが不十分だと思います。理由を説明します。一般に:

  • viewからViewModelsにアクセスしても問題ありません。
  • ViewModelsからViewsにアクセスするのは間違っています。循環依存関係を導入し、ViewModelsのテストを困難にするためです。

ベニー・ジョビガンの答え:

_App.Container.Resolve<MyChildView>().ShowDialog();
_

これは実際には何も解決しません。密結合された方法でViewModelからビューにアクセスしています。 new MyChildView().ShowDialog()との唯一の違いは、間接参照の層を通過したことです。 MyChildView ctorを直接呼び出すことに勝るものはありません。

ビューにインターフェースを使用すると、よりきれいになります。

_App.Container.Resolve<IMyChildView>().ShowDialog();`
_

これで、ViewModelはビューに厳密に結合されません。ただし、各ビューのインターフェイスを作成することは非常に実用的ではありません。

アルコナウトの答え:

_Messenger.Default.Send(new DialogMessage(...));
_

より良いです。 Messenger、EventAggregator、または別のpub/subパターンがMVVMのすべてに共通の解決策のように思えます:)欠点は、DialogMessageHandlerにデバッグしたり、ナビゲートするのが難しいことです。間接的すぎます。たとえば、ダイアログからの出力をどのように読みますか? DialogMessageを変更しますか?

私のソリューション:

次のようにMainWindowViewModelからウィンドウを開くことができます。

_var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
   //you can read user input from childWindowViewModel
}
_

DialogServiceはダイアログのViewModelのみを取得するため、ビューモデルはビューから完全に独立しています。実行時に、DialogServiceは適切なビューを見つけて(たとえば命名規則を使用して)表示するか、単体テストで簡単にモックできます。

私の場合、このインターフェイスを使用します。

_interface IDialogService
{
   void Show(IDialogViewModel dialog);
   void Close(IDialogViewModel dialog); 
   bool? ShowModal(IDialogViewModel dialog);
   MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}

interface IDialogViewModel 
{
    string Caption {get;}
    IEnumerable<DialogButton> Buttons {get;}
}
_

dialogBu​​ttonは、DialogResultまたはICommand、あるいはその両方を指定します。

5
Liero

Silverlightでモーダルダイアログを表示するための現在のMVVMソリューションをご覧ください。あなたが言及した問題のほとんどを解決しますが、プラットフォーム固有のものから完全に抽象化されており、再利用できます。また、ICommandを実装するDelegateCommandsとのコードビハインドのみのバインディングも使用しませんでした。ダイアログは基本的にビューです-独自のViewModelを持つ独立したコントロールであり、メイン画面のViewModelから表示されますが、DelagateCommandバインディングを介してUIからトリガーされます。

ここで完全なSilverlight 4ソリューションを参照してください MVVMおよびSilverlight 4のモーダルダイアログ

2
Roboblob

ビュー間で渡されるすべての情報を処理するコントローラーを使用します。すべてのビューモデルは、コントローラーのメソッドを使用して、ダイアログ、他のビューなどとして実装できる詳細情報を要求します。

次のようになります。

class MainViewModel {
    public MainViewModel(IView view, IModel model, IController controller) {
       mModel = model;
       mController = controller;
       mView = view;
       view.DataContext = this;
    }

    public ICommand ShowCommand = new DelegateCommand(o=> {
                  mResult = controller.GetSomeData(mSomeData);
                                                      });
}

class Controller : IController {
    public void OpenMainView() {
        IView view = new MainView();
        new MainViewModel(view, somemodel, this);
    }

    public int GetSomeData(object anyKindOfData) {
      ShowWindow wnd = new ShowWindow(anyKindOfData);
      bool? res = wnd.ShowDialog();
      ...
    }
}
1
adrianm

私のアプローチはアドリアンのアプローチに似ています。ただし、私の場合、Controllerは具象ビュータイプでは機能しません。 Controllerは、ViewModelと同様に、Viewから完全に分離されています。

この仕組みは WPF Application Framework(WAF) のViewModelの例で確認できます。

宜しくお願いします、

jbe

0
jbe