2014-11-08 12 views
11

Sto avendo un forte mal di testa con questo problema. Non mi piacciono molto le app del negozio, ma sono costretto a usarlo in questo caso. Ho lavorato con XAML solo per alcune settimane.Pagina Navigazione utilizzando MVVM nell'app Store

La mia domanda è: Come posso chiamare un RelayCommand nel mio ViewModel (dal mio punto di vista, naturalmente) che cambierà la pagina del mio punto di vista? E ancora meglio, cambialo usando l'URI, in modo che io possa passare un parametro di comando al file.

Sono totalmente perso su questo. Attualmente sto usando this.Frame.Navigate(type type) nel codice della vista dietro per navigare tra le pagine.

Vorrei davvero e intendo VERAMENTE apprezzare una descrizione dalla A alla Z su cosa fare in questo caso.

Suppongo che potrei fare qualcosa come costruire un framecontainer sul mio View e inviarlo al mio ViewModel e da lì navigare il frame corrente su un altro. Ma non sono sicuro di come funzioni nelle app Store.

Sono davvero dispiaciuto per la mancanza di buone domande, ma ho una scadenza e ho bisogno di avere la mia vista connessa al mio ViewModel in un modo corretto .. Non mi piace avere entrambi vista codebehind pure come codice ViewModel.

+0

Avete cercato NavigationService? –

+0

stai usando mvvm light per la tua app? – SWilko

+0

No, non sono deliwheel. – Mathias

risposta

9

Ci sono 2 modi per farlo, un modo semplice è passare un'azione di comando relay dalla vista al modello di vista.

public MainPage() 
{ 
    var vm = new MyViewModel(); 
    vm.GotoPage2Command = new RelayCommand(()=>{ Frame.Navigate(typeof(Page2)) }); 
    this.DataContext = vm; 
} 

<Button Command={Binding GoToPage2Command}>Go to Page 2</Button> 

Un altro modo è utilizzare un IocContainer e DependencyInjection. Questo è un approccio più sfavorito.

Avremo bisogno di un'interfaccia per la pagina di navigazione in modo che non abbiamo bisogno di fare riferimento o sapere nulla su PageX o qualsiasi elemento dell'interfaccia utente assumendo che il tuo viewmodel si trova in un progetto separato che non conosce nulla sull'interfaccia utente.

ViewModel Progetto:

public interface INavigationPage 
    { 
    Type PageType { get; set; } 
    } 

    public interface INavigationService 
    { 
    void Navigate(INavigationPage page) { get; set; } 
    } 



public class MyViewModel : ViewModelBase 
    { 
    public MyViewModel(INavigationService navigationService, INavigationPage page) 
    { 
     GotoPage2Command = new RelayCommand(() => { navigationService.Navigate(page.PageType); }) 
    } 

    private ICommand GotoPage2Command { get; private set; } 
    } 

UI Progetto:

public class NavigationService : INavigationService 
    { 
     //Assuming that you only navigate in the root frame 
     Frame navigationFrame = Window.Current.Content as Frame; 
     public void Navigate(INavigationPage page) 
     { 
      navigationFrame.Navigate(page.PageType); 
     } 
    } 

public abstract class NavigationPage<T> : INavigationPage 
{ 
    public NavigationPage() 
    { 
     this.PageType = typeof(T); 
    } 
} 

public class NavigationPage1 : NavigationPage<Page1> { } 


public class MainPage : Page 
{ 
    public MainPage() 
    { 
     //I'll just place the container logic here, but you can place it in a bootstrapper or in app.xaml.cs if you want. 
     var container = new UnityContainer(); 
     container.RegisterType<INavigationPage, NavigationPage1>(); 
     container.RegisterType<INavigationService, NavigationService>(); 
     container.RegisterType<MyViewModel>(); 

     this.DataContext = container.Resolve<MyViewModel>();  
    } 
} 
+0

