Aamiliah
Aamiliah

Reputation: 15

Applying different bindings to a DataTemplate for Mahapps Metro HamburgerMenuIconItem

I am using the Hamburger Menu in a MVVM WPF application and have applied the Badge control to each menu item using a DataTemplate as follows: (taken from https://github.com/MahApps/MahApps.Metro/issues/3800).

`<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type Controls:HamburgerMenuIconItem}">
    <Grid Height="48">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="48" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Controls:Badged Grid.Column="0"
                         Badge="{Binding DataContext.TestCount, RelativeSource={RelativeSource AncestorType=UserControl}}"
                         BadgeBackground="Red"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Center">
            <ContentControl Content="{Binding Icon}"
                            Margin="2"
                            Focusable="False"
                            IsTabStop="False" />
        </Controls:Badged>
        <TextBlock Grid.Column="1"
                   VerticalAlignment="Center"
                   FontSize="16"
                   Text="{Binding Label}" />
    </Grid>
</DataTemplate>`

This works fine, but this applies the same value to all menu items - in this example I am binding to a property named TestCount from MessagingMainViewModel (the view model for the view containing the markup for the HamburgerMenu control).

`<Controls:HamburgerMenu x:Name="HamburgerMenuControl"
                                    HamburgerWidth="48"
                                    IsPaneOpen="True"
                                    CanResizeOpenPane="True"
                                    ItemInvoked="HamburgerMenuControl_OnItemInvoked"
                                    ItemTemplate="{StaticResource MenuItemTemplate}"
                                    OptionsItemTemplate="{StaticResource MenuItemTemplate}"
                                    SelectedIndex="0"
                                    Style="{StaticResource MahApps.Styles.HamburgerMenu.Ripple}"
                                    VerticalScrollBarOnLeftSide="False">
                <!--  Items  -->
                <Controls:HamburgerMenu.ItemsSource>
                    <Controls:HamburgerMenuItemCollection>
                        <Controls:HamburgerMenuIconItem Icon="{iconPacks:FontAwesome Kind=CommentAltSolid}" Label="Chat">
                            <Controls:HamburgerMenuIconItem.Tag>
                                <views:ChatView />
                            </Controls:HamburgerMenuIconItem.Tag>
                        </Controls:HamburgerMenuIconItem>
                        <Controls:HamburgerMenuIconItem Icon="{iconPacks:FontAwesome Kind=PenSquareSolid}" Label="Compose">
                            <Controls:HamburgerMenuIconItem.Tag>
                                <views:ComposeMessageView />
                            </Controls:HamburgerMenuIconItem.Tag>
                        </Controls:HamburgerMenuIconItem>
                        <Controls:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=InboxArrowDown}" Label="Inbox">
                            <Controls:HamburgerMenuIconItem.Tag>
                                <views:InboxView />
                            </Controls:HamburgerMenuIconItem.Tag>
                        </Controls:HamburgerMenuIconItem>
                    </Controls:HamburgerMenuItemCollection>
                </Controls:HamburgerMenu.ItemsSource>
...`

Each of the menu items is a view, with it's own view model and I would like to bind to an exposed property from the ChatViewModel and InboxViewModel (but not the ComposeViewModel), for example, a property named UnreadCount. I do not have insances of the child view models in the MessagingMainViewModel (as there has been no need thus far in the application).

I know how to bind to a property on the MessagingMainViewModel (as the DataTemplate code above shows) but cannot find a way to access the "child" view's viewmodel - never mind use this in the DataTemplate somehow. Is this possible at all? Thanks.

Upvotes: 1

Views: 964

Answers (2)

Aamiliah
Aamiliah

Reputation: 15

Just in case anyone faces a similar problem, I used the Tag property to gain access to the relevent view model and property and also implemented a DataTemplateSelector in order to select between showing the badge/count control or not (i.e. not shown for the ComposeMessageView). All thanks to Tim U.

My final code:

<!-- Add reference to the custom DataTemplateSelector namespace -->
xmlns:helpers="clr-namespace:MyCompany.Wpf.Modules.Messaging.Helpers"

<!-- Include the template selector within Resouces (in my case within UserControl.Resources) -->
<helpers:HamburgerMenuIconItemTemplateSelector x:Key="MenuDataTemplateSelector"/> 

<!--  This is the template for the menu items (no badge/count control).  -->
<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type Controls:HamburgerMenuIconItem}">
    <Grid Height="48">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="48" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ContentControl Grid.Column="0"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Content="{Binding Icon}"
                        Focusable="False"
                        IsTabStop="False" />
        <TextBlock Grid.Column="1"
                   VerticalAlignment="Center"
                   FontSize="16"
                   Text="{Binding Label}" />
    </Grid>
</DataTemplate>

