Search code examples
c#mvvmuwpuwp-xamlwindows-community-toolkit

MVVM command binding to UWP toolkit hamburger menu


I try to use MVVM pattern to bind ItemClick and OptionItemClick events to command. According to the document https://developer.microsoft.com/en-us/windows/uwp-community-toolkit/api/microsoft_toolkit_uwp_ui_controls_hamburgermenu there is no Command I can bind to. So I am thinking to use https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Uwp.Managed/ package to attach my event trigger to a command.

My XAML code is

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
    xmlns:localModel="using:App1.Models"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
    mc:Ignorable="d">

    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <localTemplate:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
            <DataTemplate x:Key="DefaultTemplate" x:DataType="localModel:MenuItem">
                <Grid Width="240" Height="48" Margin="0,0,0,0" HorizontalAlignment="Left">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="48" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <SymbolIcon Grid.Column="0" Symbol="{x:Bind Icon, Mode=OneWay}" Foreground="White" />
                    <TextBlock Grid.Column="1" Text="{x:Bind Name, Mode=OneWay}" FontSize="16" VerticalAlignment="Center" Foreground="White" />
                </Grid>
            </DataTemplate>
        </ResourceDictionary>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:HamburgerMenu x:Name="hamburgerMenuControl"
                                Grid.Row="0"
                                Grid.Column="0"
                                PaneBackground="Black"
                                Foreground="White"
                                DisplayMode="CompactOverlay"
                                ItemsSource="{Binding Path=MainMenuItems}"
                                ItemTemplate="{StaticResource DefaultTemplate}"
                                OptionsItemsSource="{Binding Path=OptionMenuItems}"
                                OptionsItemTemplate="{StaticResource DefaultTemplate}"
                                OptionsItemClick="OnMenuItemClick">
            <i:Interaction.Behaviors>
                <ic:EventTriggerBehavior EventName="ItemClick">
                    <ic:InvokeCommandAction Command="{Binding Path=NavigateCommand}" />
                </ic:EventTriggerBehavior>
            </i:Interaction.Behaviors>
            <Frame x:Name="contentFrame" />
        </controls:HamburgerMenu>
    </Grid>
</Page>

First of all the code looks funny in VS2017, but it does not generate compile error. enter image description here

Secondly, when I launch the code and click on a menu item, there is exception thrown inside generated code App.g.i.cs enter image description here

And also with further message to ask me to launch another debugger.

A debugger is attached to App1.exe but not configured to debug this unhandled exception. To debug this exception, detach the current debugger.

Any clue, what is the proper way to bind commands to my view model?


Solution

  • I don't see your Page's DataContext set to your viewmodel in XAML, so I suppose this is done in the code behind.

    Like Andrey mentioned, you can see the actual exception when hovering over the e parameter, or checking it's value in the Watch window. My first idea is that your Command in your viewmodel is of the wrong type, as your XAML seems to be correct. The argument passed in to your viewmodel is not the actual type bound to your HamburgerMenu but ItemClickedEventArgs.

    This is the code for DelegateCommand, but RelayCommand (or whatever ICommand implementation you use) should be similar.

    public DelegateCommand<ItemClickEventArgs> NavigateCommand { get; }
    
    private void OnMenuItemClicked(ItemClickEventArgs args)
    {
        HamburgerMenuPrismItem menuItem = (HamburgerMenuPrismItem)args.ClickedItem;
        _navigationService.Navigate(menuItem.TargetPageToken, null);
    }
    

    Note that the HamburgerMenuPrismItem is my own type, this should be the type you bound to your HamburgerMenu.

    I've created a similar sample (with Prism as MVVM framework) some time ago, so you can check the source yourself on GitHub.

    Bonus: In case you added the Microsoft.Xaml.Behaviors.Uwp.Managed yourself, there's no need for it (unless if you want to specify the version). You need Microsoft.Toolkit.Uwp.UI.Controls for your HamburgerMenu, which brings in Microsoft.Toolkit.Uwp.UI.Animations, which in itself brings in Microsoft.Xaml.Behaviors.Uwp.Managed.