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

3

8

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?

Screw answered 24/10, 2017 at 2:59 Comment(2)
Does putting only a single element in MyStuff work? Does hard-coding a height in ti the NavigationListView force it to show correctly?Sandbag
Related: #49255833Sarpedon
B
5

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>
Babara answered 11/8, 2018 at 12:30 Comment(0)
C
2

I can reproduce it. It looks like NavigationViewList only take the space of one item when putting itself in NavigationView.MenuItem. Which is the same like putting a ListView in a ListViewItem. To change this behavior we need to change the item's behaviour ourselves. However after some investigating it seems currently customization of NavigationViewList is blackbox for us. So the only way I could think is to build our own NavigationView with the help of splitview and acrylic.

Crispin answered 28/10, 2017 at 6:50 Comment(1)
I'm probably going with this, looks like an acrylic container with listviews and buttons will sort it out, in spite of the hassle, loses the NavigationView cool retraction and implemented responsivity thoughScrew
O
0

I didn't find it necessary to use different templates as in the accepted answer, maybe because there were some changes in the underlying Windows code in the meantime. As I needed a stable part of the menu and then a dynamic part depending on the actual page, I created an interface:

interface IMenuProvider {
  IEnumerable<NavigationViewItemBase> GetMenuItems();
}

and made sure all my pages implement it. My MainPage returns the fixed part:

public IEnumerable<NavigationViewItemBase> GetMenuItems() {
  yield return new NavigationViewItem {
    Tag = "home",
    Icon = new SymbolIcon(Symbol.Home),
    Content = "Home",
  };
  yield return new NavigationViewItemSeparator();
  yield return new NavigationViewItem {
    Tag = "xxx",
    Icon = new SymbolIcon(Symbol.XXX),
    Content = "XXX",
  };
}

the other pages, similary, provide their own menu headers and items.

When I navigate the pages, I change the menu as well, concatenating the fixed and variable parts:

ContentFrame.Navigate(PageType, null, transitionInfo);
if (ContentFrame.Content is IMenuProvider menuProvider)
  = GetMenuItems().Concat(menuProvider.GetMenuItems()).ToList();

(Or, you might place the menu change into the Navigated handler of the Frame.)

While it's still a nuisance that these menus, at least the fixed part, cannot be declared in XAML, this approach works.

Ogrady answered 2/12, 2020 at 11:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.