Following a lot of research and mainly based on this tutorial I came up with the following structure in my application:
ObjectBase - all view models inherit it:
public abstract class ObjectBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void SetProperty<T>(ref T member, T val,
[CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged
public abstract ObjectBase BackLocation { get; }
public abstract event Action<ObjectBase> NavigateTo;
public abstract string ViewHeader { get; }
}
Main Content ViewModel:
public class MainContentViewModel : ObjectBase
{
private ObjectBase _selectedView;
public ObjectBase SelectedView
{
get { return _selectedView; }
set
{
if (SelectedView != null)
{
SelectedView.NavigateTo -= SelectedView_NavigateTo;
}
if (SelectedView is IDisposable)
{
(SelectedView as IDisposable).Dispose();
}
SetProperty(ref _selectedView, value);
SelectedView.NavigateTo += SelectedView_NavigateTo;
}
}
public MainContentViewModel()
{
SelectedView = new ViewModels.FirstViewModel();
}
private void SelectedView_NavigateTo(ObjectBase viewToNavigateTo)
{
SelectedView = viewToNavigateTo;
}
#region Back Command
private CommandBase _backCommand;
public CommandBase BackCommand
{
get { return _backCommand ?? (_backCommand = new CommandBase(Back)); }
}
private void Back(object obj)
{
if (SelectedView.BackLocation != null)
{
SelectedView = SelectedView.BackLocation;
}
else
{
Application.Current.MainWindow.Close();
}
}
#endregion Back Command
#region ObjectBase Fields
public override event Action<ObjectBase> NavigateTo;
public override string ViewHeader { get { return "Main View"; } }
public override ObjectBase BackLocation { get { return null; } }
#endregion
}
Main Content View:
<Window x:Class="WpfNavigationTest.Views.MainContentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:WpfNavigationTest.Views"
xmlns:viewModels="clr-namespace:WpfNavigationTest.ViewModels">
<Window.DataContext>
<viewModels:MainContentViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstViewModel}">
<views:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Go Back" Command="{Binding BackCommand}"/>
<Separator Margin="0,15" Grid.Row="1"/>
<ContentPresenter Content="{Binding SelectedView}" Grid.Row="2"/>
</Grid>
Sample View Model 1:
public class FirstViewModel : ObjectBase
{
#region GoForward Command
private CommandBase _goForwardCommand;
public CommandBase GoForwardCommand
{
get { return _goForwardCommand ?? (_goForwardCommand = new CommandBase(GoForward)); }
}
private void GoForward(object obj)
{
NavigateTo(new ViewModels.SecondViewModel(DateTime.Now.ToString()));
}
#endregion
public override ObjectBase BackLocation { get { return null; } }
public override string ViewHeader { get { return "First View"; } }
public override event Action<ObjectBase> NavigateTo;
}
Sample View Model 2:
public class SecondViewModel : ObjectBase
{
private string _addMessage;
public SecondViewModel(string addedMessage)
{
_addMessage = addedMessage;
}
public override ObjectBase BackLocation { get { return new ViewModels.FirstViewModel(); } }
public override string ViewHeader { get { return $"Second View. Message: {_addMessage}"; } }
public override event Action<ObjectBase> NavigateTo;
}
In general, this works as expected but I would like to know if I'm at the right direction and how can I improve this?