EDIT: Here is the code repo: https://github.com/AntwanReno/navi
I did a project in WPF MVVM. Just a single Window
with single Frame
control and few Pages. My problem was communication between pages without not violating MVVM principles. I'd like to use all best-programming-practices.
Can you check if this code agrees with the all best principles
My solution has two projects: WPF client, ViewModels PCL. I want to have my ViewModels separated from the Views. Here is the code for WPF Client:
App.xaml.cs
namespace NaviWPFApp
{
using System.Windows;
using NaviWPFApp.Views;
using NaviWPFApp.Views.Pages;
public partial class App : Application
{
public static NavigationService Navigation;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
Navigation = new NavigationService(mainWindow.MyFrame);
Navigation.Navigate<FirstPage>();
}
}
}
App.xaml It's just:
<Application x:Class="NaviWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NaviWPFApp"
x:Name="Application">
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
I Have one main window with frame, and two very similar pages (no code-behind):
<Window x:Class="NaviWPFApp.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"
mc:Ignorable="d"
Title="NaviWPFApp" Height="300" Width="300">
<Grid>
<Frame x:Name="MyFrame" Margin="10" />
</Grid>
</Window>
<Page x:Class="NaviWPFApp.Views.Pages.FirstPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Title="FirstPage"
DataContext="{Binding FirstPageViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
<Button Command="{Binding Go2}" Height="30" Content="Go to second page" />
</Grid>
</Page>
<Page x:Class="NaviWPFApp.Views.Pages.SecondPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SecondPage"
DataContext="{Binding Path=SecondPageViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
<Button Command="{Binding Go1}" Content="Go back to page 1" Height="30" />
</Grid>
</Page>
I my client I have also two additional classes ViewModelLocator and NavigationService - used for naviation between pages:
namespace NaviWPFApp
{
using NaviWPFApp.ViewModels.Pages;
public class ViewModelLocator
{
public FirstPageViewModel FirstPageViewModel => new FirstPageViewModel(App.Navigation);
public SecondPageViewModel SecondPageViewModel => new SecondPageViewModel(App.Navigation);
}
}
namespace NaviWPFApp
{
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using NaviWPFApp.ViewModels.Common;
public class NavigationService : INavigationService
{
readonly Frame frame;
public NavigationService(Frame frame)
{
this.frame = frame;
}
public void GoBack()
{
frame.GoBack();
}
public void GoForward()
{
frame.GoForward();
}
public bool Navigate(string page)
{
var type = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(a => a.Name.Equals(page));
if (type == null) return false;
var src = Activator.CreateInstance(type);
return frame.Navigate(src);
}
public bool Navigate<T>(object parameter = null)
{
var type = typeof(T);
return Navigate(type, parameter);
}
public bool Navigate(Type source, object parameter = null)
{
var src = Activator.CreateInstance(source);
return frame.Navigate(src, parameter);
}
}
}
Here is my ViewModels (Portable) project:
It's only two ViewModel classes for each Page in UI, INavigationService (I don't want to know anything about NavigationService implementation and UI client), MyObservableObject and MyCommand.
MyObservableObject
and MyCommand
are typical implementations of INotifyPropertyChanged
and ICommand
interfaces.
So this is an interface and two viewmodels:
public interface INavigationService
{
void GoForward();
void GoBack();
bool Navigate(string page);
}
public class FirstPageViewModel : MyObservableObject
{
private readonly INavigationService navigationService;
public FirstPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public MyCommand Go2
{
get { return new MyCommand(x => navigationService.Navigate("SecondPage")); }
}
}
public class SecondPageViewModel : MyObservableObject
{
private readonly INavigationService navigationService;
public SecondPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public MyCommand Go1
{
get { return new MyCommand(x => navigationService.Navigate("FirstPage")); }
}
}
My biggest concern is this: navigationService.Navigate("FirstPage"));
I pass view name as a string. That's because I don't want my ViewModel knows anything about View. But my navigation service HAS TO KNOW about View. That's why I did that interface with string parameter.
What do you think?