7

I have a UWP app with a NavigationView control. The navigation items are created by setting MenuItemsSource in the XAML to a collection of objects of type NavigationViewElement.

<NavigationView 
        Style="{StaticResource MainPageNavControlStyle}" 
        HeaderTemplate="{StaticResource MainPageNavHeaderTemplate}"
        MenuItemsSource="{Binding NavigationViewElements}"
        MenuItemContainerStyleSelector="{StaticResource NavStyleSelector}"
        MenuItemTemplateSelector="{StaticResource NavItemTemplateSelector}"
        x:Name="NavigationViewControl" 
        CompactModeThresholdWidth="480" 
        ExpandedModeThresholdWidth="635" 
        OpenPaneLength="324"
        Loaded="OnControlLoaded"
        ItemInvoked="OnItemInvoked"
        IsTabStop="False"
        IsSettingsVisible="False"
>

I would like to bind the IsEnabled property of the NavigationViewItems that get created to a property on NavigationViewElement. How might I do that?

I had a similar question for a ListBox. In that case, I was able to derive a new class from ListBox that overrides PrepareContainerForItemOverride() and sets the IsEnabled flag of the ListBoxItem based on the data in the class to which it is bound (OptionItem, in this case)

protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
    ListBoxItem lItem = element as ListBoxItem;
    OptionItem oItem = item as OptionItem;

    if (lItem != null && oItem != null)
    {
        lItem.IsEnabled = oItem.IsEnabled;
    }
    base.PrepareContainerForItemOverride(element, item);
}

Is there an equivalent for NavigationView? Or is there some other way to indicate that the IsEnabled flag for the NavigationViewItem should be bound to NavigationViewElement.IsItemEnabled?

Update I looked at the solution proposed by Nico Zhu but I am not sure how to apply it to my requirements. That could be due to my inexperience with XAML.

In my implementation, the DataTemplates that I reference from my Selector object do not include a NavigationViewItem element, due to my layout requirements. Rather than simply setting NavigationViewItem.Content and .Glyph, I populate a Grid with a bunch of controls. Here is a sample:

