MVVM Light 5.0:如何使用导航服务

在最新版本的MVVM Light note中 ,已经表明MVVM Light现在提供了“导航服务”。

但我和我的朋友谷歌无法找到如何使用它。

我可以看到我可以向ServiceLocator请求一个INavigationService ,所以我看到我可以要求转到另一个页面,但是:

  1. 我创建了一个新的窗口,我希望为“页面”保留一个特定的区域,我该如何指定?
  2. 如何指定所有可用页面? 有什么我应该打电话的吗?
  3. INavigationService的参数格式是什么

这个图书馆有官方文件吗? 因为目前我发现它很好地编码并且工作正常,但是当我要搜索如何使用它时,我从来没有找到如何使用的文档/示例,除了他的博客有一些条目。 这非常令人沮丧。 我找到的唯一文件就是这个 ,我对Pluralsight不太熟悉,但似乎必须每月订阅一次(作为个人,试图在我的空闲时间申请,是不可能的)。

是的, MvvmLight在他们的最后一个版本中引入了NavigationService , 但他们没有提供任何关于Wpf的实现(你可以在WP,Metroapps中使用Implemented NavigationService ,但是很遗憾不是Wpf ,你需要自己实现,在这里我目前是怎么做的( 信用 )

首先创建实现MvvmLight INavigationService导航界面

 public interface IFrameNavigationService:INavigationService { object Parameter { get; } } 

Parameter用于在ViewModels之间传递对象, INavigationServiceGalaSoft.MvvmLight.Views命名空间的一部分

然后实现这样的界面

 class FrameNavigationService : IFrameNavigationService,INotifyPropertyChanged { #region Fields private readonly Dictionary _pagesByKey; private readonly List _historic; private string _currentPageKey; #endregion #region Properties public string CurrentPageKey { get { return _currentPageKey; } private set { if (_currentPageKey == value) { return; } _currentPageKey = value; OnPropertyChanged("CurrentPageKey"); } } public object Parameter { get; private set; } #endregion #region Ctors and Methods public FrameNavigationService() { _pagesByKey = new Dictionary(); _historic = new List(); } public void GoBack() { if (_historic.Count > 1) { _historic.RemoveAt(_historic.Count - 1); NavigateTo(_historic.Last(), null); } } public void NavigateTo(string pageKey) { NavigateTo(pageKey, null); } public virtual void NavigateTo(string pageKey, object parameter) { lock (_pagesByKey) { if (!_pagesByKey.ContainsKey(pageKey)) { throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey"); } var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame; if (frame != null) { frame.Source = _pagesByKey[pageKey]; } Parameter = parameter; _historic.Add(pageKey); CurrentPageKey = pageKey; } } public void Configure(string key, Uri pageType) { lock (_pagesByKey) { if (_pagesByKey.ContainsKey(key)) { _pagesByKey[key] = pageType; } else { _pagesByKey.Add(key, pageType); } } } private static FrameworkElement GetDescendantFromName(DependencyObject parent, string name) { var count = VisualTreeHelper.GetChildrenCount(parent); if (count < 1) { return null; } for (var i = 0; i < count; i++) { var frameworkElement = VisualTreeHelper.GetChild(parent, i) as FrameworkElement; if (frameworkElement != null) { if (frameworkElement.Name == name) { return frameworkElement; } frameworkElement = GetDescendantFromName(frameworkElement, name); if (frameworkElement != null) { return frameworkElement; } } } return null; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } #endregion } 

上面代码中的MainFrame是x:简单Frame控件的名称在Xaml定义用于在页面之间导航(根据您的需要自定义)

第二 :在viewmodellocator ,初始化你的导航服务( SetupNavigation() ),这样你就可以在你的viewmodels中使用它:

 static ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SetupNavigation(); SimpleIoc.Default.Register(); SimpleIoc.Default.Register(); SimpleIoc.Default.Register(); } private static void SetupNavigation() { var navigationService = new FrameNavigationService(); navigationService.Configure("LoginView", new Uri("../Views/LoginView.xaml",UriKind.Relative)); navigationService.Configure("Notes", new Uri("../Views/NotesView.xaml", UriKind.Relative)); SimpleIoc.Default.Register(() => navigationService); } 

第三:最后,使用服务,例如

  public LoginViewModel(IFrameNavigationService navigationService) { _navigationService = navigationService; ... _navigationService.NavigateTo("Notes",data); .. 

编辑

可以在此回购中找到明确的样本。

我宁愿选择ViewModelFirst导航服务。

在我看来,在创建一对新的View / ViewModel时,它更容易使用并引导更少的代码添加。

为此你需要一些东西

首先是一个NavigableViewModel抽象类,它有一些方法来处理两种方式的导航。 您的所有viewModel都将从此类inheritance:

NavigableViewModel.cs

 public abstract class NavigableViewModel : ViewModelBase { public abstract void OnNavigatedTo(object parameter = null); public abstract void OnNavigatingTo(object parameter = null); } 

一个MainWindow包含导航发生的框架,只是想用NavigationUIVisibility =“Hidden”隐藏默认导航控件:

MainWindow.xaml

  <-- Just remeber to replace x:Class="YourProject.Views.MainWindow" with your actual project path-->    

还有一些代码可以处理ViewModels的更改(让我们通知每个页面的viewModel):

MainWindow.xaml.cs

 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ((MainViewModel)this.DataContext).ShowFirstView(); // we need to have our view loaded to start navigating Frame.LoadCompleted += (s, e) => UpdateFrameDataContext(); Frame.DataContextChanged += (s, e) => UpdateFrameDataContext(); } private void UpdateFrameDataContext() { Page view = (Page)Frame.Content; if (view != null) { view.DataContext = Frame.DataContext; } } } 

在你的MainViewModel中,这个小方法导航到你的第一个ViewModel(这里是LoginViewModel):

MainViewModel.cs

 public class MainViewModel : ViewModelBase { public MainViewModel() { } public void ShowFirstView() { ServiceLocator.Current.GetInstance().NavigateTo(); //To navigate wherever you want you just need to call this method, replacing LoginViewModel with YourViewModel } } 

要使这个ServiceLocator调用工作,我们需要很好地向ViewModelLocator添加一些东西:

ViewModelLocator.cs

  public class ViewModelLocator { public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register(); ViewModelFirstNavigationService navService = new ViewModelFirstNavigationService(Main); SimpleIoc.Default.Register(); navService.AddNavigableElement(SimpleIoc.Default.GetInstance); // so whenever you want to add a new navigabel View Model just add these lines here // SimpleIoc.Default.Register(); // navService.AddNavigableElement(SimpleIoc.Default.GetInstance); SimpleIoc.Default.Register(() => navService); } public MainViewModel Main { get { return ServiceLocator.Current.GetInstance(); } } public static void Cleanup() { } } 

现在你已经拥有了所有的东西,我们可以添加系统的核心,导航服务(这是棘手的部分):

ViewModelFirstNavigationService

 public class ViewModelFirstNavigationService { private Dictionary _registeredViews; private Dictionary> _registeredViewModels; private List _allXamlPages; private MainViewModel _mainContainerViewModel; public NavigableViewModel CurrentViewModel; public ViewModelFirstNavigationService(MainViewModel mainContainerViewModel) { _mainContainerViewModel = mainContainerViewModel; _registeredViews = new Dictionary(); _registeredViewModels = new Dictionary>(); _allXamlPages = GetAllXamlPages(); } private List GetAllXamlPages() { // this part is a bit tricky. We use it to find all xaml pages in the current project. // so you need to be sure that all your pages you want to use with your viewmodles need to end with page.xaml // Example : LoginPage.xaml will work fine. Parameters.xaml won't. System.Reflection.Assembly viewModelFirstProjectAssembly; viewModelFirstProjectAssembly = System.Reflection.Assembly.GetExecutingAssembly(); var stream = viewModelFirstProjectAssembly.GetManifestResourceStream(viewModelFirstProjectAssembly.GetName().Name + ".g.resources"); var resourceReader = new ResourceReader(stream); List pages = new List(); foreach (DictionaryEntry resource in resourceReader) { Console.WriteLine(resource.Key); string s = resource.Key.ToString(); if (s.Contains("page.baml")) { pages.Add(s.Remove(s.IndexOf(".baml"))); } } return pages; } private Type ResolveViewModelTypeFromSingletonGetterFunc(Func viewModelSingletonGetterFunc) { MethodInfo methodInfo = viewModelSingletonGetterFunc.Method; return methodInfo.ReturnParameter.ParameterType; } private Uri ResolvePageUriFromViewModelType(Type viewModelType) { string pageName = String.Empty; int index = viewModelType.Name.IndexOf("ViewModel"); pageName = viewModelType.Name.Remove(index); string pagePath = String.Format("{0}.xaml", _allXamlPages.Where(page => page.Contains(pageName.ToLower())).FirstOrDefault()); string cleanedPath = pagePath.Remove(0, "views/".Length); //obviously for this to work you need to have your views in a Views folder at the root of the project. But you are alowed yo reat sub folders in it return new Uri(cleanedPath, UriKind.Relative); } public void AddNavigableElement(Func viewModelSingletonGetter) { //Where the magic happens ! //If your are wondering why a Func, it's because we want our viewmodels to be instantiated only when we need them via IOC. //First we ge the type of our viewmodel to register for the Func. Type vmType = ResolveViewModelTypeFromSingletonGetterFunc(viewModelSingletonGetter); Uri uriPage = ResolvePageUriFromViewModelType(vmType); _registeredViews.Add(vmType, uriPage); _registeredViewModels.Add(vmType, viewModelSingletonGetter); } public void NavigateTo(object parameter = null) { Type key = typeof(GenericNavigableViewModelType); NavigateTo(key, parameter); } public void NavigateTo(Type key, object parameter = null) { CurrentViewModel?.OnNavigatingTo(parameter); CurrentViewModel = _registeredViewModels[key].Invoke(); Uri uri = _registeredViews[key]; ((MainWindow)Application.Current.MainWindow).Frame.Source = uri; ((MainWindow)Application.Current.MainWindow).Frame.DataContext = CurrentViewModel; CurrentViewModel.OnNavigatedTo(parameter); } } 

现在你一切正常! 华友世纪! 让我们用我们的示例LoginViewModel演示(其中只包含黑色方块中的美丽helloworld):

LoginPage.xaml

      

它的viewmodel:

LoginViewModel.cs

 public class LoginViewModel : NavigableViewModel { private string _helloWorld; public string HelloWorld { get { return _helloWorld; } set { _helloWorld = value; RaisePropertyChanged(() => HelloWorld); } } public LoginViewModel() { HelloWorld = "Hello World"; } public override void OnNavigatedTo(object parameter = null) { // whatever you want to happen when you enter this page/viewModel } public override void OnNavigatingTo(object parameter = null) { // whatever you want to happen when you leave this page/viewmodel } } 

我承认你需要一些代码才能开始。 但是当一切正常时,你最终会得到一个非常容易使用的系统。

想要导航到一些viewModel? 只需使用myNavigationService.NavigateTo(someParam);

想要添加一对新的View / ViewModel? 只需将viewModel添加到某个IOC容器中(在我的项目中,我使用自己的ioc,这样我可以随时卸载我的视图模型并提供一些精美的导航堆栈)并为其提供导航服务。

我不知道mvvm light中是否有导航function。 我用contentControl绑定实现了它:

       

然后是viewmodel属性。 它inheritance自mvvm light ViewModelBase类。

  public ViewModelBase DisplayedDetailViewModel { get { return displayedDetailViewModel; } set { if (displayedDetailViewModel == value) { return; } displayedDetailViewModel = value; RaisePropertyChanged("DisplayedDetailViewModel"); } 

要使内容控件知道它必须使用哪个用户控件,请在app.xaml中定义DataTemplates:

        

LogView是UserControl。 您只需将LoggerViewModel分配给DisplayedDetailViewModel,框架就可以完成工作。