Customizing Settings item in UWP NavigationView

Development WinUI XAML

5 years ago

In this article we will see how to modify the default icon and text of the built-in Settings item in UWP's NavigationView.

Custom settings item

Custom settings item

Hardcoded settings

I first realized that the Settings item is hardcoded while trying to answer this Stack Overflow question from @Xinpeng and it caught me by surprise. This particular item is not part of the NavigationView.PaneFooter but is right below it, and although the developer can control if it is displayed or not, it is not possible to access the item directly and especially customize its properties. The first idea I thought of was to instead create a custom item in the PaneFooter, but this was not a viable solution as items in this section can't participate in the built-in selected item animation. So instead I checked the default template of NavigationView to find the following:

<NavigationViewItem x:Name="SettingsNavPaneItem" Grid.Row="5">
    <NavigationViewItem.Icon>
        <SymbolIcon Symbol="Setting"/>
    </NavigationViewItem.Icon>
</NavigationViewItem>

The settings item is defined here along with its icon, so modifying it is quite easy. Not so much with the text, however. It turns out the control sets the NavigationViewItem.Content in its code to provide a localized Settings text.

Customized NavigationView

The proper solution to this problem involves deriving a custom class from NavigationView. The system applies the default template in OnApplyTemplate and at the same time sets the settings item Content to the localized string. That's where we come in. First, let's create a custom class CustomizableNavigationView:

public class CustomizableNavigationView : NavigationView
{
   ...
}

We create two dependency properties - one for the icon and one for the desired text. Note that the code won't compile just yet, as we are using a SettingsItemChanged method which we implement later.

public static readonly DependencyProperty SettingsItemTextProperty = DependencyProperty.Register(
       nameof(SettingsItemText), typeof(string), typeof(CustomizableNavigationView), new PropertyMetadata(default(string), SettingsItemChanged));

public static readonly DependencyProperty SettingsItemIconProperty = DependencyProperty.Register(
    nameof(SettingsItemIcon), typeof(IconElement), typeof(CustomizableNavigationView), new PropertyMetadata(default(IconElement), SettingsItemChanged));

public IconElement SettingsItemIcon
{
    get => (IconElement)GetValue(SettingsItemIconProperty);
    set => SetValue(SettingsItemIconProperty, value);
}

public string SettingsItemText
{
    get => (string)GetValue(SettingsItemTextProperty);
    set => SetValue(SettingsItemTextProperty, value);
}

Now we can override the OnApplyTemplate method. First, we let the control do its thing and then grab the settings item control from the template by its name and update it.

private const string SettingsNavigationViewItemName = "SettingsNavPaneItem";
private NavigationViewItem _settingsItem;

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    _settingsItem = (NavigationViewItem)GetTemplateChild(SettingsNavigationViewItemName);
    UpdateSettingsItemText();
}

Updating the item is straightforward, using the current values for the dependency properties.

private readonly SymbolIcon _defaultIcon = new SymbolIcon(Symbol.Setting);

private void UpdateSettingsItemText()
{
    if (_settingsItem != null)
    {
        _settingsItem.Content = SettingsItemText ?? "";
        _settingsItem.Icon = SettingsItemIcon ?? _defaultIcon;
    }
}

Finally, to properly support dependency properties, we implement the property changed callback and call the update method from there as well.

private static void SettingsItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    if (dependencyObject is CustomizableNavigationView navigationView)
    {
        navigationView.UpdateSettingsItemText();
    }
}

Using the resulting control in code is pretty easy. We first add an XML namespace declaration with the control's namespace to make it available:

<Page
    ...
    xmlns:controls="using:MyApp.Controls">

Now we can use our control in place of NavigationView:

<controls:CustomizableNavigationView SettingsItemText="My text" IsSettingsVisible="True">
    <controls:CustomizableNavigationView.SettingsItemIcon>
        <SymbolIcon Symbol="Edit" />
    </controls:CustomizableNavigationView.SettingsItemIcon>
</controls:CustomizableNavigationView>

Source code

Source code for this article is available on my GitHub.

Summary

While NavigationView control provides a settings navigation item by default, it is a bit hard to customize it. Luckily we can customize the control itself and set it up to look fit our needs ideally.