web-dev-qa-db-ja.com

MVVM WPFで新しいウィンドウを開く

Buttonがあり、このボタンをViewModelのコマンドにバインドします(OpenWindowCommandなど)。ボタンをクリックすると、新しいウィンドウが開きます。ただし、ウィンドウインスタンスを作成し、ビューモデルからウィンドウを表示することはMVVMに違反します。のようなインターフェイスを作成しました

interface IWindowService
{
 void showWindow(object dataContext);
}

windowServiceはこのインターフェースを次のように実装します

class WindowService:IWindowService
{
 public void showWindow(object dataContext)
 {
  ChildWindow window=new ChildWindow();
  window.DataContext=dataContext;
  window.Show();
  }
}

このクラスでは、ChildWindowを指定しました。したがって、このクラスは、ChildWindowの表示と密接に結びついています。別のウィンドウを表示したいときは、同じインターフェイスとロジックを持つ別のクラスを実装する必要があります。どのようにこのクラスをジェネリックにして、任意のウィンドウのインスタンスだけを渡し、クラスが任意のウィンドウを開くことができますか?構築されたMVVMフレームワークを使用していません。StackOverflowに関する多くの記事を読みましたが、これに対する解決策を見つけることができませんでした。

37
DT sawant

「ウィンドウインスタンスを作成し、ビューモデルからウィンドウを表示することはMVVMに違反しています」と言います。これは正しいです。

現在、VMによって指定されたタイプのビューを取るインターフェースを作成しようとしています。これは、同じくらい違反です。インターフェイスの背後にある作成ロジックを抽象化したかもしれませんが、VM内からビューの作成を要求しています。

VMはVMの作成のみを考慮する必要があります。新しいVMをホストするために新しいウィンドウが本当に必要な場合は、これまでと同じようにインターフェイスを提供しますが、ビューは表示されません。なぜビューが必要なのですか?ほとんどの(VMが最初の)MVVMプロジェクトは、暗黙的なデータテンプレートを使用して、ビューを特定のVMに関連付けます。 VMはそれらについて何も知りません。

このような:

class WindowService:IWindowService
{
    public void ShowWindow(object viewModel)
    {
        var win = new Window();
        win.Content = viewModel;
        win.Show();
    }
}

明らかに、これが機能するためには、app.xamlでVM-> Viewの暗黙的なテンプレートを設定しておく必要があります。これは単なる標準VM最初のMVVMです。

例えば:

<Application x:Class="My.App"
             xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:My.App.ViewModels"
             xmlns:vw="clr-namespace:My.App.Views"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <DataTemplate DataType="{x:Type vm:MyVM}">
            <vw:MyView/>
        </DataTemplate>

    </Application.Resources>
</Application>
43
GazTheDestroyer

1つの可能性はこれを持つことです:

_class WindowService:IWindowService
{
 public void showWindow<T>(object DataContext) where T: Window, new() 
 {
  ChildWindow window=new T();
  window.Datacontext=DataContext;
  window.show();
 }
}
_

次に、次のように移動します。

_windowService.showWindow<Window3>(windowThreeDataContext);
_

新しい制約の詳細については、 http://msdn.Microsoft.com/en-gb/library/sd2w2ew5.aspx を参照してください

注:new() constraintは、ウィンドウにパラメーターなしのコンストラクターがある場合にのみ機能します(ただし、この場合、これは問題にならないはずです!)より一般的な状況では、 Create instance ofジェネリック型? 可能性。

6
David E

dataConextをバインドするWindowでcontentpresenterを使用します。次に、wpfがDataContextをレンダリングできるように、DataContextのDatatemplateを定義します。私と同様の何か DialogWindow Service

必要なのは、ContentPresenterを持つ1つのChildWindowだけです:

<Window x:Class="ChildWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation" 
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter Content="{Binding .}">

</ContentPresenter>
</Window>
3
blindmeis

次のような関数を書くことができます。

class ViewManager
{
    void ShowView<T>(ViewModelBase viewModel)
        where T : ViewBase, new()
    {
        T view = new T();
        view.DataContext = viewModel;
        view.Show(); // or something similar
    }
}

abstract class ViewModelBase
{
    public void ShowView(string viewName, object viewModel)
    {
        MessageBus.Post(
            new Message 
            {
                Action = "ShowView",
                ViewName = viewName,
                ViewModel = viewModel 
            });
    }
}

ViewBaseにDataContextプロパティがあることを確認してください。 (UserControlを継承できます)

一般に、私はある種のメッセージバスを作成し、ViewManagerにビューを要求するメッセージをリッスンさせます。 ViewModelsは、表示するビューと表示するデータを要求するメッセージを送信します。 ViewManagerは上記のコードを使用します。

呼び出し側のViewModelがビュータイプを認識しないようにするには、ビューの文字列/論理名をViewManagerに渡し、ViewManagerに論理名をタイプに変換させます。

3
Erno de Weerd

受け入れられたソリューションは非常に便利ですが、実際に試してみると、UserControl(VM-> View mapping)から生じるビュー)をドッキングする機能がないことがわかりました。ホスティングウィンドウが提供する領域全体を占有するため、この機能を含むようにソリューションを拡張しました。

public Window CreateWindowHostingViewModel(object viewModel, bool sizeToContent)
{
   ContentControl contentUI = new ContentControl();
   contentUI.Content = viewModel;
   DockPanel dockPanel = new DockPanel();
   dockPanel.Children.Add(contentUI);
   Window hostWindow = new Window();
   hostWindow.Content = dockPanel;

   if (sizeToContent)
       hostWindow.SizeToContent = SizeToContent.WidthAndHeight;

   return hostWindow;
}

ここでの秘Theは、DockPanelを使用してVMから変換されたビューをホストすることです。

次に、ウィンドウのサイズをコンテンツのサイズと一致させる場合は、次のように前の方法を使用します。

var win = CreateWindowHostingViewModel(true, viewModel)
win.Title = "Window Title";
win.Show();

または、ウィンドウのサイズが固定されている場合は次のようになります。

var win = CreateWindowHostingViewModel(false, viewModel)
win.Title = "Window Title";
win.Width = 500;
win.Height = 300;
win.Show();
1
Ghareeb Falazi

ウィンドウタイプを渡すことができるかもしれません。

Activator.CreateInstance()を使用してみてください。

次の質問を参照してください。 実行時決定型のオブジェクトのインスタンス化

Chakritによる解決策:

// determine type here
var type = typeof(MyClass);

// create an object of the type
var obj = (MyClass)Activator.CreateInstance(type);
0
Hellin