web-dev-qa-db-ger.com

Neues Fenster in MVVM WPF öffnen

Ich habe Button und ich muss diesen Button binden, um in ViewModel den Befehl OpenWindowCommand einzugeben. Wenn ich auf die Schaltfläche klicke, möchte ich ein neues Fenster öffnen. Das Erstellen der Fensterinstanz und das Anzeigen des Fensters aus dem Ansichtsmodell ist jedoch eine Verletzung von MVVM. Ich habe Interface wie erstellt

interface IWindowService
{
 void showWindow(object dataContext);
}

und WindowService implementiert diese Schnittstelle wie

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

In dieser Klasse habe ich ChildWindow angegeben. Diese Klasse ist also eng mit ChildWindow gekoppelt. Wenn ich ein anderes Fenster anzeigen möchte, muss ich eine andere Klasse mit der gleichen Schnittstelle und Logik implementieren. Ich verwende kein MVVM-Framework. Ich habe viele Artikel zu StackOverflow gelesen, konnte aber keine Lösung dafür finden.

35
DT sawant

Sie sagen "Das Erstellen der Fensterinstanz und das Anzeigen des Fensters aus dem Ansichtsmodell ist eine Verletzung von MVVM". Das ist richtig.

Sie versuchen jetzt, eine Schnittstelle zu erstellen, die einen von der VM angegebenen Sichttyp annimmt. Dies ist ebenso ein Verstoß. Möglicherweise haben Sie die Erstellungslogik hinter einer Schnittstelle entfernt, Sie fordern jedoch weiterhin Ansichtserstellungen von der VM aus an.

VMs sollten sich nur um das Erstellen von VMs kümmern. Wenn Sie wirklich ein neues Fenster zum Hosten der neuen VM benötigen, geben Sie eine Schnittstelle an, die Sie bereits gemacht haben, die jedoch keine Ansicht zeigt. Warum brauchst du die Aussicht? Die meisten (VM zuerst) MVVM-Projekte verwenden implizite Datenvorlagen, um eine Ansicht einer bestimmten VM zuzuordnen. Die VM wissen nichts davon.

So was:

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

Natürlich müssen Sie sicherstellen, dass Ihre implizierten Vorlagen für VM-> View in app.xaml eingerichtet sind, damit dies funktioniert. Dies ist nur Standard VM der ersten MVVM.

z.B:

<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>
39
GazTheDestroyer

Eine Möglichkeit ist, dies zu haben:

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

Dann kannst du einfach so etwas machen:

windowService.showWindow<Window3>(windowThreeDataContext);

Weitere Informationen zur neuen Einschränkung finden Sie unter http://msdn.Microsoft.com/en-gb/library/sd2w2ew5.aspx .

Hinweis: new() constraint funktioniert nur, wenn das Fenster über einen parameterlosen Konstruktor verfügt (ich denke jedoch, dass dies in diesem Fall kein Problem sein sollte!). In einer allgemeineren Situation, siehe Instanz generischen Typs erstellen? nach Möglichkeiten.

5
David E

verwenden Sie einen Contentpresenter in Ihrem Fenster, in dem Sie Ihre DataConext an ..__ binden, und definieren Sie dann eine Datenvorlage für Ihren DataContext, damit Wpf Ihren DataContext darstellen kann. etwas ähnliches zu meinem DialogWindow Service

sie brauchen also nur ein einziges ChildWindow mit einem ContentPresenter:

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

</ContentPresenter>
</Window>
3
blindmeis

Sie könnten eine Funktion wie folgt schreiben:

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 
            });
    }
}

Stellen Sie sicher, dass die ViewBase über eine DataContext-Eigenschaft verfügt. (Sie könnten UserControl erben)

Im Allgemeinen würde ich eine Art Nachrichtenbus erstellen und einen ViewManager auf Nachrichten warten lassen, die nach einer Ansicht fragen. ViewModels würde eine Nachricht senden, in der eine Ansicht angezeigt und die Daten angezeigt werden. Der ViewManager würde dann den obigen Code verwenden.

Um zu verhindern, dass das aufrufende ViewModel über die View-Typen informiert wird, können Sie dem ViewManager einen String/logischen Namen der View übergeben und den ViewManager den logischen Namen in einen Typ übersetzen lassen.

3
Erno de Weerd

Ich finde die akzeptierte Lösung sehr nützlich, aber als ich sie praktisch ausprobierte, stellte ich fest, dass sie nicht in der Lage ist, das UserControl (die Ansicht, die sich aus der VM -> View-Zuordnung ergibt) im Host-Fenster andocken zu lassen das gesamte von ihm zur Verfügung gestellte Gebiet einnehmen. Also habe ich die Lösung um diese Fähigkeit erweitert:

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;
}

Der Trick besteht darin, ein DockPanel zu verwenden, um die von der VM konvertierte Ansicht zu hosten.

Dann verwenden Sie die vorherige Methode wie folgt, wenn die Größe des Fensters der Größe des Inhalts entsprechen soll:

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

oder wie folgt, wenn Sie eine feste Größe für das Fenster haben:

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

Vielleicht könnten Sie den Fenstertyp übergeben.

Versuchen Sie es mit Activator.CreateInstance().

Siehe folgende Frage: Instanziert ein Objekt mit einem zur Laufzeit bestimmten Typ .

Lösung von chakrit:

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

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