Navigazione della pagina utilizzando MVVM nell’app Store

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.

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

Sono completamente 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.

Presumo che potrei fare qualcosa come build 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 modo corretto .. Non mi piace avere sia la vista codebehind sia il codice ViewModel.

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

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 trovi in ​​un progetto separato che non conosce nulla sull’interfaccia utente.

Progetto ViewModel:

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

Progetto UI:

  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 : INavigationPage { public NavigationPage() { this.PageType = typeof(T); } } public class NavigationPage1 : NavigationPage { } 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(); container.RegisterType(); container.RegisterType(); this.DataContext = container.Resolve(); } } 

Come dice Scott, potresti usare un NavigationService. Per prima cosa creerò un’interfaccia che non è necessaria in questo esempio, ma sarà utile se usi Dependency Injection (buona soluzione con viewmodels e servizi) in futuro 🙂

INavigationService:

 public interface INavigationService { void Navigate(Type sourcePage); void Navigate(Type sourcePage, object parameter); void GoBack(); } 

NavigationService.cs erediterà INavigationService che ti serviranno i seguenti namespace

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

Nell’esempio semplice Ive ha creato il viewmodel in MainPage.cs e ha aggiunto NavigationService ma è ansible farlo altrove a seconda di come è stata configurata MVVM.

MainPage.cs

 public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var vm = new MyViewModel(new NavigationService()); this.DataContext = vm; } } 

MainPage.xaml (si collega al comando DoSomething)

     

Spero possa aiutare.

Non mi piace molto quando ViewModel fa riferimento a Views per navigare. Quindi preferisco un approccio ViewModel-first. Usando ContentControls, DataTemplates per i tipi ViewModel e qualche tipo di schema di navigazione nei miei ViewModels.

La mia navigazione ha questo aspetto:

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

My NavigatableViewModel ha il seguente aspetto:

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

E il mio navigatore:

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

Il mio MainWindows.xaml:

         

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:

        

Lo svantaggio è che hai più codice ViewModel che gestisce lo stato di ciò 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.

Inoltre io uso Fody / PropertyChanged, questo è ciò che riguarda [ImplementPropertyChanged]. Mi impedisce di scrivere codice OnPropertyChanged.

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

Supponendo che il modello di visualizzazione della pagina di destinazione sia qualcosa del genere:

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

Quindi il tuo 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’implementazione di NavigationService incapsula il tipo di visualizzazione (Pagina2) e il riferimento al frame che viene iniettato tramite 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 frame, basta chiamarlo sul tuo xaml MainPage:

  

Nel codice sottostante di MainPage inizializzare il bootstrapper passando il frame principale:

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

Finalmente ecco l’implementazione del bootstrapper per completezza;)

 class Bootstrapper { private Container _container = new Container(); public Bootstrapper(Frame frame) { _container.RegisterSingleton(frame); _container.RegisterSingleton(); _container.Register(); _container.Register(); #if DEBUG _container.Verify(); #endif } public IMyViewModel GetMainScreenViewModel() { return _container.GetInstance(); } }