16

私は、MV-MC (Model-View-ModelController) と呼んでいる MV-VM のフレーバーを使用して、非常に大きな LOB アプリを開発してきました。これは、MVC と MV-VM の一種の組み合わせです。MV-VM でビューがどのようにインスタンス化されるかに関するこの回答を、「 wpf 開発で最も一般的な間違いは何ですか」という質問に投稿しました。

サムは私の答えに関して次のコメントをしました:

これにより、フォローアップの質問が作成されます。ビューをどのように作成しますか? RelayCommands を使用してアクションをビューから ViewModel にバインドするため、ビューはアクションが発生したことさえ認識せず、新しいビューを開く必要があることも認識しません。解決策: ビューがサブスクライブするためのイベントを VM に作成しますか?

私が最初に MV-VM の開発を始めたとき、私はすべてが ViewModel に存在するべきであるという考えを持っており、Josh SmithKarl Shifflettのような人々から多くの例を研究しました。ただし、コマンドが ViewModel に存在する必要がある場合の良い例をまだ見つけていません。

たとえば、顧客を表示する ListView と、現在選択されている顧客を編集するためにクリックするボタンがあるとします。ListView (View) は CustomerVM (ViewModel) にバインドされます。ボタンをクリックすると、EditCustomerCommand が起動され、CustomerVM のすべてのプロパティを編集できるポップアップ ウィンドウが開きます。この EditCustomerCommand はどこにありますか? ウィンドウを開く必要がある場合 (UI 機能)、ビューのコード ビハインドで定義すべきではありませんか? 代替テキスト

ビューとビューモデルでコマンドを定義する必要がある場合の例はありますか?

マシュー・ライトは次のように述べています。

リストからの新規作成と削除が良い例です。そのような場合、空のレコードが追加されるか、ViewModel によって現在のレコードが削除されます。ビューによって実行されるアクションは、発生したイベントに応答する必要があります。

新しいボタンをクリックするとどうなりますか? CustomerVM の新しいインスタンスは、親 ViewModel によって作成され、そのコレクションに追加されますか? では、どのようにして私の編集画面が開かれるのでしょうか? ビューは Customer ViewModel の新しいインスタンスを作成し、それを ParentVM.Add(newlyCreatedVM) メソッドに渡す必要がありますか?

VM 上にある DeleteCommand を使用して顧客レコードを削除するとします。VM はビジネス レイヤーを呼び出し、レコードの削除を試みます。できないので、VM にメッセージを返します。このメッセージをダイアログボックスに表示したい。ビューはどのようにしてコマンド アクションからメッセージを取得しますか?

4

4 に答える 4

3

質問で自分が引用されるとは思いもしませんでした。

私はしばらくの間、この質問について自分自身で考え、コードベースについてかなり現実的な決定を下しました。

私のコード ベースでは、アクションが発生すると ViewModel が呼び出されます。このままにしておきたいと思いました。さらに、ViewModel でビューを制御したくありません。

私は何をしましたか?
ナビゲーション用のコントローラーを追加しました:

public interface INavigation
{
  void NewContent(ViewModel viewmodel);
  void NewWindow(ViewModel viewmodel);
}

このコントローラーには 2 つのアクションが含まれています。NewContent() は現在のウィンドウに新しいコンテンツを表示し、NewWindow() は新しいウィンドウを作成し、コンテンツを入力して表示します。
もちろん、私のビューモデルには、どのビューを表示するかわかりません。しかし、彼らは表示したいビューモデルを知っているので、あなたの例によれば、DeleteCommand が実行されると、ナビゲーション サービス関数NewWindow(new ValidateCustomerDeletedViewModel())が呼び出され、 「顧客が削除されました」というウィンドウが表示されます (やり過ぎです)。この単純なメッセージボックスですが、単純なメッセージボックス用の特別なナビゲーター機能を持つのは簡単でしょう)。

ビューモデルはどのようにナビゲーション サービスを取得しますか?

ビューモデル クラスには、ナビゲーション コントローラーのプロパティがあります。

public class ViewModel
{
  public INavigation Navigator { get; set; }
  [...]
}

ビューモデルがウィンドウ (またはビューを表示するもの) にアタッチされると、ウィンドウは Navigator プロパティを設定するため、ビューモデルはそれを呼び出すことができます。

ナビゲーターはビューモデルへのビューをどのように作成しますか?

