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