C# WPF Navigation Between Pages (Views)
Using MVVM is absolutely fine as it will simplify the implementation of your requirement. WPF is build to be used with the MVVM pattern, which means to make heavy use of data binding and data templates.
The task is quite simple. Create a UserControl
(or DataTemplate
) for each view e.g., WelcomePage
and LoginPage
with their corresponding view models WelcomePageViewModel
and LoginPageViewModel
.
A ContentControl
will display the pages.
The main trick is that, when using an implicit DataTemplate
(a template resource without an x:Key
defined), the XAML parser will automatically lookup and apply the correct template, where the DataType
matches the current content type of a ContentControl
. This makes navigation very simple, as you just have to select the current page from a collection of page models and set this page via data binding to the Content
property of the ContentControl
or ContentPresenter
:
Usage
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type WelcomePageviewModel}">
<WelcomPage />
</DataTemplate>
<DataTemplate DataType="{x:Type LoginPageviewModel}">
<LoginPage />
</DataTemplate>
</Window.Resources>
<StackPanel>
<!-- Page navigation -->
<StackPanel Orientation="Horizontal">
<Button Content="Show Login Screen"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.LoginPage}" />
<Button Content="Show Welcome Screen"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.WelcomePage}" />
</StackPanel>
<!--
Host of SelectedPage.
Automatically displays the DataTemplate that matches the current data type
-->
<ContentControl Content="{Binding SelectedPage}" />
<StackPanel>
</Window>
Implementation
- Create the individual page controls (the controls that host the page content). This can be a
Control
,UserControl
,Page
or simply a plainDataTemplate
:
WelcomePage.xaml
<UserControl>
<StackPanel>
<TextBlock Text="{Binding PageTitle}" />
<TextBlock Text="{Binding Message}" />
</StackPanel>
</UserControl>
LoginPage.xaml
<UserControl>
<StackPanel>
<TextBlock Text="{Binding PageTitle}" />
<TextBox Text="{Binding UserName}" />
</StackPanel>
</UserControl>
- Create the page models:
IPage.cs
interface IPage : INotifyPropertyChanged
{
string PageTitel { get; set; }
}
WelcomePageViewModel.cs
class WelcomePageViewModel : IPage
{
private string pageTitle;
public string PageTitle
{
get => this.pageTitle;
set
{
this.pageTitle = value;
OnPropertyChanged();
}
}
private string message;
public string Message
{
get => this.message;
set
{
this.message = value;
OnPropertyChanged();
}
}
public WelcomePageViewModel()
{
this.PageTitle = "Welcome";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
LoginPageViewModel.cs
class LoginPageViewModel : IPage
{
private string pageTitle;
public string PageTitle
{
get => this.pageTitle;
set
{
this.pageTitle = value;
OnPropertyChanged();
}
}
private string userName;
public string UserName
{
get => this.userName;
set
{
this.userName = value;
OnPropertyChanged();
}
}
public LoginPageViewModel()
{
this.PageTitle = "Login";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
- Create an enumeration of page identifiers (to eliminate magic strings in XAML and C#):
PageName.cs
public enum PageName
{
Undefined = 0, WelcomePage, LoginPage
}
- Create the
MainViewModel
which will manage the pages and their navigation:
MainViewModel.cs
An implementation of RelayCommand
can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class MainViewModel
{
public ICommand SelectPageCommand => new RelayCommand(SelectPage);
private Dictionary<PageName, IPage> Pages { get; }
private IPage selectedPage;
public IPage SelectedPage
{
get => this.selectedPage;
set
{
this.selectedPage = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
this.Pages = new Dictionary<PageName, IPage>
{
{ PageName.WelcomePage, new WelcomePageViewModel() },
{ PageName.LoginPage, new LoginPageViewModel() }
};
this.SelectedPage = this.Pages.First().Value;
}
public void SelectPage(object param)
{
if (param is PageName pageName
&& this.Pages.TryGetValue(pageName, out IPage selectedPage))
{
this.SelectedPage = selectedPage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Navigating between pages in WPF
The idea behind it is very simple. The data is not being stored in the page since when you navigate to the page, a new instance of the page is being created.
You can do so by maintaining a static instance of the page.
ie the page is created only the first time the application is run. The next time you load the page, the state will be maintained.
How do I toggle between pages in a WPF application?
The easiest and most lightweight way opposed to using a Frame
, is to create a view model for each page. Then create a main view model which holds all pages and manages their selection. A ContentControl
will display the view models using a DataTemplate
assigned to the ContentControl.ContentTemplate
property or in a multi page scenario either a DataTemplateSelector
assigned to ContentControl.ContentTemplateSelector
or implicit templates by only defining the DataTemplate.DataType
without the Key
attribute:
The View
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel x:Key="MainViewModel" />
</Window.DataContext>
<Window.Resources>
<!--
The templates for the view of each page model.
Can be moved to dedicated files.
-->
<DataTemplate DataType="{x:Type LoginViewModel}">
<Border Background="Coral">
<!-- UserControl -->
<local:LoginView />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type PageOneViewModel}">
<Border Background="Red">
<local:PageOne />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type PageTwoViewModel}">
<Border Background="DeepSkyBlue">
<TextBox Text="{Binding PageTitle}" />
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Load Login Page"
Command="{Binding SelectPageFromIndexCommand}"
CommandParameter="0" />
<Button Content="Load Page One"
Command="{Binding SelectPageFromIndexCommand}"
CommandParameter="1" />
<Button Content="Load Next Page"
Command="{Binding SelectNextPageCommand}" />
<!-- The actual page control -->
<ContentControl Content="{Binding SelectedPage}" />
</StackPanel>
</Window>
The View Model
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.Pages = new ObservableCollection<IPageViewModel>()
{
new LoginViewModel(),
new PageOneViewModel(),
new PageTwoViewModel()
};
// Show startup page
this.SelectedPage = this.Pages.First();
}
// Define the Execute and CanExecute delegates for the command
// and pass them to the constructor
public ICommand SelectPageFromIndexCommand => new SelectPageCommand(
param => this.SelectedPage = this.Pages.ElementAt(int.Parse(param as string)),
param => int.TryParse(param as string, out int index));
// Define the Execute and CanExecute delegates for the command
// and pass them to the constructor
public ICommand SelectNextPageCommand => new SelectPageCommand(
param => this.SelectedPage = this.Pages.ElementAt(this.Pages.IndexOf(this.SelectedPage) + 1),
param => this.Pages.IndexOf(this.SelectedPage) + 1 < this.Pages.Count);
private IPageViewModel selectedPage;
public IPageViewModel SelectedPage
{
get => this.selectedPage;
set
{
if (object.Equals(value, this.selectedPage))
{
return;
}
this.selectedPage = value;
OnPropertyChanged();
}
}
private ObservableCollection<IPageViewModel> pages;
public ObservableCollection<IPageViewModel> Pages
{
get => this.pages;
set
{
if (object.Equals(value, this.pages))
{
return;
}
this.pages = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
SelectPageCommand.cs
class SelectPageCommand : ICommand
{
public SelectPageCommand(Action<object> executeDelegate, Predicate<object> canExecuteDelegate)
{
this.ExecuteDelegate = executeDelegate;
this.CanExecuteDelegate = canExecuteDelegate;
}
private Predicate<object> CanExecuteDelegate { get; }
private Action<object> ExecuteDelegate { get; }
#region Implementation of ICommand
public bool CanExecute(object parameter) => this.CanExecuteDelegate?.Invoke(parameter) ?? false;
public void Execute(object parameter) => this.ExecuteDelegate?.Invoke(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
#endregion
}
The Page Models
IPageViewModel.cs
// Base type for all pages
interface IPageViewModel : INotifyPropertyChanged
{
public string PageTitle { get; set; }
}
LoginViewModel.cs
// BaseViewModel implementation.
// Consider to introduce dedicated abstract class Page which implements IPageViewModel
class LoginViewModel : IPageViewModel
{
// Implementation
}
PageOneViewModel.cs
// BaseViewModel implementation.
// Consider to introduce dedicated abstract class Page which implements IPageViewModel
class PageOneViewModel : IPageViewModel
{
// Implementation
}
PageTwoViewModel.cs
// BaseViewModel implementation.
// Consider to introduce dedicated abstract class Page which implements IPageViewModel
class PageTwoViewModel : IPageViewModel
{
// Implementation
}
How to navigate to other page with button in WPF
Solution to my own question:
I feel a bit silly providing a solution to my own question but thanks to Jasti's link I was able to sort my code out. As he had only posted a comment, I can't mark it as an answer, so here is the solution.
I changed the NavigationWindow to a Window and inserted:
<DockPanel>
<Frame x:Name="_NavigationFrame" NavigationUIVisibility="Hidden" />
</DockPanel>
And within the constructor of the MainWindow.xaml.cs I added:
_NavigationFrame.Navigate(new Page1());
Then the last step was to adjust the button event handler to:
this.NavigationService.Navigate(new Uri("Pages/Page2.xaml", UriKind.Relative));
Change between pages in WPF
If you are going to use Page
s in WPF, then you will need to read the Navigation Overview page on MSDN. In short however, you can navigate between Page
s in a WPF Application by using the NavigationService
Class. To change the page from code behind, you could do something like this:
NextPage page = new NextPage();
NavigationService.Navigate(page);
To let the users change the Page
, you can use the Hyperlink
Class in your Page
s:
<Hyperlink NavigateUri="pack://application:,,,/AppName;component/Pages/NextPage.xaml">
Navigate to Next Page
</Hyperlink>
To get your desired page setup, you will have to load your Page
s into a Frame
, which you can then layout wherever you like in MainWindow.xaml
:
<Frame Source="pack://application:,,,/AppName;component/Pages/SomePage.xaml" />
Related Topics
Does Parallel.Foreach Limit the Number of Active Threads
How to Get Client Date and Time in ASP.NET
External JSON Vulnerable Because of JSON.Net Typenamehandling Auto
How to Enable Nuget Package Restore in Visual Studio
How to Convert a Timespan to a Formatted String
C# - Get a List of Files Excluding Those That Are Hidden
Unity How to Make a Visual Joystick in Unity
How to Implement Icomparable Interface
What Does "Datetime" Mean in C#
Cannot Step into .Net Framework Source Code
Visual Studio "Could Not Copy" .... During Build
Dictionary Returning a Default Value If the Key Does Not Exist
Read Post Data Submitted to ASP.NET Form
How to Call a Non-Static Method from a Static Method in C#
Deserializing into a List Without a Container Element in Xml