2014-09-15 5 views
28

Ho Button e ho associato questo pulsante al comando in ViewModel, ad esempio OpenWindowCommand. Quando clicco sul pulsante voglio aprire una nuova finestra. Ma la creazione di un'istanza di finestra e la visualizzazione di una finestra dal modello di visualizzazione sono una violazione di MVVM. Ho creato l'interfaccia comeApertura di una nuova finestra in MVVM WPF

interface IWindowService 
{ 
void showWindow(object dataContext); 
} 

e WindowService implementa questa interfaccia come

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

In questa classe ho specificato childWindow. Quindi questa classe è strettamente accoppiata a mostrare ChildWindow. Quando voglio mostrare un'altra finestra, devo implementare un'altra classe con la stessa interfaccia e logica. Come posso rendere questa classe generica in modo che possa passare solo l'istanza di qualsiasi finestra e la classe sarà in grado di aprire qualsiasi finestra? Non sto usando alcun framework MVVM. Ho letto molti articoli su StackOverflow ma non ho trovato alcuna soluzione per questo.

+1

Ho trovato un [modo alternativo] (http://stackoverflow.com/a/15512972/385995) di finestre di apertura in MVVM, utilizzando un comportamento anziché un servizio. –

risposta

29

Tu dici "la creazione di un'istanza della finestra e mostrando finestra dalla vista del modello è violazione della MVVM". Questo è corretto.

Si sta tentando di creare un'interfaccia che accetta un tipo di visualizzazione specificato dalla VM. Questa è solo una violazione. Potresti aver sottratto la logica di creazione dietro un'interfaccia, ma stai ancora richiedendo creazioni di visualizzazione all'interno della VM.

Le VM devono preoccuparsi solo della creazione di macchine virtuali. Se hai davvero bisogno di una nuova finestra per ospitare la nuova VM, quindi fornire un'interfaccia come hai fatto, ma uno che non ha una vista. Perché hai bisogno della vista? La maggior parte dei progetti MVVM (prima VM) utilizzano datatemplates impliciti per associare una vista a una particolare VM. La VM non sa nulla di loro.

Ti piace questa:

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

Ovviamente è necessario assicurarsi di avere i tuoi VM-> Visualizza i modelli impliciti impostati in app.xaml per far funzionare tutto questo. Questo è solo MV VMM standard prima.

esempio:

<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> 
+0

Come posso aprire una finestra senza passare la finestra per funzionare? –

+1

Perché hai bisogno di diversi tipi di finestre? La finestra è solo un contenitore per la vista. Basta usare una finestra generica e usare DataTemplates impliciti come normali per mappare VM-> Visualizza – GazTheDestroyer

+0

Vuoi dire che dovrei usare solo una finestra e non devo aprire nuove finestre? –

0

Forse potresti passare il tipo di finestra.

Provare a utilizzare Activator.CreateInstance().

Vedere la seguente domanda: Instantiate an object with a runtime-determined type.

Solution di chakrit:

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

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

Si potrebbe scrivere una funzione come questa:

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

Assicurarsi che il ViewBase ha una proprietà DataContext. (È possibile ereditare UserControl)

In generale, creo un tipo di bus messaggi e un ViewManager ascolta i messaggi che richiedono una visualizzazione. ViewModels inviava un messaggio per chiedere di mostrare una vista e mostrare i dati. Il ViewManager userebbe quindi il codice sopra.

Per impedire a ViewModel che chiama di conoscere i tipi di vista, è possibile passare una stringa/nome logico della vista al ViewManager e fare in modo che ViewManager traduca il nome logico in un tipo.

+0

Potrei sbagliarmi, ma sono abbastanza sicuro che tu abbia bisogno del 'dove T: ViewBase, new()' per creare un nuovo oggetto di tipo generico nella tua funzione? a la: http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx –

+0

@DavidEdey - sì sì. Non c'è bisogno di copiare la mia risposta ... –

+0

E 'stato un caso di risposta simultanea - il tuo non c'era quando ho scritto il mio, poi mi sono rinfrescato per scoprire che mi avevi battuto su di esso! Scuse Erno :) –

5

Una possibilità è quella di avere questo:

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

Poi si può solo andare qualcosa di simile:

windowService.showWindow<Window3>(windowThreeDataContext); 

Per ulteriori informazioni sul nuovo vincolo, vedere http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx

Nota: il new() constraint funziona solo dove la finestra avrà un costruttore senza parametri (ma immagino che questo non dovrebbe essere un problema in questo caso!) In un altro gener al situazione, vedere Create instance of generic type? per le possibilità.

+4

windowService.showWindow (windowThreeDataContext); questa affermazione è in viewmodel e contiene il nome della vista. Non è viola l'approccio MVVM? –

+0

Infatti, scusate, stavo prendendo la domanda 'Come posso rendere questa classe generica in modo che possa passare solo l'istanza di una finestra qualsiasi e la classe sarà in grado di aprire qualsiasi finestra?' Un po 'troppo forte, e non discusso correttamente il problema di root! Da qualche parte in un approccio MVVM è necessario creare finestre/viste, quindi quanto sopra può essere utile - potenzialmente si dispone di una mappatura da ViewModel a View, o qualche forma di convenzione (ad esempio '\ ViewModels \ MyViewModel.cs -> \ Views \ MyView.cs'), ma dipende da te :) –

+0

Personalmente, sosterrei l'uso di un framework se si vuole un approccio MVVM hardcore, che di solito avvolge tutto questo per te :). Ho usato 'Caliburn Micro' che mi piace molto, ma dipende da te ^^ –

3

utilizzare un contentpresent nella finestra in cui si associa il DataConext. Quindi definire un Datatemplate per DataContext in modo che wpf possa eseguire il rendering di DataContext.qualcosa di simile al mio DialogWindow Service

quindi tutto ciò che serve è il vostro childWindow con un ContentPresenter:

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

</ContentPresenter> 
</Window> 
0

trovo la soluzione accettata molto utile, ma quando prova praticamente, ho scoperto che non ha la capacità di rendere l'UserControl (la vista che risulta dalla VM - Visualizza mapping>) attraccare all'interno della finestra di hosting per occupare l'intera area da essa fornita. Così ho esteso la soluzione per includere questa 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; 
} 

Il trucco sta usando una DockPanel per ospitare la vista convertito dalla VM.

quindi si utilizza il metodo precedente come segue, se si desidera che la dimensione della finestra in base alle dimensioni del suo contenuto:

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

o come segue se si dispone di una dimensione fissa per la finestra:

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