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

55 Views Asked by At

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.

1

There are 1 best solutions below

0
BionicCode On BEST ANSWER

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
}