UPDATE: There is an improved version of this technique, one which does not need to create Views, which can be found at the following link:
https://www.technical-recipes.com/2018/navigating-between-views-in-wpf-mvvm/
This technique has been already discussed on a number of blog / website forums including the following:
https://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/
http://stackoverflow.com/questions/19654295/wpf-mvvm-navigate-views
http://stackoverflow.com/questions/10993385/changing-view-on-buttonclick
I thought it would be useful to share an implemention of a working version of this technique.
The complete Visual Studio project can be downloaded from here:
https://www.technical-recipes.com/Downloads/MvvmSwitchViews.zip
To summarise, your application should at least implement the following:
A ViewModel that contains a property which defines your current view, so that to change the view you switch your ViewModel’s properties.
The ViewModel needs to implement INotifyPropertyChanged otherwise the view won’t be notified when a property changes.
A ContentControl whose content to the is bound to the current view.
Some DataTemplates for each of the views you wish to switch between.
To get started create a new WPF project in Visual Studio:
And in our project create 2 x new WPF User Controls, View1.xaml and View2.xaml:
View1.xaml
<UserControl x:Class="MvvmSwitchViews.View1"
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">
<Grid>
<Button
Content="Goto View 2"
Command="{Binding GotoView2Command}"
HorizontalAlignment="Center"
Margin="10,10,0,0"
VerticalAlignment="Center"
Width="75">
</Button>
</Grid>
</UserControl>
View2.xaml
<UserControl x:Class="MvvmSwitchViews.View2"
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">
<Grid>
<Button
Content="Goto View 1"
Command="{Binding GotoView1Command}"
HorizontalAlignment="Center"
Margin="10,10,0,0"
VerticalAlignment="Center"
Width="75">
</Button>
</Grid>
</UserControl>
Create ViewModels for each of the Views: View1ViewModel and View2ViewModel. These will just be empty classes for our minimalist implementation:
View1ViewModel.cs
namespace MvvmSwitchViews
{
public class View1ViewModel
{
}
}
View2ViewModel.cs
namespace MvvmSwitchViews
{
public class View2ViewModel
{
}
}
Modify the MainWindow.xaml to include the DataTemplate and CurrentView bindings:
MainWindow.xaml
<Window x:Class="MvvmSwitchViews.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmSwitchViews"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:View1ViewModel}">
<local:View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:View2ViewModel}">
<local:View2/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
</Window>
We then create a new ViewModel for the MainWindow.xaml, called MainWindowViewModel:
MainWindowViewModel.cs
using System.Windows.Input;
namespace MvvmSwitchViews
{
public class MainWindowViewModel : ViewModelBase
{
private ICommand _gotoView1Command;
private ICommand _gotoView2Command;
private object _currentView;
private object _view1;
private object _view2;
public MainWindowViewModel()
{
_view1 = new View1();
_view2 = new View2();
CurrentView = _view2;
}
public object GotoView1Command
{
get
{
return _gotoView1Command ?? (_gotoView1Command = new RelayCommand(
x =>
{
GotoView1();
}));
}
}
public ICommand GotoView2Command
{
get
{
return _gotoView2Command ?? (_gotoView2Command = new RelayCommand(
x =>
{
GotoView2();
}));
}
}
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
private void GotoView1()
{
CurrentView = _view1;
}
private void GotoView2()
{
CurrentView = _view2;
}
}
}
We also need to implement INotifyPropertyChanged or create a class that implements INotifyPropertyChanged. For this we create a new class called ViewModelBase:
ViewModelBase.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace MvvmSwitchViews
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Create three more classes to implement RelayCommand, EventArgs and EventRaiser for our event handling:
RelayCommand.cs
using System;
using System.Windows.Input;
namespace MvvmSwitchViews
{
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T) parameter);
}
public void Execute(object parameter)
{
_execute((T) parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
// Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
// associated views should be enabled whenever a command is invoked
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
private event EventHandler CanExecuteChangedInternal;
public void RaiseCanExecuteChanged()
{
CanExecuteChangedInternal.Raise(this);
}
}
}
EventArgs.cs
using System;
namespace MvvmSwitchViews
{
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}
EventRaiser.cs
using System;
namespace MvvmSwitchViews
{
public static class EventRaiser
{
public static void Raise(this EventHandler handler, object sender)
{
if (handler != null)
{
handler(sender, EventArgs.Empty);
}
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
{
if (handler != null)
{
handler(sender, new EventArgs<T>(value));
}
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
{
if (handler != null)
{
handler(sender, value);
}
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
{
if (handler != null)
{
handler(sender, value);
}
}
}
}
We can now run the application to demonstrate how the switch of views can be achieved by the pressing of the buttons:
And switching to the next view like so: