Switching between WPF XAML views using MVVM DataTemplate

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: MvvmSwitch1 And in our project create 2 x new WPF User Controls, View1.xaml and View2.xaml: MvvmSwitch2 View1.xaml [code language="xml"] <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> [/code] View2.xaml [code language="xml"] <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> [/code] Create ViewModels for each of the Views: View1ViewModel and View2ViewModel. These will just be empty classes for our minimalist implementation: MvvmSwitch3 View1ViewModel.cs [code language="csharp"] namespace MvvmSwitchViews { public class View1ViewModel { } } [/code] View2ViewModel.cs [code language="csharp"] namespace MvvmSwitchViews { public class View2ViewModel { } } [/code] Modify the MainWindow.xaml to include the DataTemplate and CurrentView bindings: MainWindow.xaml [code language="xml"] <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> [/code] We then create a new ViewModel for the MainWindow.xaml, called MainWindowViewModel: MainWindowViewModel.cs [code language="csharp"] 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; } } } [/code] We also need to implement INotifyPropertyChanged or create a class that implements INotifyPropertyChanged. For this we create a new class called ViewModelBase: MvvmSwitch4 ViewModelBase.cs [code language="csharp"] 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)); } } } } [/code] Create three more classes to implement RelayCommand, EventArgs and EventRaiser for our event handling: RelayCommand.cs [code language="csharp"] 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); } } } [/code] EventArgs.cs [code language="csharp"] using System; namespace MvvmSwitchViews { public class EventArgs<T> : EventArgs { public EventArgs(T value) { Value = value; } public T Value { get; private set; } } } [/code] EventRaiser.cs [code language="csharp"] 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); } } } } [/code] We can now run the application to demonstrate how the switch of views can be achieved by the pressing of the buttons: MvvmSwitch5 And switching to the next view like so: MvvmSwitch6

Comments

Popular posts from this blog

Using the Supervisor Controller Pattern to access View controls in MVVM

Getting started with Tesseract optical character recognition (OCR) library in Visual Studio

How to send an e-mail via Google SMTP using C#