Search code examples
xamluwpuwp-xamlcreators-update

How to mix dynamic and static items in UWP XAML NavigationView MenuItems?


I'm trying to make a NavigationViewMenu and I need a menu layed out as follows

  • static Home item

  • static Header

  • dynamic elements from DB as items

  • static Header

  • static set of items

This is what I tried:

<NavigationView.MenuItems>

    <NavigationViewItem Icon="Home"  Content="Home" Tag="home" />

    <NavigationViewItemSeparator />

    <NavigationViewItemHeader Content="My Stuff"/>

    <NavigationViewList ItemsSource="{x:Bind MyStuff}">
        <NavigationViewList.ItemTemplate>
            <DataTemplate x:DataType="local:MyModel">
                <NavigationViewItem Icon="Pictures" Content="{x:Bind Name}" Tag="{x:Bind Tag}" />
            </DataTemplate>
        </NavigationViewList.ItemTemplate>
    </NavigationViewList>

    <!-- Static equivalent to the above:
    <NavigationViewItem Icon="Pictures" Content="Woop" Tag="foos"/>
    <NavigationViewItem Icon="Pictures" Content="Doop" Tag="foos"/>
    <NavigationViewItem Icon="Pictures" Content="Loop" Tag="foos"/>
    -->

    <NavigationViewItemHeader Content="Other Stuff"/>

    <NavigationViewItem Icon="Pictures" Content="Foos" Tag="foos"/>
    <NavigationViewItem Icon="ContactInfo" Content="Bars" Tag="bars"/>
    <NavigationViewItem Icon="SwitchApps" Content="Bazes" Tag="bazes"/>

</NavigationView.MenuItems>

This is what I've got:

enter image description here

This is what I wanted:

enter image description here

Is there anything as good and practical as Angular's *ngFor in XAML for UWP?


Solution

  • I ran into the same behavior, and managed to find a work around. In my case, I had two lists of menu items (dynamically data-bound items), and I wanted to use NavigationViewItemHeader on top of both (static items). I tried using a NavigationViewList and ran into your problem.

    TL;DR:

    Create a list of menu items in C# code. The elements of this list can be a mix of your viewmodels, and any static Navigation Items (headers, separators, etc). Then use a DataTemplateSelector to either databind to your viewmodel or pass-through the navigation items unchanged.

    More detailed

    In your C# code-behind, create an enumerable (or observable collection) of your menu items. In my case SomeCollection and AnotherCollection represent my data sources that I wanted to bind to my NavigationView. I have to type it as object because it's a mix of my viewmodels and the built-in UWP navigation item types.

    private IEnumerable<object> MenuItems()
    {
        yield return new NavigationViewItemHeader { Content = "Some List" };
        foreach (var some in SomeCollection)
        {
            yield return some;
        }
        yield return new NavigationViewItemHeader { Content = "Another List" };
        foreach (var another in AnotherCollection)
        {
            yield return another;
        }
    }
    
    // somewhere else, like in your Page constructor or a CollectionChanged handler
    this.NavigationList = MenuItems().ToList();
    

    Second, create a Data Template Selector to switch between your template and the navigation items:

    class NavigationItemTemplateSelector : DataTemplateSelector
    {
        public DataTemplate ViewModelTemplate{ get; set; }
        public DataTemplate NavigationItemTemplate { get; set; }
        protected override DataTemplate SelectTemplateCore(object item)
        {
            return item is MyViewModel
                ? ViewModelTemplate
                : NavigationItemTemplate;
        }
    }
    

    Finally, change your NavigationView to reference the template selector and menu item source. The NavigationItemTemplate is just a pass-through, and your ViewModelTemplate would have the normal viewmodel item binding logic.

    <Page.Resources>
        <DataTemplate x:Key="ViewModelTemplate" x:DataType="local:MyViewModel">
            <TextBlock Text="{x:Bind SomeProperty}" />
        </DataTemplate>
        <DataTemplate x:Key="NavigationItemTemplate">
        </DataTemplate>
        <local:NavigationItemTemplateSelector x:Key="NavigationItemTemplateSelector"
            ViewModelTemplate="{StaticResource ViewModelTemplate}"
            NavigationItemTemplate="{StaticResource NavigationItemTemplate}" />
    </Page.Resources>
    
    
    <NavigationView
        MenuItemsSource="{x:Bind NavigationList, Mode=OneWay}"
        MenuItemTemplateSelector="{StaticResource NavigationItemTemplateSelector}">
        <Frame x:Name="ContentFrame"></Frame>
    </NavigationView>