在Store App中使用MVVM进行页面导航

我对这个问题感到非常头疼。 我真的不喜欢商店应用程序,但在这种情况下我被迫使用它。 我只和XAML合作了几个星期。

我的问题是:如何在我的ViewModel调用RelayCommand (从我的视图当然)来改变我视图中的页面? 更好的是,使用URI更改它,以便我可以将命令参数传递给文件。

我完全迷失了。 目前我在后面的View Code中使用this.Frame.Navigate(type type)来浏览页面。

我真的,我的意思是真的很感谢从a到z的描述在这种情况下该做什么。

我认为我可以做一些事情,比如在我的View上构建一个framecontainer并将其发送到我的ViewModel,并从那里导航当前帧到另一个。 但我不确定它在Store应用程序中是如何工作的。

我很抱歉缺乏好的问题,但我正处于截止日期,我需要以适当的方式将我的View连接到我的ViewModel ..我不喜欢同时查看代码隐藏和ViewModel代码。

有两种方法可以做到这一点,一种简单的方法是将视图中的中继命令操作传递给视图模型。

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

另一种方法是使用IocContainer和DependencyInjection。 这是一种更加失败的耦合方法。

我们需要一个导航页面界面,这样我们就不需要引用或了解有关PageX或任何UI元素的任何内容,假设您的viewmodel位于一个不了解UI的单独项目中。

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

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

正如斯科特所说,你可以使用NavigationService。 我首先要创建一个这个示例中不需要的接口,但是如果你将来使用dependency injection(与viewmodels和服务的良好解决方案)将会很有用:)

INavigationService:

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

NavigationService.cs将inheritanceINavigationService,您将需要以下命名空间

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

简单的ViewModel显示RelayCommand示例。 注意我使用DoSomething RelayCommand导航到另一个页面(Page2.xaml)。

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

在简单示例中,Ive在MainPage.cs中创建了viewmodel并添加了NavigationService,但您可以在其他位置执行此操作,具体取决于您的MVVM设置。

MainPage.cs

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

MainPage.xaml(绑定命令DoSomething)

     

希望有所帮助。

当ViewModel引用要导航到的视图时,我真的不喜欢。 所以我更喜欢ViewModel-first方法。 通过在ViewModel中使用ContentControls,DataTemplates for ViewModel类型和某种导航模式。

我的导航看起来像这样:

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

我的NavigatableViewModel看起来像:

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

我的导航器:

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

我的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中:

        

缺点是你有更多的ViewModel代码来管理你正在看的状态。 但显然这在可测试性方面也是一个巨大的优势。 当然,您的ViewModel不需要依赖于您的视图。

另外我使用Fody / PropertyChanged,这就是[ImplementPropertyChanged]的含义。 让我不要编写OnPropertyChanged代码。

这是实现NavigationService的另一种方法,不使用抽象类,也不在视图模型中引用视图类型。

假设目标页面的视图模型是这样的:

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

然后您的NavigationService可以简单地实现以下接口:

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

在主窗口ViewModel中,您需要注入导航器和目标页面的视图模型:

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

NavigationService的实现封装了视图类型(Page2)和对通过构造函数注入的框架的引用:

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

要获取框架只需在MainPage xaml中命名:

  

在MainPage的代码中,通过传递根框架来初始化你的引导程序:

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

最后这里是完整性的bootstrapper实现;)

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