MahApps is probably one of the most used UI library among WPF developers, with a galaxy of great controls. Despite that, recently I was surprised to see lack of proper example for Hamburger Menu
control, particulary MVVM based. I was also more keen to know how to make best use of capabilities of Caliburn Micro along the way.
Here is how I ended up achieving my objectives.
XAML : Control, Content, ItemTemplate and HeaderTemplate
The first step would be to define the Control in Xaml, along with desired ContendTemplate
,ItemTemplate
and HeaderTemplate
.
<mah:HamburgerMenu x:Name="Menu" DisplayMode="CompactOverlay" SelectedItem="{Binding ActiveItem,Converter={StaticResource SelectedItemConverter}}"
ItemsSource="{Binding MenuItems,Converter={StaticResource MenuConverter}}"
cal:Message.Attach="[Event ItemClick]=[Action MenuSelectionChanged($source,$eventArgs)]"
HamburgerMenuHeaderTemplate="{StaticResource MenuHeaderTemplate}"
ItemTemplate="{StaticResource MenuItemTemplate}">
<mah:HamburgerMenu.Content>
<ContentControl Grid.Column="0" Grid.Row="1" x:Name="ActiveItem" />
</mah:HamburgerMenu.Content>
</mah:HamburgerMenu>
As you would have noticed couple of converters in here. We will get to that in a moment, but first let us define our MenuItemTemplate
and MenuHeaderTemplate
.
<!-- Menu Header Template -->
<DataTemplate x:Key="MenuHeaderTemplate">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="16" Foreground="White" Text="Sample App" />
</DataTemplate>
<!-- Menu Item Template -->
<DataTemplate x:Key="MenuItemTemplate">
<Grid x:Name="RootGrid" Height="48" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding Icon}"
Focusable="False" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding Label}" />
</Grid>
</DataTemplate>
At this point, we need to create our ViewModels for each of the menu items. As we notice from the templates above, there are couple of fields which are required for each of Menu Items – a Title, and a Icon (_Icon is not quite mandatory, depending on the type of Menu Item you are creating).
Destination ViewModels
Let us go ahead and define our ViewModels for Menu Items.
public class PageViewModelBase : Screen
{
public virtual string Title { get; }
public virtual object Icon { get; }
}
public class HomePageViewModel : PageViewModelBase
{
public override string Title => "Home";
public override object Icon => new PackIconMaterial { Kind = PackIconMaterialKind.Home };
}
public class ProfilePageViewModel : PageViewModelBase
{
public override string Title => "Profile";
public override object Icon => new PackIconMaterial { Kind = PackIconMaterialKind.AccountStar };
}
In the above example, we are using the MahApps.Metro.IconPack
for Icons, but that is only an implementation detail which could can easily vary. For sake of simplicity, I have omitted the Views here, but that could be found in the example solution given in the end of this article.
The Glue : ValueConverters and ActiveItem
We now have different ViewModels defined, and it is time to create the dynamic collection to bind to our HamburgerMenu Control. So, in our ShellViewModel, we will do the following.
public ShellViewModel()
{
MenuItems = new List<PageViewModelBase>
{
new HomePageViewModel(),
new ProfilePageViewModel()
};
}
public IEnumerable<PageViewModelBase> MenuItems { get; }
We are almost there, and now as the final step, we need to write our converters. There are two converters involved,
PageToMenuItemConverter
– Converts from ViewModel Collection toHamburgerMenuIconItem
.SelectedItemConverter
– Parse the ViewModel from SelectedItem
public class PageToMenuItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable<PageViewModelBase> nmItemCollection)
{
return nmItemCollection.Select(item => new HamburgerMenuIconItem
{
Tag = item,
Icon = item.Icon,
Label = item.Title
});
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is HamburgerMenuIconItem menuItem ? menuItem.Tag : value;
}
}
That’s all we need. Your menu would be up and running now. You can find the example code in my Github here