2014-11-08 8 views
11

Mam poważny ból głowy z tym problemem. Naprawdę nie lubię aplikacji sklepowych, ale jestem zmuszony do korzystania z nich w tym przypadku. Pracowałem z XAML tylko przez kilka tygodni.Nawigacja strony za pomocą MVVM w aplikacji Store

Moje pytanie brzmi: Jak mogę zadzwonić pod numer RelayCommand w moim ViewModel (z mojego widoku oczywiście), który zmieni stronę w moim widoku? A jeszcze lepiej, zmień go za pomocą URI, dzięki czemu mogę przekazać parametr polecenia do pliku.

Jestem całkowicie zagubiony w tej sprawie. Obecnie używam this.Frame.Navigate(type type) w kodzie widoku z tyłu, aby przeglądać strony.

Naprawdę i naprawdę NAPRAWDĘ doceniam opis od a do z, co robić w tym przypadku.

Przypuszczam, że mogłem zrobić coś takiego jak zbudowanie ramki na moim widoku i wysłanie jej do mojego ViewModelu, a stamtąd nawigować w bieżącej ramie do innej. Ale nie jestem pewien, jak to działa w aplikacjach Sklepu.

Naprawdę przepraszam za brak dobrych pytań, ale jestem w terminie i muszę uzyskać mój widok podłączony do mojego ViewModel we właściwy sposób .. Nie podoba mi się posiadanie zarówno widoku kodu, jak również jako kod ViewModel.

+0

Czy sprawdziłeś usługę nawigacyjną? –

+0

używasz światła mvvm do swojej aplikacji? – SWilko

+0

Nie, nie jestem dellywheel. – Mathias

Odpowiedz

9

Są to 2 sposoby, aby w prosty sposób przekazać działanie polecenia przekazywania z widoku do modelu widoku.

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> 

Innym sposobem jest użycie IocContainer i DependencyInjection. Ten jest bardziej luźno powiązanym podejściem.

Będziemy potrzebować interfejsu do strony nawigacyjnej, abyśmy nie musieli się odwoływać lub Wiedzieliśmy nic o PageX lub jakimkolwiek elemencie interfejsu użytkownika zakładając, że twój viewmodel jest w osobnym projekcie, który nie wie nic o interfejsie użytkownika.

ViewModel Projekt:

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 projektu:

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

Miło, ale mam pytanie: Co jeśli mam jeden kontener utworzony w App.xaml.cs. To rozwiązanie działa świetnie, ale mam niewiele stron nawigacyjnych, które implementują jeden interfejs. Nie mogę utworzyć dwóch rejestrów z tym samym interfejsem. Mogę używać RegisterCollection, ale w konstruktorze ViewModel muszę używać indeksów i to nie jest dobre rozwiązanie. Chciałbym zarejestrować kilka stron nawigacyjnych i używać ich w konstruktorze viewModels. Czy istnieje możliwość identyfikacji, która nawigacja z IEnumerable kolekcji ma być używana bez sprawdzania typu (ponieważ te typy są w UIProject i nie mam dostępu) – darson1991

+0

Ioc ma sposoby rejestrowania wielu wystąpień i identyfikacji tych wystąpień podczas rozstrzygania. w końcówce sądzę, że możesz podać nazwę podczas rejestracji. Możesz sprawdzić ten post http://stackoverflow.com/questions/16921008/getting-unity-to-resolve-multiple- instance-of-the-same Typ – Lance

12

Jak Scott mówi, że możesz użyć usługi nawigacji. Chciałbym najpierw utworzyć interfejs ten nie jest potrzebny w tym przykładzie, ale będzie przydatna, jeśli używasz Dependency Injection (dobre rozwiązanie ViewModels i usług) w przyszłości :)

INavigationService:

NavigationService .cs odziedziczy INavigationService trzeba będzie następujące obszary nazw

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

Proste ViewModel pokazać RelayCommand przykład. NB I Przejdź do innej strony (Strona2.xaml) przy użyciu polecenia Przekazuj DoSomething.

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

w prosty przykład Ive stworzył ViewModel w MainPage.cs i dodaje NavigationService ale można to zrobić gdzie indziej w zależności od tego, co twoje ustawienia MVVM jest podobne.

MainPage.cs

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

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

MainPage.xaml (wiąże się z doSomething poleceń)

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

nadzieję, że pomoże.

+0

Jakie jest znaczenie vm w MainPage.cs @dellywheel –

+0

@WD - Tworzę nowe wystąpienie MyViewModel, a następnie dodaję je jako DataContext na MainPage, aby włączyć wiązanie danych, np. Abyśmy mogli powiązać z poleceniem DoSomething z xaml. – SWilko

+1

@Swilko - Oznaczałoby to, że maszyna wirtualna będzie miała pojęcie o Page2, myślę, że jest w porządku, ale nie jest zalecana. Wolałbym przekazać coś bardziej abstrakcyjnego niż przekazanie samej strony typu 2. – Lance

3

ja naprawdę nie lubię, gdy referencje ViewModel Odwiedzin nawigować. Więc wolę podejście oparte na ViewModelu. Korzystając z ContentControls, DataTemplates for ViewModel typy & pewnego rodzaju wzór nawigacji w moim ViewModels.

Moja nawigacji wygląda następująco:

[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 wygląda następująco:

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

    public NavigatableViewModel PreviousViewModel { get; set; } 

    public NavigatableViewModel NextViewModel { get; set; } 

} 

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

Moja 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> 

Minusem jest to, że masz więcej ViewModel kod, który zarządza stan tego, na co patrzysz. Ale oczywiście jest to także ogromna zaleta pod względem Testability. Oczywiście modele ViewModels nie muszą zależeć od Twoich widoków.

Plus Używam Fody/PropertyChanged, o to chodzi w [ImplementPropertyChanged]. Nie pozwala mi pisać kodu OnPropertyChanged.

+0

DataType nie jest obsługiwany w szablonach UWP Typ danych jest obliczany Nawigacja oparta na szablonach, w której kontrola treści automatycznie rozwiązuje szablon danych, nie jest obsługiwana (łatwo) w uwp, jak to jest w wpf lub silverlight – bleepzter

+0

Interesujące (i smutne) Czy nie byłoby to rozwiązanie? Http://stackoverflow.com/questions/33252915/how-to-associate-view-with-viewmodel-or-multiple-datatemplates-for-viewmodel –

1

Oto inny sposób implementacji usługi NavigationService bez użycia klasy abstrakcji i bez odwoływania typów widoków do modelu widoku.

Zakładając, że model widok od strony docelowej jest coś takiego:

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

Wtedy twój NavigationService może po prostu wdrożyć następujący interfejs:

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

W głównym oknie ViewModel trzeba wstrzyknij nawigator i model widoku docelowej strony:

Realizacja NavigationService obudowuje typ widoku (Strona2) i odesłanie do ramy, która jest wstrzykiwana przez konstruktora:

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

Aby ramka po prostu wymienić go w mainpage XAML:

<Frame x:Name="RootFrame"/> 

W związany kod z mainpage zainicjować inicjującego przekazując ramkę root:

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

Wreszcie jest tu bootstrappe r implementacja pod kątem kompletności;)

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>(); 
    } 
} 
Powiązane problemy