0

I have implemented a Hamburger Menu using the community toolkit which started out nicely. I am now trying to add the ability to change the items in the menu. For example I want one of the OptionItems to display logged in status. If not logged in I want it to say Log In, if they are I want it to show their name. I tried adding a standard x:Bind to the label but it does not work:

<controls:HamburgerMenuGlyphItem Label="{x:Bind Path=UserProfileViewModel.UserName}" Tag="SignIn" Glyph="Contact" />

Using this exact same binding on a TextBlock outside the Hamburger menu works fine, the user name is displayed once they have logged in.

Anyone have any idea how to implement binding within the MenuItems?

BGTurner
  • 307
  • 3
  • 9

2 Answers2

1

Anyone have any idea how to implement binding within the MenuItems?

You could set the ItemsSource of the HamburgerMenu to an IEnumerable and bind to a property of the type T in the ItemTemplate.

View:

<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
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>
    <DataTemplate x:Name="ItemTemplate" x:DataType="local:Item">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="48" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <SymbolIcon Grid.Column="0" Symbol="{x:Bind Symbol}" />
            <TextBlock Grid.Column="1" Text="{x:Bind Label, Mode=TwoWay}" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <controls:HamburgerMenu x:Name="hamburgerMenuControl"
                            ItemsSource="{Binding Items}"
                            SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                            ItemTemplate="{StaticResource ItemTemplate}"
                            ItemClick="hamburgerMenuControl_ItemClick">
        <ContentControl Content="{Binding SelectedItem.Content}" />
    </controls:HamburgerMenu>
</Grid>
</Page>

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.DataContext = new ViewModel();
    }

    private void hamburgerMenuControl_ItemClick(object sender, ItemClickEventArgs e)
    {
        //workaround to set the SelectedItem property of the view model when a new item is selected
        (DataContext as ViewModel).SelectedItem = e.ClickedItem as Item;
    }
}

View Model:

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        Items.Add(new Item() { Label = "1", Symbol = Symbol.Bullets, Content = "1..." });
        Items.Add(new Item() { Label = "2", Symbol = Symbol.Bullets, Content = "2..." });
        Items.Add(new Item() { Label = "3", Symbol = Symbol.Bullets, Content = "3..." });
        SelectedItem = Items[0];
    }

    public IList<Item> Items { get; } = new List<Item>();

    private Item _selectedItem;
    public Item SelectedItem
    {
        get { return _selectedItem; }
        set { _selectedItem = value; NotifyPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Model:

public class Item : INotifyPropertyChanged
{
    private string _label;
    public string Label
    {
        get { return _label; }
        set { _label = value; NotifyPropertyChanged(); }
    }

    public Symbol Symbol { get; set; }
    public object Content { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Set the Label property of the model to the username or whatever you want to display in the options menu. This is how you data bind the HamburgerMenu using the recommended MVVM design pattern.

If you are defining the HamburgerMenuGlyphItems directly in the XAML markup of your view you could bind set up the binding programmatically in the code-behind of the same view:

<controls:HamburgerMenuGlyphItem x:Name="item" />

public MainPage()
    {
        this.InitializeComponent();

        BindingOperations.SetBinding(item, HamburgerMenuGlyphItem.LabelProperty, 
            new Binding() { Path = new PropertyPath(nameof(Username)), Source = this });
    }

    public string Username { get; } = "username...";
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Hi, thanks for the suggestion, however I can't get it working. Created a new solution and used the code provided. Added INotifyPropertyChanged to the Item class. Added a button to update the label of the last item. Upon pressing the button nothing happened. So I added another content control and bound that to the label value. The content control does indeed update when the button is pressed while the Hamburger menu seems to ignore it. This is the same issue I see when trying to use the standard x:Bind I noted in the question. Other controls update but the Hamburger Menu does not. – BGTurner Dec 12 '16 at 11:11
  • mm8, deleted my original comment as got called away and SO locks comment editing after 5 minutes. The code I am using in the sample is as simple as updating the label on a button click. ((ViewModel)this.DataContext).Items[2].Label = "Signed In"; The content control shows the change but the menu still displays '3' – BGTurner Dec 12 '16 at 11:56
  • Scratch that, I needed to add Mode=TwoWay to the TextBlock binding in the DataTemplate, once I did that it updated on the button press. So your answer works but there are some amendments that need to be made for the full solution. Mode=TwoWay. change 'Model' class to 'Item'. Change DataType of the template to 'Item'. Add INotifyPropertyChanged interface to the Item class. Then we are good to go :D – BGTurner Dec 12 '16 at 12:06
0

So the solution provided by mm8 indeed work. However, in getting that to work I discovered how to solve the initial problem without having to go to any extra effort. Adding the Mode=TwoWay (OneWay works also) to the TextBlock binding in the DataTemplate solved the issue of getting the label to update when the property changes. I then found that the databinding on the HamburgerMenuGlyphItem itself also needs this 'Mode' value setting. Once I added the Mode to that binding as well the label started updating when a user signs in and does not need any external code at all :)

BGTurner
  • 307
  • 3
  • 9