I've been trying to get view-based navigation to work using Prism and regions. I tried going through the documentation on MSDN, but for some reason I can't get it to work, and I don't know what I am doing wrong. So this is what I got so far:
MainShellViewModel.cs
//Private Variables
private readonly IRegionManager _regionManager;
//Public Variables
public DelegateCommand<string> NavigateCommand { get; set; }
//Functions and Methods
public MainShellViewModel(IRegionManager regionManager)
{
//Region Manager
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
Initialize();
}
public void Initialize()
{
//Startup View
_regionManager.RegisterViewWithRegion("ViewMainFrame", typeof(Views.Dashboard));
}
public void Navigate(string uri)
{
//Navigation
if(uri != null)
{
_regionManager.RequestNavigate("ViewMainFrame", uri);
}
}
Side note: One of the many tutorials I followed had me put in that Navigate method, do I need it? I am using the MainShellViewModel as the Main view that is injected on startup.
DashboardViewModel.cs: (Contains the error)
{
//Private Variables
private bool _canExercise = true;
//Public Variables
public bool CanExercise()
{
return _canExercise;
}
RelayCommand _exerciseSelCommand;
public ICommand ExerciseSelCommand
{
get
{
if (_exerciseSelCommand == null)
_exerciseSelCommand = new RelayCommand(ExerciseSel, CanExercise);
return _exerciseSelCommand;
}
}
//Dashboard Functions and Methods
IRegion _regionManager;
private void ExerciseSel()
{
SoundPlayers.ButtonSound();
_regionManager.RequestNavigate(new Uri("ExerciseView", UriKind.Relative)); //This gives me the error, it says it can't be nullable?
}
Here is where I register my containers/views, etc.
Bootstrapper.cs:
protected override void ConfigureContainer()
{
base.ConfigureContainer();
#region Region Register Zone
//register views here!
Container.RegisterType(typeof(object), typeof(Views.LoginView), "LoginView");
Container.RegisterType(typeof(object), typeof(Views.Dashboard), "Dashboard");
Container.RegisterType(typeof(object), typeof(Views.ExerciseView), "SettingsView");
Container.RegisterType(typeof(object), typeof(Views.ResultsView), "ResultsView");
Container.RegisterType(typeof(object), typeof(Views.UserCreationView), "UserCreationView");
#endregion
}
So basically I just want to be able to get from the Dashboard (Which is my current startup view) to any other view that's registered in my containers on a click of a button.
MainShell.xaml:
<Window x:Name="Shell"
x:Class="Equinox.Views.MainShell"
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"
mc:Ignorable="d"
xmlns:prism="http://www.codeplex.com/prism"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="Equinox"
FontFamily="Quicksand"
Height="900"
Width="1500"
SizeToContent="WidthAndHeight"
ResizeMode="CanResize"
Background="#EEF3F4"
WindowStyle="SingleBorderWindow"
Icon="/Equinox;component/favicon.ico"
WindowStartupLocation="CenterScreen">
<!-- Main View Region -->
<ContentControl x:Name="ContentControlMain"
prism:RegionManager.RegionName="ViewMainFrame"
Focusable="False"/>
However, I keep getting errors when I try to make my region take another view. The way I was doing it was by using my DashboardViewModel, and creating another IRegionManager named _regionManager, and doing the RequestNavigation. I got no errors until I ran it and pressed the button that should link me to the next view.
Any help would be appreciated!
Thanks!
Not sure if you got the answer already, but I was running into the same thing today. The comment from Brian gave me the necessary hint.
I have the following code in the bootstrapper. This registers the two views to allow navigation to them:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterTypeForNavigation<ViewA>("ViewA");
Container.RegisterTypeForNavigation<ViewB>("ViewB");
}
}
This gives a MainWindow, which registers ViewA and ViewB.
To allow for navigation to ViewB from a button on ViewA, the following needs to be done in ViewAViewModel:
public class ViewAViewModel: BindableBase
{
private readonly IRegionManager _regionManager;
public ViewAViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
ButtonCommand= new DelegateCommand(ButtonClicked);
}
private void ButtonClicked()
{
_regionManager.RequestNavigate("ContentRegion", "ViewB");
}
}
In the XAML form ViewA the last thing you need is of course the button itself:
<Button Content="Navigate" Command="{Binding ButtonCommand}"/>
Check out sampels 17, 18, and 19. This should help get you going in the right direction:
https://github.com/PrismLibrary/Prism-Samples-Wpf
Related
I am new to WPF/Prism and I am trying to do the following: I need to load a Login View ( windows or user control) before launching my Main Window. And after successful login, remove the Login view and go to the Main Mindow.
I have looked at several answers within here but all are referencing older versions of PRISM with the boostrapper class.
I have a WPF (Prism 7) application which contains the main project and an Authorization Module.
From my Main project App.xaml.cs
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<AuthorizationModule>();
}
In the Authorization module i have my LoginView / LoginViewModel. The authortization Module registers a LoginService which will be injected to the LoginViewModel
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register(typeof(ILoginService), typeof(LoginService));
}
The LoginViewModel will take care of authenticating the user using this LoginService.
Part of the answers I've seen show something like this:
protected override void InitializeShell()
{
Window login = new LoginView();
var loginVM = new LoginViewModel(new LoginAuth());
loginVM.LoginCompleted += (sender, args) =>
{
login.Close();
Application.Current.MainWindow.Show();
};
login.DataContext = loginVM;
// problem below:
login.ShowDialog();
}
However, this seems a bit off having to instantiate the LoginView manually instead of just having a container do it for you.
Also, the InitiallizeShell on PRISM 7 is expecting the current shell being created. Im not sure if I should use this value being passed to Activate the Main Window.
protected override void InitializeShell(Window shell)
I also read from Brian Lagunas himself on Github to maybe use the EventAggregator (which I've tried). i've had the Authorization Module register the EventAggregator and from the LoginViewModel, on successful login, publish a SuccessfulLoginEvent but I can figure out how to subscribe to that event from Main app
So basically the expected result is that when the application launches, if the user is not logged in, show the LoginView, after the user authenticates him self, take him to the MainWindow with all needed modules already loaded.
Any help would be greatly appreciated.
I gave this a try and came up with the following:
App:
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<Login>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register(typeof(object), typeof(Login), "Login");
containerRegistry.RegisterInstance(typeof(LoginViewModel), new LoginViewModel(Container.GetContainer(), Container.Resolve<RegionManager>()));
containerRegistry.Register(typeof(object), typeof(MainWindow), "MainWindow");
containerRegistry.RegisterInstance(typeof(MainWindowViewModel), new MainWindowViewModel(Container.GetContainer(), Container.Resolve<RegionManager>()));
}
}
XAML:
<Window x:Class="LoginTest.Views.Login"
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:LoginTest.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Title="Login" Height="250" Width="400">
<Grid>
<Button Content="Login" HorizontalAlignment="Left" Height="61" Margin="100,100,0,0" VerticalAlignment="Top" Width="164" Command="{Binding LoginCommand}"/>
</Grid>
Codebehind:
public partial class Login : Window
{
public Login()
{
InitializeComponent();
((LoginViewModel)DataContext).NewWindow += StartMainApp;
((LoginViewModel)DataContext).CloseWindow += CloseWindow;
}
private void StartMainApp(Object win)
{
Application.Current.MainWindow = (Window)win;
Application.Current.MainWindow.Show();
}
private void CloseWindow()
{
this.Close();
}
}
ViewModel:
class LoginViewModel : BindableBase
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
private PrismApplication _application;
private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public DelegateCommand LoginCommand { get; set; }
public delegate void NewWindowDelegate(Object win);
public delegate void CloseWindowDelegate();
public CloseWindowDelegate CloseWindow{ get; set; }
public NewWindowDelegate NewWindow { get; set; }
public LoginViewModel(IUnityContainer container, IRegionManager regionManager)
{
_regionManager = regionManager;
_container = container;
LoginCommand = new DelegateCommand(OnLogin);
}
private void OnLogin()
{
Trace.WriteLine("Logging in");
// do your login stuff
// If Login OK continue here
NewWindow.Invoke(_container.Resolve<MainWindow>());
CloseWindow.Invoke();
}
}
I hope my example is of any use!
I want to express my gratitude to user947737 for good advice. I tried it myself and it worked out for me.
Additionally, of course, I added the MainWindowsModelView constructor.
public class MainWindowViewModel : BindableBase
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
private PrismApplication _application;
//private string _title = "Prism Application";
//public string Title
//{
// get { return _title; }
// set { SetProperty(ref _title, value); }
//}
public MainWindowViewModel(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
}
}
I created a wpf project (using caliburn micro with MVVM pattern, no code-behind) with 2 view models and their related views:
ShellView.xaml and ShellViewModel.cs
OtherView.xaml and OtherViewModel.cs
The ShellView contains:
ContentControl which refers to OtherView/OtherViewModel.
A TextBox, which contains so called "target text".
The OtherView contains a StackPanel which contains:
A TextBox which accepts text from user (as "source text").
A Button which copies the source text to target on Right-MouseButton Click event.
My questions:
How to copy the source text which is in OtherView/ViewModel to target text which is in ShellView/ViewModel? Any best practice for it?
Can ShellViewModel catch the PropertyChange event from source TextBox?
How to copy in opposite direction (from target to source)?
Thank you in advance and please feel free to modify the code below if needed.
ShellView.xaml
<UserControl
x:Class="CmMultipleViewModelView.Views.ShellView"
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"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid Width="800" Height="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl
x:Name="ActiveItem"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBox
x:Name="TargetText"
Grid.Column="1"
Width="80"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</UserControl>
ShellViewModel.cs
public class ShellViewModel : Conductor<object>
{
public ShellViewModel()
{
DisplayName = "Shell Window";
var otherVM = new OtherViewModel();
ActivateItem(otherVM);
}
public string DisplayName { get; set; }
private string _targetText = "Target";
public string TargetText
{
get => _targetText;
set
{
_targetText = value;
NotifyOfPropertyChange(() => TargetText);
}
}
}
OtherView.xaml
<UserControl
x:Class="CmMultipleViewModelView.Views.OtherView"
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"
d:DesignHeight="150"
d:DesignWidth="150"
mc:Ignorable="d">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Top"
Orientation="Vertical">
<TextBox
x:Name="SourceText"
Width="80"
Margin="3" />
<Button
x:Name="CopyText"
Width="100"
Margin="3"
Content="Copy" />
</StackPanel>
</UserControl>
OtherViewModel.cs
public class OtherViewModel : Screen
{
private string _sourceText = "Source";
public string SourceText
{
get => _sourceText;
set
{
_sourceText = value;
NotifyOfPropertyChange(() => SourceText);
}
}
public void CopyText()
{
// How to copy the SourceText to TargetText using Caliburn Micro MVVM?
// Can ShellViewModel catch the PropertyChange event from source textbox?
}
}
Edited:
AppBootstrapper.cs
public class AppBootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container = new SimpleContainer();
public AppBootstrapper()
{
Initialize();
}
public ShellViewModel ShellViewModel { get; set; }
protected override object GetInstance(Type serviceType, string key)
{
return _container.GetInstance(serviceType, key);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetAllInstances(serviceType);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void Configure()
{
base.Configure();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.Singleton<ShellViewModel>();
_container.PerRequest<OtherViewModel>(); // Or Singleton if there'll only ever be one
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
try
{
base.OnStartup(sender, e);
DisplayRootViewFor<ShellViewModel>();
}
catch (Exception ex)
{
Debug.WriteLine(ex.StackTrace);
Debug.WriteLine(ex.Message);
}
}
}
ShellViewModel.cs
public class ShellViewModel : Conductor<object>, IHandle<string>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAgg, OtherViewModel otherVm)
{
_eventAggregator = eventAgg;
_eventAggregator.Subscribe(this);
ActivateItem(otherVm);
}
public sealed override void ActivateItem(object item)
{
base.ActivateItem(item);
}
public OtherViewModel OtherViewModel { get; set; }
private string _targetText = "Target";
public string TargetText
{
get => _targetText;
set
{
_targetText = value;
NotifyOfPropertyChange(() => TargetText);
}
}
public void Handle(string message)
{
TargetText = message;
}
}
OtherViewModel.cs
public class OtherViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
public OtherViewModel(IEventAggregator eventAgg)
{
_eventAggregator = eventAgg;
}
private string _sourceText = "Source";
public string SourceText
{
get => _sourceText;
set
{
_sourceText = value;
NotifyOfPropertyChange(() => SourceText);
}
}
public void CopyText()
{
_eventAggregator.PublishOnUIThreadAsync(SourceText);
}
}
Edited again
added
_container.Singleton<IWindowManager, WindowManager>();
in AppBootstraper::Configure
Problem solved!
As others have said, the correct way is to use the event aggregator.
If you're using the SimpleContainer in Caliburn.Micro then in your OnConfigure override you'd put:
_container.Singleton<IEventAggregator>();
This will create an instance of an IEventAggregator when you first access it. Now, you have a choice of how you access it. Either by injecting into your constructor or using the IoC.GetInstance method.
If you want to inject then you'll need to modify your viewmodels:
public class ShellViewModel : Conductor<object>, IHandle<string>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventagg, OtherViewModel otherVM)
{
_eventAggregator = eventagg;
_eventAggregator.Subscribe(this);
ActivateItem(otherVM);
}
public void Handle(string message)
{
TargetText = message;
}
}
public class OtherViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
public OtherViewModel(IEventAggregator eventagg)
{
_eventAggregator = eventagg;
}
public void CopyText()
{
_eventAggregator.PublishOnUIThread(SourceText);
}
}
In the Bootstrapper you'll then need to register both your viewmodels:
_container.Singleton<ShellViewModel>();
_container.PerRequest<OtherViewModel>(); // Or Singleton if there'll only ever be one
So, what's all this doing?
In your ShellViewModel we're telling it to implement the IHandle interface for strings.
IHandle<string>
Anytime a string event is fired the ShellViewModel will call the Handle method with the same signature. If you only want to handle specific types then create a new class to hold your copy text and change the handler from string to your type.
IHandle<string>
IHandle<yourtype>
When the event aggregator receives the string event it will call any listeners Handle method. In your case Handle(string message). If you change the IHandle type you'll also need to change the Handle method to the same type.
public void Handle(string message)
{
TargetText = message;
}
This will set the TargetText to whatever the string value is that you fired in your event.
We have an instance of IEventAggregator, this is a singleton object so anywhere it's referenced it should be the same one. We've modified your ShellViewModel constructor to accept an IEventAggregator object and an instance of your OtherViewModel.
Once we've stored the reference to the event aggregator locally we call:
_eventAggregator.Subscribe(this);
This tells the event aggregator that we're interested in any events that will be handled by the IHandle's we've defined on the class (you can have multiple as long as they handle different types).
With the OtherViewModel it's a little different, we've again added IEventAggregator to the constructor so we can inject it on start up but this time we're not subscribing to any events as OtherViewModel only fires an event.
In your CopyText method you would call:
_eventAggregator.PublishOnUIThread(SourceText);
This raises the event on the event aggregator. Which is then propagated to the ShellViewModel which handles it with the Handle method.
As long as you register your view models and the event aggregator in the SimpleContainer instance in your Bootstrapper then Caliburn.Micro will know which items to inject into the constructors when it creates the instances of your VM's.
The flow would go:
ShellViewModel subscribes to the string event
_eventAggregator.Subscribe(this);
User types some text into SourceText
User presses right mouse button, this invokes:
CopyText()
Which calls:
_eventAggregator.PublishOnUIThread(SourceText);
The event aggregator then checks all the subscribed view models that have a IHandle interface and then calls:
Handle(string message)
on each one.
In your case this then sets the TargetText to the message:
TargetText = message;
Apologies for the wall of text!
There is a much simpler way which is to have your ShellViewModel subscribe to the PropertyChanged event on OtherViewModel:
otherVM.PropertyChange += OtherVMPropertyChanged;
Then in handler you'd have to look for notifications of the SourceText property and update your target text. A much simpler solution but would mean you'd tightly couple your ShellVM and OtherVM plus you'd have to make sure to unsubscribe from the event when you close the OtherVM otherwise it will never get garbage collected.
Here's how to set up the DI container
In your Bootstrapper class you''ll want to add the SimpleContainer:
private SimpleContainer _simplecontainer = new SimpleContainer();
Then you'll need to override some methods and make sure the code is as below:
protected override object GetInstance(Type serviceType, string key)
{
return _container.GetInstance(serviceType, key);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetAllInstances(serviceType);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
Now override the OnConfigure method. This is where we tell Caliburn.Micro what ViewModels we're using and where we set up the EventAggregator and the WindowManager (so it can wrap your ShellViewModel in a window):
protected override void Configure()
{
base.Configure();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.Singleton<ShellViewModel>();
_container.PerRequest<OtherViewModel>(); // If you'll only ever have one OtherViewModel then you can set this as a Singleton instead of PerRequest
}
Your DI is all now set up.
Finally in your StartUp override you'll just make sure you've got it looking like this:
protected override void OnStartup(object sender, StartupEventArgs e)
{
base.OnStartup(sender, e);
DisplayRootViewFor<ShellViewModel>();
}
If you run your application now, when the ShellViewModel is created Caliburn.Micro will look at the constructor parameters of ShellViewModel to see what it needs to provide. It will see that it requires an event aggregator and OtherViewModel so it will look in the SimpleContainer to see if they've been registered. If they have then it will create the instances (if needed) and inject them into the constructor. As it creates the OtherViewModel it will also check the constructor params and also create whatever that needs.
Finally it will display the ShellViewModel.
I'm using NInject to resolve the dependency for my first WPF application.
Following are my code snippets.
My App.xaml.cs goes like.
public partial class App : Application
{
private IKernel container;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ConfigureContainer();
ComposeObjects();
}
private void ComposeObjects()
{
Current.MainWindow = this.container.Get<MainWindow>();
}
private void ConfigureContainer()
{
this.container = new StandardKernel();
container.Bind<ISystemEvents>().To<MySystemEvents>();
}
}
App.xaml goes like this.
<Application x:Class="Tracker.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
MainWindow.xaml.
<Window x:Class="Tracker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:Tracker.ViewModel"
Title="MainWindow" Height="150" Width="350">
<Window.DataContext>
<viewmodel:TrackerViewModel>
</viewmodel:TrackerViewModel>
</Window.DataContext>
<Grid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
and viewmodel
internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
public TrackerViewModel(ISystemEvents systemEvents)
{
systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
}
}
Now when I launch the application, I get an exception An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll in InitializeComponent() method.
I know its because of the viewmodel class not have parameterless constructor. But I am not able to undestand why dependency injector is not able to resolve this? Am I doing something wrong?
Any help would be greatly appreciated.
First of all, I recommend reading the book Dependency Injection in .NET, especially the section about WPF. But even if you don't read it, there is a helpful example in the code download for the book.
You have already worked out that you need to remove the StartupUri="MainWindow.xaml" from your App.xaml file.
However, when using DI you must not wire up the DataContext declaratively otherwise it will only be able to work with the default constructor.
<Window x:Class="WpfWithNinject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="350">
</Window>
The pattern that is used in WPF is a bit confusing when it comes to DI. The main issue is that if you want your ViewModel to be able to control its own windowing environment, there is a circular dependency issue between the MainWindow and its ViewModel, so you will need to make an Abstract Factory in order to instantiate the ViewModel so the dependencies can be satisfied.
Creating the ViewModel Factory
internal interface ITrackerViewModelFactory
{
TrackerViewModel Create(IWindow window);
}
internal class TrackerViewModelFactory : ITrackerViewModelFactory
{
private readonly ISystemEvents systemEvents;
public TrackerViewModelFactory(ISystemEvents systemEvents)
{
if (systemEvents == null)
{
throw new ArgumentNullException("systemEvents");
}
this.systemEvents = systemEvents;
}
public TrackerViewModel Create(IWindow window)
{
if (window == null)
{
throw new ArgumentNullException("window");
}
return new TrackerViewModel(this.systemEvents, window);
}
}
The TrackerViewModel also needs to have some rework so it can accept the IWindow into its constructor. This allows the TrackerViewModel to control its own windowing environment, such as showing modal dialog boxes to the user.
internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
private readonly IWindow window;
public TrackerViewModel(ISystemEvents systemEvents, IWindow window)
{
if (systemEvents == null)
{
throw new ArgumentNullException("systemEvents");
}
if (window == null)
{
throw new ArgumentNullException("window");
}
systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
this.window = window;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}
Adapting the Window
You need to fix up the framework a bit with an abstract type for the windows, IWindow, and an abstraction to help manage DI of each of the windows, WindowAdapter.
internal interface IWindow
{
void Close();
IWindow CreateChild(object viewModel);
void Show();
bool? ShowDialog();
}
internal class WindowAdapter : IWindow
{
private readonly Window wpfWindow;
public WindowAdapter(Window wpfWindow)
{
if (wpfWindow == null)
{
throw new ArgumentNullException("window");
}
this.wpfWindow = wpfWindow;
}
#region IWindow Members
public virtual void Close()
{
this.wpfWindow.Close();
}
public virtual IWindow CreateChild(object viewModel)
{
var cw = new ContentWindow();
cw.Owner = this.wpfWindow;
cw.DataContext = viewModel;
WindowAdapter.ConfigureBehavior(cw);
return new WindowAdapter(cw);
}
public virtual void Show()
{
this.wpfWindow.Show();
}
public virtual bool? ShowDialog()
{
return this.wpfWindow.ShowDialog();
}
#endregion
protected Window WpfWindow
{
get { return this.wpfWindow; }
}
private static void ConfigureBehavior(ContentWindow cw)
{
cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
}
}
public static class PresentationCommands
{
private readonly static RoutedCommand accept = new RoutedCommand("Accept", typeof(PresentationCommands));
public static RoutedCommand Accept
{
get { return PresentationCommands.accept; }
}
}
Then we have a specialized window adapter for the MainWindow which ensures the DataContext property is initialized correctly with the ViewModel.
internal class MainWindowAdapter : WindowAdapter
{
private readonly ITrackerViewModelFactory vmFactory;
private bool initialized;
public MainWindowAdapter(Window wpfWindow, ITrackerViewModelFactory viewModelFactory)
: base(wpfWindow)
{
if (viewModelFactory == null)
{
throw new ArgumentNullException("viewModelFactory");
}
this.vmFactory = viewModelFactory;
}
#region IWindow Members
public override void Close()
{
this.EnsureInitialized();
base.Close();
}
public override IWindow CreateChild(object viewModel)
{
this.EnsureInitialized();
return base.CreateChild(viewModel);
}
public override void Show()
{
this.EnsureInitialized();
base.Show();
}
public override bool? ShowDialog()
{
this.EnsureInitialized();
return base.ShowDialog();
}
#endregion
private void DeclareKeyBindings(TrackerViewModel vm)
{
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.RefreshCommand, new KeyGesture(Key.F5)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.InsertProductCommand, new KeyGesture(Key.Insert)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.EditProductCommand, new KeyGesture(Key.Enter)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.DeleteProductCommand, new KeyGesture(Key.Delete)));
}
private void EnsureInitialized()
{
if (this.initialized)
{
return;
}
var vm = this.vmFactory.Create(this);
this.WpfWindow.DataContext = vm;
this.DeclareKeyBindings(vm);
this.initialized = true;
}
}
The Composition Root
And finally, you need a way to create the object graph. You are doing that in the correct place, but you are not doing yourself any favors by breaking it into many steps. And putting the container as an application-level variable is not necessarily a good thing - it opens up the container for abuse as a service locator.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Begin Composition Root
var container = new StandardKernel();
// Register types
container.Bind<ISystemEvents>().To<MySystemEvents>();
container.Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
container.Bind<Window>().To<MainWindow>();
container.Bind<IWindow>().To<MainWindowAdapter>();
// Build the application object graph
var window = container.Get<IWindow>();
// Show the main window.
window.Show();
// End Composition Root
}
}
I think the main issue you are having is that you need to ensure to call Show() on the MainWindow manually.
If you really do want to break the registration out into another step, you can do so by using one or more Ninject Modules.
using Ninject.Modules;
using System.Windows;
public class MyApplicationModule : NinjectModule
{
public override void Load()
{
Bind<ISystemEvents>().To<MySystemEvents>();
Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
Bind<Window>().To<MainWindow>();
Bind<IWindow>().To<MainWindowAdapter>();
}
}
And then the App.xaml.cs file will look like this:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Begin Composition Root
new StandardKernel(new MyApplicationModule()).Get<IWindow>().Show();
// End Composition Root
}
}
The trackerviewmodel will be instantiated by the auto-generated xaml designer code, not by ninject.
I've never used ninject, but I think you need to configure the container to know about your viewModel, and then inject the viewmodel for Ninject to resolve it and it's dependencies:
public class MainWindow : Window
{
[Inject]
public TrackerViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = ViewModel;
}
}
I'm having a serious headache with this problem. I really dislike store apps but am forced to use it in this case. I've only worked with XAML for a few weeks.
My question is:
How can I call a RelayCommand in my ViewModel (from my View of course) that will change the page on my view? And even better, change it using URI, so that I can pass a command parameter to file.
I'm totally lost on this. Currently I'm using this.Frame.Navigate(type type) in the View Code behind to navigate through pages.
I would really and I mean REALLY appreciate a description from a to z on what to do in this case.
I presume i could do something like building a framecontainer on my View and send it to my ViewModel and from there navigate the current frame to another. But I'm not sure how that works in Store apps.
I am really sorry for the lack of good questions, but I'm on a deadline and i need to get my View connected to my ViewModel in a proper way.. I don't like having both view codebehind as well as ViewModel code.
There are 2 ways to do this, a simple way is to pass a relay command action from the view to the view model.
public MainPage()
{
var vm = new MyViewModel();
vm.GotoPage2Command = new RelayCommand(()=>{ Frame.Navigate(typeof(Page2)) });
this.DataContext = vm;
}
<Button Command={Binding GoToPage2Command}>Go to Page 2</Button>
Another way is by using an IocContainer and DependencyInjection. This one is a more losely coupled approach.
We will need an interface for navigation page so that we don't need to reference or Know anything about PageX or any UI element assuming that your viewmodel is in a separate project that doesn't know anything about the UI.
ViewModel Project:
public interface INavigationPage
{
Type PageType { get; set; }
}
public interface INavigationService
{
void Navigate(INavigationPage page) { get; set; }
}
public class MyViewModel : ViewModelBase
{
public MyViewModel(INavigationService navigationService, INavigationPage page)
{
GotoPage2Command = new RelayCommand(() => { navigationService.Navigate(page.PageType); })
}
private ICommand GotoPage2Command { get; private set; }
}
UI Project:
public class NavigationService : INavigationService
{
//Assuming that you only navigate in the root frame
Frame navigationFrame = Window.Current.Content as Frame;
public void Navigate(INavigationPage page)
{
navigationFrame.Navigate(page.PageType);
}
}
public abstract class NavigationPage<T> : INavigationPage
{
public NavigationPage()
{
this.PageType = typeof(T);
}
}
public class NavigationPage1 : NavigationPage<Page1> { }
public class MainPage : Page
{
public MainPage()
{
//I'll just place the container logic here, but you can place it in a bootstrapper or in app.xaml.cs if you want.
var container = new UnityContainer();
container.RegisterType<INavigationPage, NavigationPage1>();
container.RegisterType<INavigationService, NavigationService>();
container.RegisterType<MyViewModel>();
this.DataContext = container.Resolve<MyViewModel>();
}
}
As Scott says you could use a NavigationService.
I would firstly create an interface this is not needed in this example but will be useful if you use Dependency Injection (good solution with viewmodels and services) in the future :)
INavigationService:
public interface INavigationService
{
void Navigate(Type sourcePage);
void Navigate(Type sourcePage, object parameter);
void GoBack();
}
NavigationService.cs will inherit INavigationService
you will need the following namespaces
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
public sealed class NavigationService : INavigationService
{
public void Navigate(Type sourcePage)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage);
}
public void Navigate(Type sourcePage, object parameter)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage, parameter);
}
public void GoBack()
{
var frame = (Frame)Window.Current.Content;
frame.GoBack();
}
}
Simple ViewModel to show RelayCommand example. NB I Navigate to another Page (Page2.xaml) using the DoSomething RelayCommand.
MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
private INavigationService _navigationService;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
private ICommand _doSomething;
public ICommand DoSomething
{
get
{
return _doSomething ??
new RelayCommand(() =>
{
_navigationService.Navigate(typeof(Page2));
});
}
}}
In simple example Ive created the viewmodel in MainPage.cs and added the NavigationService
but you can do this elsewhere depending on what your MVVM setup is like.
MainPage.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var vm = new MyViewModel(new NavigationService());
this.DataContext = vm;
}
}
MainPage.xaml (binds to the command DoSomething)
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Width="200" Height="50" Content="Go to Page 2"
Command="{Binding DoSomething}"/>
</Grid>
Hope that helps.
I don't really like when a ViewModel references Views to navigate to. So I prefer to a ViewModel-first approach. By using ContentControls, DataTemplates for ViewModel types & some kind of navigation pattern in my ViewModels.
My navigation looks like this:
[ImplementPropertyChanged]
public class MainNavigatableViewModel : NavigatableViewModel
{
public ICommand LoadProfileCommand { get; private set; }
public ICommand OpenPostCommand { get; private set; }
public MainNavigatableViewModel ()
{
LoadProfileCommand = new RelayCommand(() => Navigator.Navigate(new ProfileNavigatableViewModel()));
OpenPostCommand = new RelayCommand(() => Navigator.Navigate(new PostEditViewModel { Post = SelectedPost }), () => SelectedPost != null);
}
}
My NavigatableViewModel looks like:
[ImplementPropertyChanged]
public class NavigatableViewModel
{
public NavigatorViewModel Navigator { get; set; }
public NavigatableViewModel PreviousViewModel { get; set; }
public NavigatableViewModel NextViewModel { get; set; }
}
And my Navigator:
[ImplementPropertyChanged]
public class NavigatorViewModel
{
public NavigatableViewModel CurrentViewModel { get; set; }
public ICommand BackCommand { get; private set; }
public ICommand ForwardCommand { get; private set; }
public NavigatorViewModel()
{
BackCommand = new RelayCommand(() =>
{
// Set current control to previous control
CurrentViewModel = CurrentViewModel.PreviousViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.PreviousViewModel != null);
ForwardCommand = new RelayCommand(() =>
{
// Set current control to next control
CurrentViewModel = CurrentViewModel.NextViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.NextViewModel != null);
}
public void Navigate(NavigatableViewModel newViewModel)
{
if (newViewModel.Navigator != null && newViewModel.Navigator != this)
throw new Exception("Viewmodel can't be added to two different navigators");
newViewModel.Navigator = this;
if (CurrentViewModel != null)
{
CurrentViewModel.NextViewModel = newViewModel;
}
newViewModel.PreviousViewModel = CurrentViewModel;
CurrentViewModel = newViewModel;
}
}
My MainWindows.xaml:
<Window
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:viewmodels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Windows.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="389" Width="573"
d:DataContext="{d:DesignInstance {x:Type viewmodels:MyAppViewModel}, IsDesignTimeCreatable=True}">
<Grid>
<!-- Show data according to data templates as defined in App.xaml -->
<ContentControl Content="{Binding Navigator.CurrentViewModel}" Margin="0,32,0,0" />
<Button Content="Previous" Command="{Binding Navigator.BackCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Width="75" />
<Button Content="Next" Command="{Binding Navigator.ForwardCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="90,5,0,0" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
App.xaml.cs:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow {DataContext = new MyAppViewModel()}.Show();
}
}
MyAppViewModel:
[ImplementPropertyChanged]
public class MyAppViewModel
{
public NavigatorViewModel Navigator { get; set; }
public MyAppViewModel()
{
Navigator = new NavigatorViewModel();
Navigator.Navigate(new MainNavigatableViewModel());
}
}
App.xaml:
<DataTemplate DataType="{x:Type viewmodels:MainNavigatableViewModel}">
<controls:MainControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:PostEditViewModel}">
<controls:PostEditControl/>
</DataTemplate>
The downside is that you have more ViewModel-code which manages the state of what you are looking at. But obviously that is also a huge advantage in terms of Testability. And of course your ViewModels do not need to depend on your Views.
Plus I use Fody/PropertyChanged, that's what the [ImplementPropertyChanged] is about. Keeps me from writing OnPropertyChanged code.
Here is another way to implement the NavigationService, without using an abstract class and without referencing view types in your view model.
Assuming that the view model of the destination page is something like this:
public interface IDestinationViewModel { /* Interface of destination vm here */ }
class MyDestinationViewModel : IDestinationViewModel { /* Implementation of vm here */ }
Then your NavigationService can simply implement the following interface:
public interface IPageNavigationService
{
void NavigateToDestinationPage(IDestinationViewModel dataContext);
}
In your main window ViewModel you need to inject the navigator and the view model of the destination page:
class MyViewModel1 : IMyViewModel
{
public MyViewModel1(IPageNavigationService navigator, IDestinationViewModel destination)
{
GoToPageCommand = new RelayCommand(() =>
navigator.NavigateToDestinationPage(destination));
}
public ICommand GoToPageCommand { get; }
}
The implementation of the NavigationService encapsulates the view type (Page2) and the reference to the frame which is injected through the constructor:
class PageNavigationService : IPageNavigationService
{
private readonly Frame _navigationFrame;
public PageNavigationService(Frame navigationFrame)
{
_navigationFrame = navigationFrame;
}
void Navigate(Type type, object dataContext)
{
_navigationFrame.Navigate(type);
_navigationFrame.DataContext = dataContext;
}
public void NavigateToDestinationPage(IDestinationViewModel dataContext)
{
// Page2 is the corresponding view of the destination view model
Navigate(typeof(Page2), dataContext);
}
}
To get the frame simply name it in your MainPage xaml:
<Frame x:Name="RootFrame"/>
In the code behind of the MainPage initialize your bootstrapper by passing the root frame:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var bootstrapper = new Bootstrapper(RootFrame);
DataContext = bootstrapper.GetMainScreenViewModel();
}
}
Finally here is the bootstrapper implementation for completeness ;)
class Bootstrapper
{
private Container _container = new Container();
public Bootstrapper(Frame frame)
{
_container.RegisterSingleton(frame);
_container.RegisterSingleton<IPageNavigationService, PageNavigationService>();
_container.Register<IMyViewModel, MyViewModel1>();
_container.Register<IDestinationViewModel, IDestinationViewModel>();
#if DEBUG
_container.Verify();
#endif
}
public IMyViewModel GetMainScreenViewModel()
{
return _container.GetInstance<IMyViewModel>();
}
}
This simply bothers me that no one has solved this at the architectural level. So this is the code for complete decoupling the views, viewmodels and the mapping between them with using the built-in Frame based navigation. The implementation uses Autofact as DI container, but can be easily ported to other IoC solutions.
Core VM logic (these should be in the same assembly):
// I would not get into how the ViewModel or property change notification is implemented
public abstract class PageViewModel : ViewModel
{
protected internal INavigationService Navigation { get; internal set; }
internal void NavigationCompleted()
{
OnNavigationCompleted();
}
protected virtual void OnNavigationCompleted()
{
}
}
public interface INavigationService
{
void Navigate<TModel>() where TModel : PageViewModel;
}
public abstract class NavigationServiceBase : INavigationService
{
public abstract void Navigate<TModel>() where TModel : PageViewModel;
protected void CompleteNavigation(PageViewModel model)
{
model.Navigation = this;
model.NavigationCompleted();
}
}
This code should be in a UWP class library or executable:
public interface INavigationMap<TModel>
where TModel: PageViewModel
{
Type ViewType { get; }
}
internal class NavigationMap<TModel, TView> : INavigationMap<TModel>
where TModel: PageViewModel
where TView: Page
{
public Type ViewType => typeof(TView);
}
public class NavigationService : NavigationServiceBase
{
private readonly Frame NavigationFrame;
private readonly ILifetimeScope Resolver;
public NavigationService(ILifetimeScope scope)
{
Resolver = scope;
NavigationFrame = Window.Current.Content as Frame;
NavigationFrame.Navigated += NavigationFrame_Navigated;
}
private void NavigationFrame_Navigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
if(e.Content is FrameworkElement element)
{
element.DataContext = e.Parameter;
if(e.Parameter is PageViewModel page)
{
CompleteNavigation(page);
}
}
}
public override void Navigate<TModel>()
{
var model = Resolver.Resolve<TModel>();
var map = Resolver.Resolve<INavigationMap<TModel>>();
NavigationFrame.Navigate(map.ViewType, model);
}
}
The rest is just convenience code for registering in the DI and usage examples:
public static class NavigationMap
{
public static void RegisterNavigation<TModel, TView>(this ContainerBuilder builder)
where TModel : PageViewModel
where TView : Page
{
builder.RegisterInstance(new NavigationMap<TModel, TView>())
.As<INavigationMap<TModel>>()
.SingleInstance();
}
}
builder.RegisterNavigation<MyViewModel, MyView>();
public class UserAuthenticationModel : PageViewModel
{
protected override void OnNavigationCompleted()
{
// UI is visible and ready
// navigate to somewhere else
Navigation.Navigate<MyNextViewModel>();
}
}
I am working on a windows phone 8.1 universal app and want to find the best way of handling page navigations without having large amounts of logic in the code behind. I want to keep the code behind in my View as uncluttered as possible. What is the accepted MVVM way of navigating to a new page in response to a button click?
I currently have to send a RelayComnmand message from the ViewModel to the view with the details of the page to navigate to. This means that the code behind has to be wired up as follows:
public MainPage()
{
InitializeComponent();
Messenger.Default.Register<OpenArticleMessage>(this, (article) => ReceiveOpenArticleMessage(article));
...
}
private object ReceiveOpenArticleMessage(OpenArticleMessage article)
{
Frame.Navigate(typeof(ArticleView));
}
This just doesn't seem the best way although it does work. How can I do the page navigations directly from the ViewModel? I am using MVVM-Light in my project.
Ok, I have found an answer to this question. Took a bit of investigation but I eventually found the preferred MVVM-Light way of doing this. I don't take credit for this answer in anyway but just posting it here in case people are looking for an answer to this question.
Create an INavigationService interface as follows:
public interface INavigationService
{
void Navigate(Type sourcePageType);
void Navigate(Type sourcePageType, object parameter);
void GoBack();
}
Create a NavigationService class as follows:
public class NavigationService : INavigationService
{
public void Navigate(Type sourcePageType)
{
((Frame)Window.Current.Content).Navigate(sourcePageType);
}
public void Navigate(Type sourcePageType, object parameter)
{
((Frame)Window.Current.Content).Navigate(sourcePageType, parameter);
}
public void GoBack()
{
((Frame)Window.Current.Content).GoBack();
}
}
Now in the ViewModelLocator, set it up like this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<INavigationService, Design.DesignNavigationService>();
}
else
{
SimpleIoc.Default.Register<INavigationService>(() => new NavigationService());
}
SimpleIoc.Default.Register<MainViewModel>();
}
Next setup a navigation service for design time as follows:
public class DesignNavigationService : INavigationService
{
// This class doesn't perform navigation, in order
// to avoid issues in the designer at design time.
public void Navigate(Type sourcePageType)
{
}
public void Navigate(Type sourcePageType, object parameter)
{
}
public void GoBack()
{
}
}
My MainViewModel constructor is as follows:
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
...
Now you can simply use this to navigate in your viewmodel:
_navigationService.Navigate(typeof(WelcomeView));
For more details on the original author Laurent Bugnion see this article and associated code.
http://msdn.microsoft.com/en-us/magazine/jj651572.aspx
There is a new and simpler implementation here: https://marcominerva.wordpress.com/2014/10/10/navigationservice-in-mvvm-light-v5/
First we create the NavigationService and DialogService (for the page navigation params):
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));
// navigationService.Configure("key2", typeof(OtherPage2));
return navigationService;
}
Then we create a RelayCommand and NavigationService in your ViewModel, like so:
public class MainViewModel : ViewModelBase {
private INavigationService _navigationService;
public RelayCommand<Tuple<string, string>> DetailsCommand { get; set; }
public MainViewModel(INavigationService navigationService) {
this._navigationService = navigationService;
DetailsCommand = new RelayCommand<Tuple<string, string>>((args) => NavigateTo(args));
}
public void NavigateTo(Tuple<string, string> args) {
this._navigationService.NavigateTo(args.Item1, args.Item1);
}
public void ClickAndNavigate() {
NavigateTo(new Tuple<string, string>("AdminPivotPage", "Test Params"));
}
}
And finally, we can get the page navigation params like so:
public sealed partial class DetailsPage : Page {
// ...
protected override void OnNavigatedTo(NavigationEventArgs e) {
var parameter = e.Parameter as string; // "My data"
base.OnNavigatedTo(e);
}
}
But to read the arguments passed in page navigation in MVVM pattern, you can take a look here.
I agree with ricochete above, its simpler, though my direct implimentation messed up with my Design Data Binding in Blend.
I decided to create an a class that inherited from the NavigationService
public class NavigationServiceHelper : NavigationService
{
public NavigationServiceHelper()
{
this.Configure("Page1", typeof(View.Page1));
this.Configure("Page2", typeof(View.Page2));
}
}
Then in the ViewModelLocator I registerred it this way
SimpleIoc.Default.Register<INavigationService, NavigationServiceHelper>();
My Design View Data Bindings worked again. If someone could explain why the design data wun't work in ricochete above, please do. Thank you!