Search code examples
c#wpfcontroltemplatenavigationservicenavigationwindow

WPF How do I allow my ControlTemplate to use an existing NavigationService?


I have a ControlTemplate for a Menu which I want to be used for all my Pages. The function calls of the ControlTemplate requires the usage of the NavigationService belonging my NavigationWindow. However, I can not place the ControlTemplate in the NavigationWindow xaml file, because it is not accessible to the Pages. How would I allow my ControlTemplate to access the NavigationService while being available to use in my Pages?

My NavigationWindow is located at MainWindow.xaml

App.xaml.cs

namespace WPFApplication
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private void Menu_ModeAdvanced_Click(object sender, RoutedEventArgs e)
        {
            // Where I want to access the Navigation Service
        }
    }
}

App.xaml

<Application x:Class="WPFApplication.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WPFApplication"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <!-- Page Design -->
            <Style TargetType="Page">
                <Setter Property="Foreground" Value="#FBF5F3" />
                <Setter Property="Background" Value="#363537" />
            </Style>
            
            <!-- Label Design-->
            <Style TargetType="Label">
                <Setter Property="Foreground" Value="#FBF5F3" />
            </Style>
        </ResourceDictionary>

        <!-- Menu Template-->
        <ControlTemplate x:Key="StandardMenu" TargetType="Menu">
            <Menu Height="20">
                <MenuItem Header="File" AllowDrop="True">
                    <MenuItem Header="Mode" AllowDrop="True">
                        <MenuItem Header="Advanced"/>
                    </MenuItem>
                </MenuItem>
            </Menu>
        </ControlTemplate>
    </Application.Resources>
</Application>

I have tried putting the ControlTemplate and related code into a ResourceDictionary located in the NavigationWindow's class. However, I could not use it as a static resource in my pages.


Solution

  • Simply get the parent Window of the MenuItem. If it is the NavigationWindow you can use its API to execute navigation related tasks or use its associated NavigationWindow.NavigationService:

    private void Menu_ModeAdvanced_Click(object sender, RoutedEventArgs e)
    {
      FrameworkElement element = sender as FramwworkElement;
      NavigationWindow navigationWindow = null;
      while (element.Parent is not null)
      {
        element = element.Parent as FrameworkElement;
        if (element is NavigationWindow window)
        {
          navigationWindow = window;
          break;
        }
      }
    
      // Navigate etc. using the NavigationWindow API
      navigationWindow?.Navigate(new Uri());
     
      // Navigate etc. using the NavigationService
      navigationWindow?.NavigationService.Navigate(new Uri());
    }
    

    But I recommend defining routed commands in your NavigationWindow.
    It's much cleaner from a design perspective as the navigation details are kept private to the navigation host:

    MainWindow.xaml.cs

    partial class MainWindow : NavigationWindow
    {
      public static RoutedCommand NavigateToPageCommand { get; }
        = new RoutedCommand(nameof(MainWindow.NavigateToPageCommand, typeof(MainWindow)); 
    
      private Dictionary<PageId, Uri> PageUriStore { get; }
    
      public MainWindow()
      {
        InitializeComponent();
    
        var navigateCommandBinding = new CommandBinding(
          MainWindow.NavigateToPageCommand,
          ExecutedNavigateToPageCommand,
          CanexecutedNavigateToPageCommand);
        this.CommandBindings.Add(navigateCommandBinding);
    
        this.PageUriStore = new Dictionary<PageId, Uri>
        {
          { PageId.SettingsPage, new Uri(...) },
        };
      }
    
      private void CanexecutedNavigateToPageCommand(object sender, ExecutedRoutedEventArgs e)
        => e.CanExecute = e.Parameter is PageId;
    
      private void CanexecutedNavigateToPageCommand(object sender, ExecutedRoutedEventArgs e)
      {
        var pageId = (PageId)e.Parameter;
        if (this.PageUriStore.TryGetValue(pageId, out Uri destinationUri))
        {
          this.Navigate(ddestinationUri);
        }
      }
    }
    

    App.xaml

    <MenuItem Header="Settings"
              Command="{x:Static MainWindow.NavigateToPageCommand}"
              CommandParameter="{x:Static PageId.SettingsPage}" />
    

    PageId.cs

    enum PageId
    {
      Default = 0,
      OverviewPage,
      SettingsPage
    }