どのビューモデルに対してどのビューを作成するかの簡単なリストを作成できます。私の場合、名前が一致しているため、単純なリフレクションを使用できます。

public static FrameworkElement CreateView(ViewModel viewmodel)
{
  Type vmt = viewmodel.GetType();
  // big bad dirty hack to get the name of the view, but it works *cough*
  Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, ")); 
  return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}

もちろん、ビューにはビューモデルをパラメーターとして受け入れるコンストラクターが必要です。

public partial class ValidateCustomerDeletedView : UserControl
{
  public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
  {
    InitializeComponent();
    this.DataContext = dac;
  }
}

私の窓はどのように見えますか?

シンプル: 私のメイン ウィンドウは INavigation インターフェイスを実装しており、作成時に開始ページを表示します。自分で見て:

public partial class MainWindow : Window, INavigation
{
  public MainWindow()
  {
    InitializeComponent();
    NewContent(new StartPageViewModel());
  }

  public MainWindow(ViewModel newcontrol)
  {
    InitializeComponent();
    NewContent(newcontrol);
  }

  #region INavigation Member
  public void NewContent(ViewModel newviewmodel)
  {
    newviewmodel.Navigator = this;
    FrameworkElement ui = App.CreateView(newviewmodel);
    this.Content = ui;
    this.DataContext = ui.DataContext;
  }

  public void NewWindow(ViewModel viewModel)
  {
    MainWindow newwindow = new MainWindow(viewModel);
    newwindow.Show();
  }
  #endregion
}

(これは、NavigationWindow でも同じように機能し、ビューをページにラップします)

もちろん、ナビゲーション コントローラーは簡単にモックできるため、これはテスト可能です。

これが完璧な解決策かどうかはわかりませんが、今のところうまく機能しています。アイデアやコメントは大歓迎です!

于 2009-01-16T11:47:47.687 に答える
1

メッセージ ボックスの削除の場合、インターフェイスを介してメッセージ ボックスを抽象化します。これに似ています。また、これらのインターフェイスを WPF アプリに挿入します。

コンストラクタ

    public MyViewModel(IMessage msg)
    {
      _msg = msg;
    }

次に、ViewModelのメソッドdeleteメソッドで...次のようなもの

    public void Delete()
    {
      if(CanDelete)
      {
        //do the delete 
      }
      else
      {
        _msg.Show("You can't delete this record");
      }
    }

これにより、テスト可能になります。実際にはメッセージボックスを表示しない別の IMessage 実装をプラグインできます。これらは、テスト目的でコンソールに出力されるだけかもしれません。明らかに、WPF アプリには次のような実装がある場合があります

public class MessageBoxQuestion : IMessage
{
   public void Show(string message)
   {
     MessageBox.Show(message);
   }
}

これを行うと、さまざまなルート (はい/いいえダイアログを考えてください) のテストが非常に簡単かつ簡単になります。削除の確認を想像できます。IMessage の具体的なインスタンスを使用して確認のために true/false を返すか、テスト中にコンテナーをモックアウトすることができます。

[Test]
public void Can_Cancel_Delete()
{
  var vm = new ProductViewModel(_cancel);
  ...

}
[Test]
public void Can_Confirm_Delete()
{
  var vm = new ProductViewModel(_yes);
  ...

}

コマンドをいつ使用するかについての他の質問については、問題のビューから新規追加ビューまたは詳細ビューをインスタンス化します。あなたの例と同じように。 ビューは、アプリ内の他のビューによってのみインスタンス化されます。そのような場合、私はコマンドを使用しません。ただし、親ビューの ViewModel プロパティを子ビューに使用します。

public void Object_DoubleClick(object sender, EventArgs e)
{
  var detailView = new DetailView(ViewModel.Product);
  detailView.Show();
}

お役に立てれば!

于 2009-01-09T20:03:20.377 に答える
0

リストからの新規作成と削除が良い例です。そのような場合、空のレコードが追加されるか、ViewModel によって現在のレコードが削除されます。ビューによって実行されるアクションは、発生したイベントに応答する必要があります。

于 2009-01-08T17:00:09.990 に答える
0

1 つの方法は、ビジネス レイヤーが変更でき、コマンドの実行後に VM が処理できるコマンド パラメーター オブジェクトを使用することです。

于 2009-01-09T14:15:07.880 に答える