<!--  This is the template for the menu items (with badge/count control).  -->
<DataTemplate x:Key="MenuItemTemplateBadged" DataType="{x:Type Controls:HamburgerMenuIconItem}">
    <Grid Height="48">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="48" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Controls:Badged Grid.Column="0"
                         Badge="{Binding Tag.DataContext.UnreadMessagesCount}"
                         BadgeBackground="Red"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Center">
            <ContentControl Content="{Binding Icon}"
                            Margin="2"
                            Focusable="False"
                            IsTabStop="False" />
        </Controls:Badged>
        <TextBlock Grid.Column="1"
                   VerticalAlignment="Center"
                   FontSize="16"
                   Text="{Binding Label}" />
    </Grid>
</DataTemplate> 

<Controls:HamburgerMenu x:Name="HamburgerMenuControl"
                    HamburgerWidth="48"
                    IsPaneOpen="True"
                    CanResizeOpenPane="True"
                    ItemInvoked="HamburgerMenuControl_OnItemInvoked"
                    ItemTemplateSelector="{StaticResource MenuDataTemplateSelector}"
                    SelectedIndex="0"
                    Style="{StaticResource MahApps.Styles.HamburgerMenu.Ripple}"
                    VerticalScrollBarOnLeftSide="False">
    <!--  Items  -->
    <Controls:HamburgerMenu.ItemsSource>
        <Controls:HamburgerMenuItemCollection>
            <Controls:HamburgerMenuIconItem Icon="{iconPacks:FontAwesome Kind=CommentAltSolid}" Label="Chat">
                <Controls:HamburgerMenuIconItem.Tag>
                    <views:ChatView />
                </Controls:HamburgerMenuIconItem.Tag>
            </Controls:HamburgerMenuIconItem>
            <Controls:HamburgerMenuIconItem Icon="{iconPacks:FontAwesome Kind=PenSquareSolid}" Label="Compose">
                <Controls:HamburgerMenuIconItem.Tag>
                    <views:ComposeMessageView />
                </Controls:HamburgerMenuIconItem.Tag>
            </Controls:HamburgerMenuIconItem>
            <Controls:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=InboxArrowDown}" Label="Inbox">
                <Controls:HamburgerMenuIconItem.Tag>
                    <views:InboxView />
                </Controls:HamburgerMenuIconItem.Tag>
            </Controls:HamburgerMenuIconItem>
        </Controls:HamburgerMenuItemCollection>
    </Controls:HamburgerMenu.ItemsSource>
    ...


<!-- The custom data template selector class. -->
namespace MyCompany.Wpf.Modules.Messaging.Helpers
{
    using System.Windows;
    using System.Windows.Controls;

    using MyCompany.Wpf.Modules.Messaging.ViewModels;
    using MyCompany.Wpf.Modules.Messaging.Views;

    using MahApps.Metro.Controls;

    public class HamburgerMenuIconItemTemplateSelector: DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            FrameworkElement element = container as FrameworkElement;

            if (element != null && item != null && item is HamburgerMenuIconItem)
            {
                var count = GetUnreadMessagesCount((HamburgerMenuIconItem)item);

                if (count == -1)
                {
                    return element.FindResource("MenuItemTemplate") as DataTemplate;
                }

                return element.FindResource("MenuItemTemplateBadged") as DataTemplate;
            }    

            return null;
        }

        private int GetUnreadMessagesCount(HamburgerMenuIconItem item)
        {
            // Get the view/user control
            var viewUserControl = (UserControl)item.Tag;

            // Get the data context. NOTE: All view models in the example
            // inherit from BaseMessageViewModel that exposes property 
            // UnreadMessagesCount - and this property is set to -1 when 
            // the view model is constructed.
            var dataContext = (BaseMessageViewModel)viewUserControl.DataContext;

            return dataContext.UnreadMessagesCount;
        }
    }
}

Upvotes: 0

Tim U.
Tim U.

Reputation: 129

You have your Content set in the Tag-Property. We can use this in our DataTemplate to access anything in the Tag-object. You say that your Tag is a UserControl, so we can access its DataContext.

Here is an example, assuming that the Property is the same for every view you have. If not, you need to use a TemplateSelector:

<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type Controls:HamburgerMenuIconItem}">
    <Grid Height="48">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="48" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Controls:Badged Grid.Column="0"
                         Badge="{Binding Tag.DataContext.[YourBadgePropertyGoesHere]}"
                         BadgeBackground="Red"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Center">
            <ContentControl Content="{Binding Icon}"
                            Margin="2"
                            Focusable="False"
                            IsTabStop="False" />
        </Controls:Badged>
        <TextBlock Grid.Column="1"
                   VerticalAlignment="Center"
                   FontSize="16"
                   Text="{Binding Label}" />
    </Grid>
</DataTemplate>

Upvotes: 1

Related Questions