MVVM Light 5.0:如何使用导航服务
在最新版本的MVVM Light note中 ,已经表明MVVM Light现在提供了“导航服务”。
但我和我的朋友谷歌无法找到如何使用它。
我可以看到我可以向ServiceLocator请求一个INavigationService
,所以我看到我可以要求转到另一个页面,但是:
- 我创建了一个新的窗口,我希望为“页面”保留一个特定的区域,我该如何指定?
- 如何指定所有可用页面? 有什么我应该打电话的吗?
- 给
INavigationService
的参数格式是什么
这个图书馆有官方文件吗? 因为目前我发现它很好地编码并且工作正常,但是当我要搜索如何使用它时,我从来没有找到如何使用的文档/示例,除了他的博客有一些条目。 这非常令人沮丧。 我找到的唯一文件就是这个 ,我对Pluralsight不太熟悉,但似乎必须每月订阅一次(作为个人,试图在我的空闲时间申请,是不可能的)。
是的, MvvmLight
在他们的最后一个版本中引入了NavigationService
, 但他们没有提供任何关于Wpf
的实现(你可以在WP,Metroapps中使用Implemented NavigationService
,但是很遗憾不是Wpf
,你需要自己实现,在这里我目前是怎么做的( 信用 )
首先创建实现MvvmLight
INavigationService
导航界面
public interface IFrameNavigationService:INavigationService { object Parameter { get; } }
Parameter
用于在ViewModels
之间传递对象, INavigationService
是GalaSoft.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,框架就可以完成工作。