web-dev-qa-db-fra.com

Ouverture d'une nouvelle fenêtre dans MVVM WPF

J'ai Button et j'ai lié ce bouton à la commande dans ViewModel dire OpenWindowCommand. Lorsque je clique sur le bouton, je veux ouvrir une nouvelle fenêtre. Mais créer une instance de fenêtre et afficher une fenêtre à partir d'un modèle de vue constitue une violation de MVVM. J'ai créé une interface comme

interface IWindowService
{
 void showWindow(object dataContext);
}

et WindowService implémente cette interface comme

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

Dans cette classe, j'ai spécifié ChildWindow. Donc, cette classe est étroitement associée à la présentation de ChildWindow. Quand je veux montrer une autre fenêtre, je dois implémenter une autre classe avec la même interface et la même logique. Comment puis-je rendre cette classe générique afin que je puisse passer l'instance de n'importe quelle fenêtre et que la classe puisse ouvrir n'importe quelle fenêtre? Je n'utilise pas de framework MVVM construit. J'ai lu de nombreux articles sur StackOverflow mais je n'ai trouvé aucune solution à ce problème.

35
DT sawant

Vous dites "créer une instance de fenêtre et afficher une fenêtre à partir d'un modèle de vue constitue une violation de MVVM". C'est correct.

Vous essayez maintenant de créer une interface qui prend un type de vue spécifié par la VM. C'est tout autant une violation. Vous avez peut-être résumé la logique de création derrière une interface, mais vous demandez toujours des créations de vues à partir de la machine virtuelle.

Les ordinateurs virtuels ne devraient se préoccuper que de créer des ordinateurs virtuels. Si vous avez vraiment besoin d'une nouvelle fenêtre pour héberger la nouvelle machine virtuelle, fournissez une interface comme vous l'avez déjà faite, mais sans interface. Pourquoi avez-vous besoin de la vue? La plupart des projets MVVM (VM en premier) utilisent des modèles de données implicites pour associer une vue à un VM particulier. Le VM n'en sait rien.

Comme ça:

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

Pour que cela fonctionne, vous devez évidemment vous assurer que votre machine virtuelle -> Afficher les modèles implicites est configurée dans app.xaml. Il s’agit simplement du premier MVVM standard VM.

par exemple:

<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

Une possibilité est d'avoir ceci:

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

Ensuite, vous pouvez simplement aller quelque chose comme:

windowService.showWindow<Window3>(windowThreeDataContext);

Pour plus d'informations sur la nouvelle contrainte, voir http://msdn.Microsoft.com/en-gb/library/sd2w2ew5.aspx

Remarque: la new() constraint ne fonctionne que dans les cas où la fenêtre aura un constructeur sans paramètre (mais j'imagine que cela ne devrait pas poser de problème dans ce cas!) Dans une situation plus générale, voir Créer une instance de type générique? pour les possibilités.

5
David E

utilisez un ContentPresenter dans votre fenêtre où vous liez votre DataConext à ..__et définissez un modèle de données pour votre DataContext afin que wpf puisse restituer votre DataContext. quelque chose de similaire à mon Service DialogWindow

donc tout ce dont vous avez besoin est votre seule ChildWindow avec 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

Vous pourriez écrire une fonction comme celle-ci:

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

Assurez-vous que ViewBase a une propriété DataContext. (Vous pouvez hériter de UserControl)

En général, je créerais une sorte de bus de messages et laisserais à ViewManager les messages demandant une vue. ViewModels enverrait un message demandant qu'une vue soit affichée et que les données soient affichées. Le ViewManager utiliserait alors le code ci-dessus.

Pour empêcher le ViewModel appelant de connaître les types de vue, vous pouvez transmettre une chaîne/un nom logique de la vue au ViewManager et lui demander de traduire le nom logique en un type.

3
Erno de Weerd

Je trouve la solution acceptée très utile, mais lors de l’essai pratique, j’ai trouvé qu’il lui manquait la possibilité de rendre UserControl (la vue qui résulte du dock VM -> View mapping) dans la fenêtre occuper toute la surface fournie par elle. J'ai donc étendu la solution pour inclure cette capacité:

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

Le truc ici consiste à utiliser un DockPanel pour héberger la vue convertie à partir de la VM.

Vous utilisez ensuite la méthode précédente comme suit, si vous souhaitez que la taille de la fenêtre corresponde à celle de son contenu:

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

ou comme suit si vous avez une taille fixe pour la fenêtre:

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

Peut-être que vous pourriez passer le type de fenêtre.

Essayez d'utiliser Activator.CreateInstance().

Voir la question suivante: Instancier un objet avec un type déterminé à l'exécution .

Solution par chakrit:

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

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