MVVM Light 5.0: Cómo usar el servicio de navegación

En la última versión de MVVM Light note , se ha indicado que MVVM Light ahora proporciona un “Servicio de navegación”.

Pero yo y mi amigo Google no podemos encontrar cómo usarlo.

Puedo ver que puedo solicitar un servicio de INavigationService en ServiceLocator, así que veo cómo puedo solicitar ir a otra página, pero:

  1. Creé una nueva ventana, donde espero reservar una zona específica para la “página”, ¿cómo especifico esto?
  2. ¿Cómo especifico todas las páginas disponibles? ¿Hay algo que deba llamar?
  3. ¿Cuál sería el formato de los parámetros dados al INavigationService

¿Hay alguna documentación oficial para esta biblioteca? Porque actualmente lo encuentro bien codificado y funciona bien, pero cuando tengo que buscar cómo usarlo, nunca encuentro una documentación / muestra que muestre cómo hacerlo, excepto su blog que tiene alguna entrada. Esto es muy frustrante La única documentación que encontré es la siguiente , no estoy muy familiarizado con Pluralsight, pero parece que es obligatorio tener una suscripción mensual (que como individuo, que está tratando de hacer una solicitud en mi tiempo libre, no es posible). )

Sí, MvvmLight introdujo el NavigationService en su última versión, pero no ofrecieron ninguna implementación con respecto a Wpf (puede usar el NavigationService implementado en WP, Metroapps, ..) pero lamentablemente no es Wpf , debe implementarlo por su cuenta, aquí cómo lo estoy haciendo actualmente ( crédito )

primero crea tu interfaz de navegación que implementa el MvvmLight INavigationService

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

el Parameter se usa para pasar objetos entre ViewModels , y el servicio INavigationService es parte del espacio de nombres GalaSoft.MvvmLight.Views

luego implementar esa interfaz como tal

 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 en el código anterior es la x: nombre de un simple control de Frame definido en Xaml utilizado para navegar entre las páginas (personalizar según sus necesidades)

Segundo : en el viewmodellocator , viewmodellocator su servicio de navegación ( SetupNavigation() ), para poder usarlo en sus 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); } 

Tercero: finalmente, use el servicio, por ejemplo

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

EDITAR

Se puede encontrar una muestra explícita en este repository .

Prefiero ir con un servicio de navegación de ViewModelFirst .

En mi opinión, es más fácil de usar e induce mucho menos código para agregar al crear un nuevo par de View / ViewModel.

Para esto necesitas algunas cosas :

Primero, una clase abstracta NavigableViewModel con algunos métodos para manejar la navegación en ambos sentidos. Todos sus modelos de vista van a heredar de esta clase:

NavigableViewModel.cs

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

Una MainWindow que contiene el Frame donde ocurre la navegación, solo piensa ocultar los controles de navegación predeterminados con NavigationUIVisibility = “Hidden” :

MainWindow.xaml

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

Y algún código detrás para manejar el cambio de ViewModels (permitiéndonos notificar cada página de su viewModelo):

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

Y en su MainViewModel, este pequeño método para navegar a su primer ViewModel (aquí 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 } } 

Para que esta llamada de ServiceLocator funcione, debemos agregar muy bien algunas cosas a nuestro 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() { } } 

Y ahora que tiene todo listo, agreguemos el núcleo del sistema, el Servicio de navegación (esa es la parte difícil):

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

¡Y ahora tienes todo funcionando! ¡Hurra! Demostremos con nuestro ejemplo LoginViewModel (que solo contiene un hermoso helloworld en un cuadrado negro):

LoginPage.xaml

      

Y su modelo de vista:

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

Admito que necesitas un código para empezar. Pero cuando todo funciona, terminas con un sistema muy fácil de usar.

¿Desea navegar a algún viewModel? Solo use myNavigationService.NavigateTo (someParam);

¿Desea agregar un nuevo par View / ViewModel? Simplemente agregue su viewModel en algún contenedor de IOC (en mis proyectos uso mi propio ioc, que me permite descargar mis viewmodels siempre que quiera y le doy un buen paquete de navegación) y le doy su servicio de navegación.

No sé si una función de navegación está disponible en mvvm light. Lo implementé con un enlace contentControl:

       

Y luego la propiedad viewmodel. Hereda de la clase mvvm light ViewModelBase.

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

Para que el control de contenido sepa qué control de usuario debe usar, define DataTemplates en app.xaml:

        

LogView es UserControl. Simplemente debe asignar LoggerViewModel a DisplayedtailViewModel, y Framework hará el trabajo.