Bello, ma ho una domanda: Cosa succede se ho un contenitore creato in App.xaml.cs. Questa soluzione funziona alla grande, ma ho alcune NavigationPages che implementano un'interfaccia. Non riesco a creare due registri con la stessa interfaccia. Posso usare RegisterCollection, ma nel costruttore ViewModel devo usare gli indici e non è una soluzione carina. Vorrei registrare alcune NavigationPages e usarle nel costruttore viewModels. Esiste la possibilità di identificare quale NavigationPage dalla raccolta IEnumerable voglio utilizzare senza controllare il tipo (perché questi tipi sono in UIProject e non ho accesso. – darson1991

+0

Ioc ha modi per registrare più istanze e identificare tali istanze durante la risoluzione. e in diciannove, credo che tu possa dare un nome quando ti registri. Puoi controllare questo post http://stackoverflow.com/questions/16921008/getting-unity-to-resolve-multiple-instances-of-the-same -type – Lance

12

Come dice Scott, è possibile utilizzare un NavigationService. vorrei in primo luogo creare un'interfaccia questo non è necessario in questo esempio, ma sarà utile se si utilizza Dependency Injection (buona soluzione con ViewModels e servizi) in futuro :)

INavigationService:

NavigationService cs erediteranno INavigationService sono necessari i seguenti spazi dei nomi

using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls; 


public sealed class NavigationService : INavigationService 
{ 
    public void Navigate(Type sourcePage) 
    { 
     var frame = (Frame)Window.Current.Content; 
     frame.Navigate(sourcePage); 
    } 

    public void Navigate(Type sourcePage, object parameter) 
    { 
     var frame = (Frame)Window.Current.Content; 
     frame.Navigate(sourcePage, parameter); 
    } 

    public void GoBack() 
    { 
     var frame = (Frame)Window.Current.Content; 
     frame.GoBack(); 
    } 
} 

semplice ViewModel per mostrare esempio RelayCommand. NB I Passare a un'altra pagina (Pagina2.xaml) utilizzando DoSomething RelayCommand.

MyViewModel.cs

public class MyViewModel : INotifyPropertyChanged 
{ 
    private INavigationService _navigationService; 

    public event PropertyChangedEventHandler PropertyChanged; 

    public void OnPropertyChanged(string propertyName) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    public MyViewModel(INavigationService navigationService) 
    { 
     _navigationService = navigationService; 
    } 

    private ICommand _doSomething; 

    public ICommand DoSomething 
    { 
     get 
     { 
      return _doSomething ?? 
       new RelayCommand(() => 
        { 
         _navigationService.Navigate(typeof(Page2)); 
        }); 
     } 
    }} 

Nel semplice esempio Ive ha creato il ViewModel in MainPage.cs e ha aggiunto il NavigationService ma si può fare questo altrove a seconda di ciò che la configurazione MVVM è come.

MainPage.cs

public sealed partial class MainPage : Page 
{ 
    public MainPage() 
    { 
     this.InitializeComponent(); 

     var vm = new MyViewModel(new NavigationService()); 
     this.DataContext = vm; 
    } 
} 

MainPage.xaml (si lega al comando DoSomething)

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 
    <Button Width="200" Height="50" Content="Go to Page 2" 
      Command="{Binding DoSomething}"/> 
</Grid> 

Speranza che aiuta.

+0

Qual è la rilevanza di vm in MainPage.cs @dellywheel –

+0

@WD - Creo una nuova istanza di MyViewModel e quindi la aggiungo come DataContext di MainPage per abilitare il databinding ad esempio in modo da poter eseguire il binding al comando DoSomething da xaml. – SWilko

+1

@SWilko - Ciò significherebbe che la VM avrà un'idea di Page2, penso che sia ok ma non raccomandata. Preferisco passare qualcosa di più astratto che passare il tipo page2 stesso. – Lance

3

non mi piace molto quando un Riferimenti ViewModel Visualizzazioni per spostarsi. Quindi preferisco un approccio ViewModel-first. Usando ContentControls, DataTemplates per i tipi ViewModel & un qualche tipo di schema di navigazione nel mio ViewModels.