<DataTemplate x:Key="MainPageNavigationViewItem1LineTemplate">
    <Grid Margin="{StaticResource MainPageNavigationViewItemTopGridMargin}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <FontIcon Glyph="{Binding Glyph, FallbackValue=&#xE14E;}" FontFamily="{Binding FontFamily, FallbackValue=xGlyph}" FontSize="{StaticResource MainPageNavigationViewItemGlyphFontSize}" VerticalAlignment="Center" Margin="{StaticResource MainPageNavigationViewItemGlyphMargin}"/>
        <StackPanel Grid.Column="1" Margin="{StaticResource MainPageNavigationViewItemTextMargin}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
            <TextBlock x:Name="Header"
                               Text="{Binding TheSummaryHelper.SummaryHeaderLabel, FallbackValue=TheHeader}" 
                               Style="{StaticResource DefaultFontStyle}"
                               />
            <TextBlock x:Name="Line1" 
                               Text="{Binding TheSummaryHelper.Line1.DisplayString, FallbackValue=TheFirstLine}" 
                               Visibility="{Binding TheSummaryHelper.Line1.ItemVisibility, FallbackValue=Visible}" 
                               Style="{StaticResource SmallSummaryTextStyle}"
                               />
        </StackPanel>
    </Grid>
</DataTemplate>

The result looks like this, with the item's contents set equal to those of the Grid:

enter image description here

It's exactly what I need, but I couldn't figure out how to bind the IsEnabled property of the item to NavigationViewElement.IsItemEnabled.

When I tried to follow the model suggested, adding a NavigationViewItem to the DataTemplate like this:

        <misc:MainPageNavigationViewItemTemplateSelector
            x:Key="NavItemTemplateSelector"
            >
            <misc:MainPageNavigationViewItemTemplateSelector.ItemTemplate>
                <DataTemplate x:DataType="vm_misc:NavigationViewElement">
                    <NavigationViewItem IsEnabled="{x:Bind IsItemEnabled}" Content="{x:Bind TheSummaryHelper.SummaryHeaderLabel}">
                    </NavigationViewItem>
                </DataTemplate>
            </misc:MainPageNavigationViewItemTemplateSelector.ItemTemplate>
        </misc:MainPageNavigationViewItemTemplateSelector>

then I am able to bind the IsEnabled property as desired, but the UI does not draw properly. The Content for the item seems to add a second NavigationViewItem on top of the one I already had. Clicking the area with the text does nothing - I have to click on the item outside the light gray area to cause navigation to occur.

enter image description here

Any insights into what I'm doing wrong? In summary, I am hoping to find a way to both customize the display contents of the NavigationViewItem while also binding the IsEnabled property to a property on the NavigationViewElement in my model.

Rich S
  • 111
  • 8

2 Answers2

5

For binding MenuItemsSource with model, you could refer official code sample. In latest version we have added new feature about setting datasource for MenuItemsSource. If you want to enable or disable NavigationViewItem, you could make IsEnabled property in the model then bind it. Please check the following code.

Xaml Code

<Page.Resources>
    <local:MenuItemTemplateSelector x:Key="selector">
        <local:MenuItemTemplateSelector.ItemTemplate>
            <DataTemplate x:DataType="local:Category" >
                <NavigationViewItem Content="{x:Bind Name}" 
                                    ToolTipService.ToolTip="{x:Bind Tooltip}" 
                                    IsEnabled="{x:Bind IsEnabled}" >
                    <NavigationViewItem.Icon>
                        <SymbolIcon Symbol="{x:Bind Glyph}" />
                    </NavigationViewItem.Icon>
                </NavigationViewItem>
            </DataTemplate>
        </local:MenuItemTemplateSelector.ItemTemplate >
    </local:MenuItemTemplateSelector>
</Page.Resources>
<Grid>
    <NavigationView x:Name="nvSample" 
            MenuItemTemplateSelector="{StaticResource selector}"                      
            MenuItemsSource="{x:Bind Categories, Mode=OneWay}" />

</Grid>

Code Behind

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        Categories = new ObservableCollection<CategoryBase>();
        Categories.Add(new Category { Name = "Category 1", Glyph = Symbol.Home, Tooltip = "This is category 1", IsEnabled = false });
        Categories.Add(new Category { Name = "Category 2", Glyph = Symbol.Keyboard, Tooltip = "This is category 2", IsEnabled = true });
        Categories.Add(new Category { Name = "Category 3", Glyph = Symbol.Library, Tooltip = "This is category 3" , IsEnabled = false });
        Categories.Add(new Category { Name = "Category 4", Glyph = Symbol.Mail, Tooltip = "This is category 4", IsEnabled = true });
    }

    public ObservableCollection<CategoryBase> Categories { get;  set; }
}


public class CategoryBase { }

public class Category : CategoryBase
{
    public string Name { get; set; }
    public string Tooltip { get; set; }
    public Symbol Glyph { get; set; }
    public bool IsEnabled { get; set; }
}

public class Separator : CategoryBase { }

public class Header : CategoryBase
{
    public string Name { get; set; }
}

