Contenidos recomendados:
El famoso Hamburger Menu trajo muchas facilidades con la llegada del SDK de Windows 10, pero la verdad es que en un inicio no es del todo clara su implementacion. No tenemos un elemento predefinido para utilizarlo, esto es una dificultad a la hora de comprenderlo porque podemos encontrar distintas formas, pero a la vez es una gran ventaja en cuanto a flexibilidad permitiendo construirlo a nuestro gusto y adecuandose a cualquier necesidad.
También es un poco confuso utilizarlo si tenemos un proyecto con MVVM y no estamos muy familiarizados con la arquitectura.
Quiero aclarar que cada uno tiene sus formas de diseñar el Hamburguer Menu y todas son correctas.
Preparemos algunas cosas
Comenzamos preparando el proyecto para trabajar con MVVM. Creamos Models, ViewModels, Views.
Lo siguiente es la creación de las Views y sus respectivos ViewModels con la correcta implementación en ViewModelLocator y heredando de PageBase y ViewModelBase. Para este caso yo voy a crear 4 paginas.
Luego vamos a crear una carpeta Styles y dentro un ResourceDictionary con los siguientes estilos.
<SolidColorBrush x:Key="NavButtonPressedBackgroundBrush" Color="White" Opacity="0.3" />
<SolidColorBrush x:Key="NavButtonCheckedBackgroundBrush" Color="White" Opacity="0.2" />
<SolidColorBrush x:Key="NavButtonHoverBackgroundBrush" Color="White" Opacity="0.1" />
<Style x:Key="HamburgerRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonHoverBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonPressedBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NavButtonCheckedBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="PointerFocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Name="BackgroundGrid" Background="Transparent" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock FontSize="16" Height="16" Text="{TemplateBinding Tag}" FontFamily="Segoe MDL2 Assets" Margin="0,16" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White"/>
<ContentPresenter x:Name="ContentPresenter" FontSize="16" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" TextWrapping="Wrap" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="White" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Vamos a crear la clase PageType en Models.
public class PageType
{
public String Name { get; set; }
public String Icon { get; set; }
public Type Type { get; set; }
public Type NavigateType { get; set; }
}
Conozcamos un poco a SplitView
El SplitView es un control que nos va a permitir diseñar el Hamburger Menu en nuestras aplicaciones UWP. Aclaro que hay diferentes formas de implementar el Hamburger Menu algunas mas fáciles que otras pero todas son correctas dependiendo de lo que necesitemos. La que voy a mostrar en esta publicación es la que utilizo y que me resulta mas comoda para trabajar con MVVM.
El elemento principal para implementar el Hamburger Menu es el SplitView. Dentro de las propiedades que mas nos interesan se encuentran las siguientes.
Pane : Es un Panel que se despliega del lateral.
Content : Es el contenido que se mostrara en el SplitView. Puede varior dependiendo de lo seleccionado en el Pane.
PanePlacement : Permite establecer de que lateral se desplegará el Panel.
OpenPaneLength : Permite establecer el tamaño del Panel.
IsPaneOpen : Indica si Pane se encuentra desplegado.
Con esto aprendido ahora podemos definir la estructura que tendrá el SplitView.
<SplitView>
<SplitView.Pane>
</SplitView.Pane>
<SplitView.Content>
<Frame/>
</SplitView.Content>
</SplitView>
Y AHORA SI, A CODEAR!
Vamos a dejar para lo ultimo la parte de las vistas y del SplitView y vamos a comenzar por el code-behind.
ViewModels
En el MainViewModel vamos a crear las siguientes propiedades:
PageName: Sera el nombre de la pagina que se encuentre abierta en ese momento.
IsOpen: Indica si el Pane se encuentra abierto o cerrado.
CurrentPage: Establece la pagina que se mostrara en el Content del SplitView.
MenuList: Lista de elementos PageType que se mostrara en la parte superior de Pane.
SecondMenuList: Lista de elementos PageType que se mostrara en la parte inferior de Pane.
private string _pageName;
public string PageName
{
get { return _pageName; }
set
{
_pageName = value;
RaisePropertyChanged();
}
}
private bool _isOpen = false;
public bool IsOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
RaisePropertyChanged();
}
}
private Type _currentPage = typeof(Pagina1);
public Type CurrentPage
{
get { return _currentPage; }
set
{
_currentPage = value;
RaisePropertyChanged();
}
}
private ObservableCollection<PageType> _menuList;
public ObservableCollection<PageType> MenuList
{
get { return _menuList; }
set
{
_menuList = value;
RaisePropertyChanged();
}
}
private ObservableCollection<PageType> _secondMenuList;
public ObservableCollection<PageType> SecondMenuList
{
get { return _secondMenuList; }
set
{
_secondMenuList = value;
RaisePropertyChanged();
}
}
Lo siguiente que vamos a hacer también en MainPageViewModel es crear los siguientes métodos y comandos:
LoadMenu: Metodo que carga MenuList y SecondMenuList con los elementos PageType que queremos mostrar en el Pane.
NavigatePageCommand: Ejecuta la lógica para intercambiar el Content del SplitView dependiendo del elemento seleccionado.
CheckedRadioButtonPaneCommand: Cambia el estado IsOpen para abrir y cerrar Pane.
private void LoadMenu()
{
MenuList = new ObservableCollection<PageType>();
MenuList.Add(new PageType() { Name = "Pagina 1", Type = typeof(Pagina1), Icon = "[SIMBOLO]" });
MenuList.Add(new PageType() { Name = "Pagina 2", Type = typeof(Pagina2), Icon = "[SIMBOLO]" });
MenuList.Add(new PageType() { Name = "Pagina 3", Type = typeof(Pagina3), Icon = "[SIMBOLO]" });
SecondMenuList = new ObservableCollection<PageType>();
SecondMenuList.Add(new PageType() { Name = "Pagina 4", Type = typeof(Pagina4), Icon = "[SIMBOLO]" });
}
private DelegateCommand<PageType> _navigatePageCommand;
public ICommand NavigatePageCommand
{
get { return _navigatePageCommand = _navigatePageCommand ?? new DelegateCommand<Page>(NavigatePageCommandExecute); }
}
private void NavigatePageCommandExecute(PageType pagetype)
{
CurrentPage = pagetype.Type;
PageName = pagetype.Name;
IsOpen = false;
}
private DelegateCommand _checkedRadioButtonPaneCommand;
public ICommand CheckedRadioButtonPaneCommand
{
get { return _checkedRadioButtonPaneCommand = _checkedRadioButtonPaneCommand ?? new DelegateCommand(CheckedRadioButtonPaneCommandExecute); }
}
private void CheckedRadioButtonPaneCommandExecute()
{
IsOpen = !IsOpen;
}
En LoadMenu, las propiedades Icon las definimos con [SIMBOLO], en realidad hay que remplazarlos por caracteres de la fuente Segoe MDL2 Assets que se agregaron con la llegada del SDK de UWP para facilitar las cosas. Esta fuente provee caracteres que nos permiten agregar distintos iconos dentro de nuestra aplicación. Los podemos encontrar abriendo Mapa de caracteres y seleccionando la fuente Segoe MDL2 Assets.
Lo último que nos queda hacer en MainPageViewModel dentro de OnNavitatedTo es cargar las listas de elementos con LoadMenu().
public override Task OnNavigatedTo(NavigationEventArgs args)
{
LoadMenu();
return null;
}
Views
Comenzamos colocando un RowDefinitions en el Grid principal de MainPage.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="48"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
Grid.Row 0
En la row 0 se muestra un panel desde el cual aparece un RadioButton y un TextBlock.
El RadioButton nos permitirá abrir y cerrar el Pane del SplitView con el commando CheckedRadioButtonPaneCommand. Además el RadioButton tiene el Style HamburgerRadioButtonStyle que definimos al principio en el ResourceDictionary. En donde dice [SIMBOLO] va el símbolo del Menu que podemos encontrar con la herramienta antes presentada de Mapa de Caracteres.
El TextBlock mostrara el titulo de la pagina bindeada a PageName.
<RelativePanel x:Name="TitleBar" Background="#FF1F1F1F" Grid.Row="0" RequestedTheme="Dark">
<Grid Width="48" x:Name="HamburgerMenuButton">
<RadioButton Command="{Binding CheckedRadioButtonPaneCommand}" Style="{StaticResource HamburgerRadioButtonStyle}" GroupName="HamburgerMenuButton" Tag="[SIMBOLO]"/>
</Grid>
<TextBlock RelativePanel.RightOf="HamburgerMenuButton" RelativePanel.AlignVerticalCenterWith="HamburgerMenuButton" Text="{Binding PageName}" Margin="5"/>
</RelativePanel>
Grid.Row 1
En la row 2 aparece el famoso SplitView. En MainPage Establecemos El SplitView con la estructura principal de Pane y Content que anteriormente mencione.
<SplitView x:Name="splitView" Grid.Row="1" IsPaneOpen="{Binding IsOpen, Mode=TwoWay}">
<SplitView.Pane>
</SplitView.Pane>
<SplitView.Content>
</SplitView.Content>
</SplitView>
Content