mio navigazione assomiglia a questo:

[ImplementPropertyChanged] 
public class MainNavigatableViewModel : NavigatableViewModel 
{ 
    public ICommand LoadProfileCommand { get; private set; } 

    public ICommand OpenPostCommand { get; private set; } 

    public MainNavigatableViewModel() 
    { 
     LoadProfileCommand = new RelayCommand(() => Navigator.Navigate(new ProfileNavigatableViewModel())); 
     OpenPostCommand = new RelayCommand(() => Navigator.Navigate(new PostEditViewModel { Post = SelectedPost }),() => SelectedPost != null); 
    } 
} 

mio NavigatableViewModel assomiglia:

[ImplementPropertyChanged] 
public class NavigatableViewModel 
{ 
    public NavigatorViewModel Navigator { get; set; } 

    public NavigatableViewModel PreviousViewModel { get; set; } 

    public NavigatableViewModel NextViewModel { get; set; } 

} 

E il mio Navigator:

[ImplementPropertyChanged] 
public class NavigatorViewModel 
{ 
    public NavigatableViewModel CurrentViewModel { get; set; } 

    public ICommand BackCommand { get; private set; } 

    public ICommand ForwardCommand { get; private set; } 

    public NavigatorViewModel() 
    { 
     BackCommand = new RelayCommand(() => 
     { 
      // Set current control to previous control 
      CurrentViewModel = CurrentViewModel.PreviousViewModel; 
     },() => CurrentViewModel != null && CurrentViewModel.PreviousViewModel != null); 

     ForwardCommand = new RelayCommand(() => 
     { 
      // Set current control to next control 
      CurrentViewModel = CurrentViewModel.NextViewModel; 
     },() => CurrentViewModel != null && CurrentViewModel.NextViewModel != null); 
    } 

    public void Navigate(NavigatableViewModel newViewModel) 
    { 
     if (newViewModel.Navigator != null && newViewModel.Navigator != this) 
      throw new Exception("Viewmodel can't be added to two different navigators"); 

     newViewModel.Navigator = this; 

     if (CurrentViewModel != null) 
     { 
      CurrentViewModel.NextViewModel = newViewModel; 
     } 

     newViewModel.PreviousViewModel = CurrentViewModel; 
     CurrentViewModel = newViewModel; 
    } 
} 

mio MainWindows.xaml:

<Window 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:viewmodels="clr-namespace:MyApp.ViewModels" 
     x:Class="MyApp.Windows.MainWindow" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="389" Width="573" 
     d:DataContext="{d:DesignInstance {x:Type viewmodels:MyAppViewModel}, IsDesignTimeCreatable=True}"> 
    <Grid> 
     <!-- Show data according to data templates as defined in App.xaml --> 
     <ContentControl Content="{Binding Navigator.CurrentViewModel}" Margin="0,32,0,0" /> 

     <Button Content="Previous" Command="{Binding Navigator.BackCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Width="75" /> 
     <Button Content="Next" Command="{Binding Navigator.ForwardCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="90,5,0,0" VerticalAlignment="Top" Width="75" /> 
    </Grid> 
</Window> 

App.xaml.cs:

public partial class App 
{ 
    protected override void OnStartup(StartupEventArgs e) 
    { 
     base.OnStartup(e); 

     new MainWindow {DataContext = new MyAppViewModel()}.Show(); 
    } 
} 

MyAppViewModel:

[ImplementPropertyChanged] 
public class MyAppViewModel 
{ 
    public NavigatorViewModel Navigator { get; set; } 

    public MyAppViewModel() 
    { 
     Navigator = new NavigatorViewModel(); 
     Navigator.Navigate(new MainNavigatableViewModel()); 
    } 
} 

App.xaml:

 <DataTemplate DataType="{x:Type viewmodels:MainNavigatableViewModel}"> 
      <controls:MainControl/> 
     </DataTemplate> 
     <DataTemplate DataType="{x:Type viewmodels:PostEditViewModel}"> 
      <controls:PostEditControl/> 
     </DataTemplate> 