[ContentProperty(Name = "ItemTemplate")]
class MenuItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate ItemTemplate { get; set; }
    protected override DataTemplate SelectTemplateCore(object item)
    {
        return item is Separator ? SeparatorTemplate : item is Header ? HeaderTemplate : ItemTemplate;
    }
    internal DataTemplate HeaderTemplate = (DataTemplate)XamlReader.Load(
       @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
               <NavigationViewItemHeader Content='{Binding Name}' />
              </DataTemplate>");

    internal DataTemplate SeparatorTemplate = (DataTemplate)XamlReader.Load(
        @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                <NavigationViewItemSeparator />
              </DataTemplate>");
}

enter image description here



Update One

The key to the problem is that we can’t use DataTemplate in 1803. In general, for creating NavigationViewItem in 1803 we often use the following solution.

Xaml

<NavigationView x:Name="nvSample" MenuItemsSource="{x:Bind NavItems}" >

</NavigationView>

Code behind

public ObservableCollection<Category> Categories { get; set; }
public MainPage()
{
    this.InitializeComponent();
    Categories = new ObservableCollection<Category>();
    Categories.Add(new Category { Name = "Category 1", Glyph = Symbol.Home, Tooltip = "This is category 1", IsEnabled = false });
    Categories.Add(new Category { Name = "Category 2", Glyph = Symbol.Keyboard, Tooltip = "This is category 2", IsEnabled = true });
    Categories.Add(new Category { Name = "Category 3", Glyph = Symbol.Library, Tooltip = "This is category 3", IsEnabled = true });
    Categories.Add(new Category { Name = "Category 4", Glyph = Symbol.Mail, Tooltip = "This is category 4", IsEnabled = true });

}
public IEnumerable<NavigationViewItemBase> NavItems
{
    get
    {
        return Categories.Select(
               b => (new NavigationViewItem
               {
                   Content = b.Name,
                   Icon = new SymbolIcon(b.Glyph),
                   IsEnabled = b.IsEnabled,
               })
        );
    }
}

In short, we need convert Data Model to NavigationViewItem. But not using DataTemplate within 1803. Please try this. For more detail you could also refer this case.

Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • This looks promising. I will try next week and report back. – Rich S Dec 28 '18 at 18:21
  • @RichS, I checked your update , it is know issue occur in 1803 operation system. please check this [case reply](https://stackoverflow.com/a/54020648/7254781). – Nico Zhu Jan 04 '19 at 07:28
  • Thank you for that information. Our software must run in 1803 - what are our options for handling this issue? Is there a version of NavigationView that would behave correctly in 1803? – Rich S Jan 04 '19 at 16:34
  • I am on 1803, and although this builds ok, it never hits either selection changed or item invoked events. So it looks great, just unusable – Mark W Jan 04 '19 at 18:53
  • Just found out I could set IsHitTestVisable to false to be able to fire the selection changed or item invoked events – Mark W Jan 13 '19 at 17:48
  • Hi @RichS, I have escalate your case. And could you tell the reason you need to stay on 1803? – Nico Zhu Jan 16 '19 at 02:08
  • Thank you Nico Zhu. We are developing a utility and many of our customers are enterprise users who do not update their OS as often as home users. So we need to support them on 1803. – Rich S Jan 17 '19 at 16:07
  • Is it possible to customize the layout and content of the NavigationView items without a DataTemplate? I am required to show custom content (e.g. multiple lines of text) and for that content to vary based on information in the NavigationViewElement (model). That is why I am using a DataTemplate and MenuItemTemplateSelector. – Rich S Jan 24 '19 at 17:12
  • If you can use 1809, the behavior there allows NavigationViewItems in a DataTemplate as you are currently doing. In down-versions however, NavigationViewItem was only allowed in the MenuItems property and could not be used in the MenuItemTemplate property. So, you could move your items to the MenuItems property without a DataTemplate. – David Hollowell - MSFT Jan 28 '19 at 16:57
  • If you're on a down-version and you want to use the MenuItemTemplate property with a DataTemplate, you'll need to use something other than NavigationViewItem. For example, you could use a button with the Button.Content set to a grid. Within the grid, you could include something like a SymbolIcon and TextBlock. It would take some work to get the formatting right. – David Hollowell - MSFT Jan 28 '19 at 16:57
0

If you can use 1809, the behavior there allows NavigationViewItems in a DataTemplate as you are currently doing.

In down-versions however, NavigationViewItem was only allowed in the MenuItems property and could not be used in the MenuItemTemplate property. So, you could move your items to the MenuItems property without a DataTemplate.

If you're on a down-version and you want to use the MenuItemTemplate property with a DataTemplate, you'll need to use something other than NavigationViewItem. For example, you could use a button with the Button.Content set to a grid. Within the grid, you could include something like a SymbolIcon and TextBlock. It would take some work to get the formatting right.

My suggestion is, if you need to use 1803, move the NavigationViewItems to the MenuItems property of the NavigationView itself.

For documentation, please see: https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.navigationviewitem

David Hollowell - MSFT
  • 1,065
  • 2
  • 9
  • 18
  • Thank you for the info and suggestions. In our case, we are dynamically binding our navigation items to a collection of objects that get determined at runtime. I believe that precludes the ability to move the items into MenuItems. Also, the layout of our items requires multiple lines of text, which is why we decided to use a DataTemplate. This solution works very well for us - except for the fact that I have been unable to bind the IsEnabled property of the item to a property on my model. That was the reason I posted here. – Rich S Jan 31 '19 at 20:04
  • The best user experience for us would be to disable the navigation item when the functionality on the associated page is not available or relevant. For 1803, it looks like we will need to settle without. Instead, we will allow the user to navigate to that page, but the controls on the page will be disabled. We can ship that, it just isn't great UI design. – Rich S Jan 31 '19 at 20:08