Mvvm Light 5.0: How to Use the Navigation Service

MVVM Light 5.0: How to use the Navigation service

Yes, MvvmLight introduced the NavigationService in their last version but they did't offer any implementation regarding Wpf (you can use the Implemented NavigationService in WP, Metroapps, ..) but unfortunately not Wpf, you need to implement that by your self,
here how i am currently doing it (credit)

first create you navigation interface that Implements the MvvmLight INavigationService

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

the Parameter is used to pass objects between ViewModels, and the INavigationService is part of GalaSoft.MvvmLight.Views namespace

then implemente that interface like so

    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
}

the MainFrame in the above code is the x:Name of a simple Frame control Defined in Xaml used to navigate between pages (customize based on your needs)

Second: In the viewmodellocator, init your navigation service (SetupNavigation()), so you can use it in your viewmodels:

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

Third: finaly, use the service, for example

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

EDIT

An explicit sample can be found at this repo.

MVVM Light Toolkit NavigationService implementation for WPF using a Frame

MvvmLight doesn't provide an implementation of the INavigationService for WPF. This is because there is no standard navigation mechanism available in WPF as Laurent Bugnion - the creator of MvvmLight - has stated here: http://blog.galasoft.ch/posts/2014/10/announcing-mvvm-light-v5-for-windows-and-xamarin/

The Frame control has a NavigationService property. There is an example of how you could implement the INavigationInterface in WPF available here:

MVVM Light 5.0: How to use the Navigation service

Navigation in WPF MVVM

like the comments mention you provided not much information but maybe following question helps:
MVVM Light 5.0: How to use the Navigation service

Or maybe you can look at the docs or posts from the author.
http://www.mvvmlight.net/doc/nav1.cshtml

or... if you use google you find blog posts like this...
https://marcominerva.wordpress.com/2014/10/10/navigationservice-in-mvvm-light-v5/

HTH

How to navigate through windows with MVVM Light for WPF?

Eventually I did it this way.

Following the idea of o_q, I created NavigationWindow as MainWindow and changed all the the views to page.

Then, I created an inteface and a class which using Navigation:

    public interface INavigationService
{
event NavigatingCancelEventHandler Navigating;
void NavigateTo(Uri pageUri);
void GoBack();
}

public class NavigationService : INavigationService
{
private NavigationWindow _mainFrame;

#region Implementation of INavigationService

public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri pageUri)
{

if (EnsureMainFrame())
{
_mainFrame.Navigate(pageUri);
}

}

public void GoBack()
{
if (EnsureMainFrame()
&& _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}

}

#endregion

private bool EnsureMainFrame()
{
if (_mainFrame != null)
{
return true;
}

_mainFrame = System.Windows.Application.Current.MainWindow as NavigationWindow;

if (_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};

return true;
}

return false;
}

}

Then, in viewModelLocator I created all the const string nedded to store the paths to my views:

    public class ViewModelLocator
{

#region Views Paths

public const string FrontendViewPath = "../Views/FrontendView.xaml";
public const string BackendViewPath = "../Views/BackendView.xaml";
public const string StartUpViewPath = "../Views/StartUpView.xaml";
public const string LoginViewPath = "../Views/LoginView.xaml";
public const string OutOfOrderViewPath = "../Views/OutOfOrderView.xaml";
public const string OperativeViewPath = "../Views/SubViews/OperativeView.xaml";
public const string ConfigurationViewPath = "../Views/SubViews/ConfigurationView.xaml";
#endregion
}

In App.cs, in the Application_Startup event handler, with the help of Unity IoC I registered a singleton of NavigationService:

    public partial class App : System.Windows.Application
{

private static IUnityContainer _ambientContainer;
public static IServiceLocator AmbientLocator { get; private set; }

...

private void Application_Startup(object sender, System.Windows.StartupEventArgs e)
{

_ambientContainer =
new UnityContainer();

_ambientContainer.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());

AmbientLocator = new UnityServiceLocator(_ambientContainer);
ServiceLocator.SetLocatorProvider(() => AmbientLocator);

Now, in my ViewModelLocator, I can register a "Galasoft" message to catch all the events and navigate to a page; in the constructor I have:

        public ViewModelLocator()
{
CreateMain();
CreateFrontend();
CreateBackend();
CreateStartUp();
CreateOperative();
CreateLogin();
CreateConfiguration();
CreateOutOfOrder();

// Set Startup Page...
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));

Messenger.Default.Register<MoveToViewMessage>(this, message =>
{
switch (message.StateInfo.StateType)
{
case StateType.StartUpState:

ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath,UriKind.Relative));
break;
case StateType.LoginState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(LoginViewPath, UriKind.Relative));
break;
case StateType.OperativeState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OperativeViewPath, UriKind.Relative));
break;
case StateType.ConfigurationState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(ConfigurationViewPath, UriKind.Relative));
break;
case StateType.ClosedState:
case StateType.OutOfOrderState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OutOfOrderViewPath, UriKind.Relative));
break;
default:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));
break;
}
});

}

