c# wpf mvvm mvvm-light

c# - MVVM Light 5.0: cómo utilizar el servicio de navegación



wpf mvvm-light (3)

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

<xcad:LayoutDocumentPane> <xcad:LayoutDocument x:Name="DetailDoc" CanClose="False"> <ContentControl Content="{Binding DisplayedDetailViewModel}"/> </xcad:LayoutDocument> </xcad:LayoutDocumentPane>

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 tiene que usar, usted define DataTemplates en app.xaml:

<Application.Resources> <ResourceDictionary> <!-- We define the data templates here so we can apply them across the entire application. The data template just says that if our data type is of a particular view-model type, then render the appropriate view. The framework takes care of this dynamically. Note that the DataContext for the underlying view is already set at this point, so the view (UserControl), doesn''t need to have it''s DataContext set directly. --> <DataTemplate DataType="{x:Type viewModel:LoggerViewModel}"> <views:LogView /> </DataTemplate>

LogView es el UserControl. Solo tiene que asignar LoggerViewModel a DisplayedDetailViewModel, y el Framework hará el trabajo.

En la última versión de la nota MVVM Light , se ha indicado que MVVM Light ahora ofrece un "Servicio de navegación".

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

Puedo ver que puedo solicitar un INavigationService al 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

¿Existe alguna documentación oficial para esta biblioteca? Porque actualmente lo encuentro muy 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 this , no estoy muy familiarizado con Pluralsight, pero parece que es obligatorio tomar una suscripción mensual (que como individuo, que está tratando de hacer una solicitud en mi tiempo libre, no es posible )


Prefiero ir con un servicio de navegación ViewModelFirst .

En mi opinión, es más fácil de usar e induce 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 heredarán 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 ventana principal que contiene el marco donde ocurre la navegación, solo piense en ocultar los controles de navegación predeterminados con NavigationUIVisibility = "Hidden" :

MainWindow.xaml

<Window x:Class="YourProject.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SS3DViewModelFirstMvvmLightProject" mc:Ignorable="d" DataContext="{Binding Main, Source={StaticResource Locator}}" Title="MainWindow" Height="350" Width="525"> <-- Just remeber to replace x:Class="YourProject.Views.MainWindow" with your actual project path--> <Frame x:Name="Frame" NavigationUIVisibility="Hidden"> </Frame> </Window>

Y algo de código detrás para manejar el cambio de ViewModels (permitiéndonos notificar a cada página de su 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; } } }

Y en su MainViewModel, este pequeño método para navegar a su First ViewModel (aquí LoginViewModel):

MainViewModel.cs

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

Para que esta llamada a ServiceLocator funcione, necesitamos agregar algunas cosas a nuestro ViewModelLocator:

ViewModelLocator.cs

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

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

ViewModelFirstNavigationService

public class ViewModelFirstNavigationService { private Dictionary<Type, Uri> _registeredViews; private Dictionary<Type, Func<NavigableViewModel>> _registeredViewModels; private List<string> _allXamlPages; private MainViewModel _mainContainerViewModel; public NavigableViewModel CurrentViewModel; public ViewModelFirstNavigationService(MainViewModel mainContainerViewModel) { _mainContainerViewModel = mainContainerViewModel; _registeredViews = new Dictionary<Type, Uri>(); _registeredViewModels = new Dictionary<Type, Func<NavigableViewModel>>(); _allXamlPages = GetAllXamlPages(); } private List<string> 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<string> pages = new List<string>(); 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<T>(Func<T> 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<NavigableViewModel> 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<GenericNavigableViewModelType>(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 cuadrado negro):

LoginPage.xaml

<Page x:Class="YourProject.Views.LoginPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:SS3DViewModelFirstMvvmLightProject.Views" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Title="LoginPage"> <Grid Background="Gray"> <Label Content="{Binding HelloWorld}" Foreground="White" Background="Black" Width="150" Height="150"></Label> </Grid> </Page>

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.

¿Quieres navegar a alguna vista Modelo? Simplemente use myNavigationService.NavigateTo (someParam);

¿Desea agregar un nuevo par View / ViewModel? Simplemente agregue su modelo de vista en algún contenedor IOC (en mis proyectos utilizo mi propio ioc, lo que me permite descargar mis modelos de vista cuando lo desee y dar una buena pila de navegación) y darle su servicio de navegación.


Sí, MvvmLight introdujo el NavigationService en su última versión, pero no ofreció ninguna implementación con respecto a Wpf (puede usar el Implementation NavigationService en WP, Metroapps, ...) pero desafortunadamente no Wpf , debe implementarlo usted mismo, aquí cómo lo estoy haciendo actualmente ( credit )

primero cree su interfaz de navegación que implemente MvvmLight INavigationService

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

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

luego implemente esa interfaz así

class FrameNavigationService : IFrameNavigationService,INotifyPropertyChanged { #region Fields private readonly Dictionary<string, Uri> _pagesByKey; private readonly List<string> _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<string, Uri>(); _historic = new List<string>(); } 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 el x: Nombre de un control de Frame simple Definido en Xaml utilizado para navegar entre páginas (personalizar según sus necesidades)

Segundo : en viewmodellocator , viewmodellocator su servicio de navegación ( SetupNavigation() ), para que pueda usarlo en sus modelos de vista:

static ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SetupNavigation(); SimpleIoc.Default.Register<MainViewModel>(); SimpleIoc.Default.Register<LoginViewModel>(); SimpleIoc.Default.Register<NoteViewModel>(); } 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<IFrameNavigationService>(() => 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 repo .