Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 264
Felype Avatar asked Oct 24 '17 02:10

Felype


2 Answers

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>
like image 118
Will Avatar answered Oct 22 '22 18:10

Will


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.

like image 45
Barry Wang Avatar answered Oct 22 '22 19:10

Barry Wang