Lo svantaggio è che avete più ViewModel codice che gestisce lo Stato di quello che stai guardando. Ma ovviamente questo è anche un enorme vantaggio in termini di testabilità. E ovviamente i tuoi ViewModels non hanno bisogno di dipendere dalle tue Visualizzazioni.

Plus Io uso Fody/PropertyChanged, questo è il significato di [ImplementPropertyChanged]. Mi impedisce di scrivere codice OnPropertyChanged.

+0

DataType non è supportato nei modelli UWP Il tipo di dati è calcolato La navigazione basata su modello in cui il controllo del contenuto risolve il modello dati automaticamente non è supportato (facilmente) in uwp come in wpf o silverlight. – bleepzter

+0

Interessante (e triste) Non sarebbe una soluzione? Http://stackoverflow.com/questions/33252915/how-to-associate-view-with-viewmodel-or-multiple-datatemplates-for-viewmodel –

1

Ecco un altro modo per implementare NavigationService, senza utilizzare una classe astratta e senza fare riferimento ai tipi di vista nel modello di visualizzazione.

Supponendo che il modello visualizzazione della pagina di destinazione è qualcosa di simile:

public interface IDestinationViewModel { /* Interface of destination vm here */ } 
class MyDestinationViewModel : IDestinationViewModel { /* Implementation of vm here */ } 

Allora la vostra NavigationService può semplicemente implementare la seguente interfaccia:

public interface IPageNavigationService 
{ 
    void NavigateToDestinationPage(IDestinationViewModel dataContext); 
} 

Nella finestra principale ViewModel è necessario iniettare il navigatore e il modello di visualizzazione della pagina di destinazione:

class MyViewModel1 : IMyViewModel 
{ 
    public MyViewModel1(IPageNavigationService navigator, IDestinationViewModel destination) 
    { 
     GoToPageCommand = new RelayCommand(() => 
       navigator.NavigateToDestinationPage(destination)); 
    } 

    public ICommand GoToPageCommand { get; } 
} 

L'attuazione del NavigationService incapsula il tipo di visualizzazione (Pagina2) e il riferimento al telaio che viene iniettato attraverso il costruttore:

class PageNavigationService : IPageNavigationService 
{ 
    private readonly Frame _navigationFrame; 

    public PageNavigationService(Frame navigationFrame) 
    { 
     _navigationFrame = navigationFrame; 
    } 

    void Navigate(Type type, object dataContext) 
    { 
     _navigationFrame.Navigate(type); 
     _navigationFrame.DataContext = dataContext; 
    } 

    public void NavigateToDestinationPage(IDestinationViewModel dataContext) 
    { 
     // Page2 is the corresponding view of the destination view model 
     Navigate(typeof(Page2), dataContext); 
    } 
} 

Per ottenere il telaio semplicemente nome nel vostro MainPage xaml:

<Frame x:Name="RootFrame"/> 

Nel codice dietro della MainPage inizializzare il programma di avvio automatico passando il telaio principale:

public sealed partial class MainPage : Page 
{ 
    public MainPage() 
    { 
     this.InitializeComponent(); 
     var bootstrapper = new Bootstrapper(RootFrame); 
     DataContext = bootstrapper.GetMainScreenViewModel(); 
    } 
} 

Infine ecco la bootstrappe r implementazione per completezza;)

class Bootstrapper 
{ 
    private Container _container = new Container(); 

    public Bootstrapper(Frame frame) 
    { 
     _container.RegisterSingleton(frame); 
     _container.RegisterSingleton<IPageNavigationService, PageNavigationService>(); 
     _container.Register<IMyViewModel, MyViewModel1>(); 
     _container.Register<IDestinationViewModel, IDestinationViewModel>(); 

#if DEBUG 
     _container.Verify(); 
#endif 
    } 

    public IMyViewModel GetMainScreenViewModel() 
    { 
     return _container.GetInstance<IMyViewModel>(); 
    } 
} 
Problemi correlati