In this way I keep all the viewModels "ignorant"... they don't know anything about navigation, plus I don't have code behind.

If I need to navigate by using a button from a view I can resolve NavigationService from the connected viewModel and navigate to the Page I need.

And, most important thing, it works!

Mvvm light wpf navigation

I managed to solve / answer my own questions

  1. Just create a dumb ViewModel class for the Detail view, provide it with the (Main)ViewModel instance and make that ViewModel instance a property so you can bind to it. Done! Easy after all, and with Dependency injection it can become a breath.

  2. See this link page.DataContext not inherited from parent Frame?

  3. I solved this one and tracked it in the following separate question. See answer here
    WPF ViewBox blocks VisualTreeHelper search

Windows Phone Mvvm Light Navigate between Views

In MVVM Light 5 Laurent has introduce NavigationService

public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

var navigationService = this.CreateNavigationService();

SimpleIoc.Default.Register<INavigationService>(() => navigationService);

SimpleIoc.Default.Register<IDialogService, DialogService>();

SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<DetailsViewModel>();
}

private INavigationService CreateNavigationService()
{
var navigationService = new NavigationService();
navigationService.Configure("Details", typeof(DetailsPage));
// navigationService.Configure("key1", typeof(OtherPage1));

//From a working project.
navigationService.Configure("tnc", new System.Uri("/Views/TncAgreement.xaml", System.UriKind.Relative));

return navigationService;
}

Your ViewModel

public class MainViewModel : ViewModelBase
{

private INavigationService navigationService;

public RelayCommand DetailsCommand { get; set; }

public MainViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;

//Show Terms and condition agreement;
navigationService.NavigateTo("tnc");

DetailsCommand = new RelayCommand(() =>
{
navigationService.NavigateTo("Details", "My data");
});
}
}

Similar SO Answer

How to implement custom navigation service in mvvmlight

Here's a full implementation example that would solve the problem by adding a new interface that fully replaces the one from MvvmLight and also allows you to choose whether to use animations or not. In this example, we add the ability to control whether the navigation should be animated or not:

Interface

public interface ICustomNavigationService
{
string CurrentPageKey { get; }
void GoBack(bool animate = true);
void NavigateTo(string pageKey, bool animate = true);
void NavigateTo(string pageKey, object parameter, bool animate = true);
}

Implementation

public class NavigationService : ICustomNavigationService
{
private readonly Dictionary<string, Type> _pagesByKey = new Dictionary<string, Type>();
private NavigationPage _navigation;
public NavigationPage Navigation
{
get
{
return _navigation;
}
}
public string CurrentPageKey
{
get
{
lock (_pagesByKey)
{
if (_navigation.CurrentPage == null)
{
return null;
}

var pageType = _navigation.CurrentPage.GetType();

return _pagesByKey.ContainsValue(pageType)
? _pagesByKey.First(p => p.Value == pageType).Key
: null;
}
}
}

public void GoBack(bool animate = true)
{
_navigation.PopAsync(animate);
MessagingCenter.Send<INavigationService>(this, "NAVIGATING");
}

public void NavigateTo(string pageKey, bool animate = true)
{
NavigateTo(pageKey, null, animate);
MessagingCenter.Send<INavigationService>(this, "NAVIGATING");
}

public void NavigateTo(string pageKey, object parameter, bool animate = true)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
var type = _pagesByKey[pageKey];
ConstructorInfo constructor;
object[] parameters;

if (parameter == null)
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(c => !c.GetParameters().Any());

parameters = new object[]
{
};
}
else
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(
c =>
{
var p = c.GetParameters();
return p.Count() == 1
&& p[0].ParameterType == parameter.GetType();
});

parameters = new[]
{
parameter
};
}

if (constructor == null)
{
throw new InvalidOperationException(
"No suitable constructor found for page " + pageKey);
}

var page = constructor.Invoke(parameters) as Page;
_navigation.PushAsync(page, animate);
}
else
{
throw new ArgumentException(
string.Format(
"No such page: {0}. Did you forget to call NavigationService.Configure?",
pageKey),
"pageKey");
}
}
}

public void Configure(string pageKey, Type pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
_pagesByKey[pageKey] = pageType;
}
else
{
_pagesByKey.Add(pageKey, pageType);
}
}
}

public void Initialize(NavigationPage navigation)
{
_navigation = navigation;
}

}

From here, you can add other methods you would like. Make sure to inject this or use it directly where you were using your MvvmLight one before.



Related Topics



Leave a reply



Submit