En Content mostramos el contenido del SplitView y es acá donde se mostraran las distintas paginas mediante un Frame. La propiedad SourcePageType del Frame esta bindeada con CurrentPage de MainPageViewModel para permitir cambiar de pagina en el ViewModel.
<SplitView.Content>
<Frame SourcePageType="{Binding CurrentPage, Mode=TwoWay}"/>
</SplitView.Content>
Pane

En pane van a estar las dos listas con los elementos PageType que cree y cargue en el MainPageViewModel. Estarán dentro de un ItemControl con RadioButtons que aplican el estilo HamburguerRadioButtonStyle que creamos en el diccionario de recursos. Además con NavigatePageCommand pasando como parámetro del Type al hacer tap sobre el RadioButton cambiara de pagina con CurrentPage.
<SplitView.Pane>
<Grid Background="Black">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding MenuList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton x:Name="Pagina1RadioButton" Command="{Binding DataContext.NavigatePageCommand , ElementName=splitView}" CommandParameter="{Binding }" Style="{StaticResource HamburgerRadioButtonStyle}" Content="{Binding Name}" GroupName="HamburgerMenu" Tag="{Binding Icon}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding SecondMenuList}" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton x:Name="Pagina1RadioButton" Command="{Binding DataContext.NavigatePageCommand , ElementName=splitView}" CommandParameter="{Binding }" Style="{StaticResource HamburgerRadioButtonStyle}" Content="{Binding Name}" GroupName="HamburgerMenu" Tag="{Binding Icon}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</SplitView.Pane>
En el resto de las Views Simplemente les asignamos un color de fondo y un TextBlock centrado para identificar cuando cambiamos de pagina.
Y Listo ya tenemos un HamburgerMenu lista para trabajar con MVVM!.
Descargar código























































































