C# Wpf Navigation Between Pages (Views)

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

  1. Create the individual page controls (the controls that host the page content). This can be a Control, UserControl, Page or simply a plain DataTemplate:

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>

  1. 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));
}
}

  1. Create an enumeration of page identifiers (to eliminate magic strings in XAML and C#):

PageName.cs

    public enum PageName
{
Undefined = 0, WelcomePage, LoginPage
}

  1. 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 Pages in WPF, then you will need to read the Navigation Overview page on MSDN. In short however, you can navigate between Pages 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 Pages:

<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 Pages 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



Leave a reply



Submit