[Windows 10] Control SplitView

El control SplitView

Algunos desarrolladores lo conocerán como side menu, otros como navigation drawer y otros como hamburguer menu.
En todas las plataformas móviles se ha utilizado en muchas Apps como
una solución de navegación. Consiste en un Menu deslizante que aparece
de uno de los laterales al pulsar un botón situado habitualmente en un
lateral en la parte superior. Con la llegada de Windows 10 nos llega un
nuevo patrón de diseño y navegación en las Apps:

Las herramientas de desarrollo de Windows 10 Technical Preview nos
aportan nuevas herramientas, emuladores, APIs y controles. Entre el
conjunto del controles nuevos disponibles cabe destacar el control SplitView. Este control es nos permite crear un menu deslizante lateral.

Anatomía del control

Antes de entrar de lleno, en el uso del control vamos a realizar un
análisis de la anatomía del mismo. El control SplitView cuenta con una
propiedad Pane que permite establecer el contenido del panel lateral. Podemos mostrar el panel a izquierda o derecha mediante la propiedad PanePlacement y el ancho utilizando OpenPaneLength.

El panel lateral puede estar expandido o no. Este comportamiento se gestiona con la propiedad IsPaneOpen. En la imagen superior el menu esta contraido. Al pulsar sobre el botón Hamburguer:

El menu se expande. La propiedad IsPaneOpen se modificará a
True. Podemos además modificar aspectos visuales como el color de fondo,
el color de fondo del panel o el ancho que tendrá el menu contraido.

En este artículo vamos a utilizar el control SplitView como
base estructural para la navegación de nuestra aplicación utilizando
buenas prácticas como el patrón MVVM, inyección de dependencias o el uso
de servicios.

Utilizando el control SplitView

Para probar las posibilidades del control SplitView crearemos un nuevo proyecto UAP:

Nuestro objetivo en este ejemplo será utilizar como base de la App el
control SplitView. Contaremos con un menu lateral von múltiples
opciones que nos permitirán acceder a distintos apartados de la App.

Comenzamos añadiendo el control SplitView en nuestra vista principal:

<SplitView>
</SplitView>

Modificamos el control utilizando sus propiedades básicas para adaptarlo a nuestras necesidades:

<SplitView x:Name="Splitter"
     DisplayMode="CompactInline"  
     Background="{StaticResource BackgroundBrush}"      
     PaneBackground="{StaticResource BackgroundPaneBrush}"       
     PanePlacement="Left"      
     CompactPaneLength="60"
     OpenPaneLength="240"    
     IsPaneOpen="{Binding IsPaneOpen}">
</SplitView>

Hemos utilizado las siguientes propiedades:

  • PanePlacement: Posición del Panel lateral. Tenemos dos posibles opciones, izquierda (Left) o derecha (Right).
  • PaneBackground: Color de fondo del Panel lateral.
  • OpenPaneLength: Ancho en píxeles del Panel abierto.
  • CompactPaneLength: Ancho en píxeles del Panel cerrado.
  • Background: Color de fondo.
  • IsPanelOpen: Propiedad de tipo bool que nos permite tanto saber como establecer si el Panel esta abierto no.

Otra de las propiedades fundamentales del control es DisplayMode.
Esta propiedad nos permite indicar el comportamiento del menu lateral
tanto abierto como cerrado. Tenemos cuatro opciones disponibles:

  • Inline: El panel cerrado no aparece mientras que abierto pasa a ocupar el ancho establecido en la propiedad OpenPaneLength, dejando que el contenido ocupe el resto del espacio disponible.
  • Overlay: El comportamiento es similar a Inline con
    la diferencia(importante) del modo abierto. En modo abierto el contenido
    ocupa todo el espacio, el Panel se posiciona por encima del contenido.
  • Compact Inline: El Panel abierto tiene el ancho establecido en la propiedad OpenPaneLength, dejando al contenido el resto del espacio. Al cerrar el Panel, pasa a ocupar el ancho establecido en la propiedad CompactPaneLength.
  • Compact Overlay: En este caso el Panel se posiciona por encima del contenido en modo abierto.

En la ViewModel de la vista tendremos una propiedad bindeada a la propiedad IsPaneOpen para poder gestionar el estado del menu:

private bool _isPaneOpen;
 
public bool IsPaneOpen
{
     get
     {
          return _isPaneOpen;
     }
     set
     {
      _isPaneOpen = value;
      RaisePropertyChanged();
     }
}

Pero… ¿cómo modificamos el estado del Panel?.

Necesitamos definir un ToggleButton para gestionar el estado
del Panel. El botón contará con dos estados para mostrar una apariencia
diferencia al usuario cuando el Panel este abierto o cerrado. Al
pulsarlo ejecutará un comando en la ViewModel que modificará la propiedad IsPaneOpen:

<ToggleButton                
     x:Name="HamburguerButton"                
     Style="{StaticResource SymbolButton}"                       
     Command="{Binding HamburgerCommand}"          
     VerticalAlignment="Top"            
     Foreground="White"          
     Margin="0,5,0,0">   
     <ToggleButton.Content>   
          <Border Background="Transparent"     
               Width="40"       
               Height="40">          
               <FontIcon x:Name="Hamburger"        
                    FontFamily="Segoe MDL2 Assets"          
                    Glyph="" />
          </Border>     
     </ToggleButton.Content>
</ToggleButton>

El comando a ejecutar:

private ICommand _hamburgerCommand;
 
public ICommand HamburgerCommand
{
     get { return _hamburgerCommand = _hamburgerCommand ?? new DelegateCommand(HamburgerCommandExecute); }
}
 
private void HamburgerCommandExecute()
{
     IsPaneOpen = (IsPaneOpen == true) ? false : true;
}

El contenido del Panel lateral lo definiremos dentro de la propiedad Pane:

<SplitView.Pane>
     <RelativePanel>
          <ListView
             ItemsSource="{Binding MenuItems}"
             SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}"
             ItemTemplate="{StaticResource MenuItemDataTemplate}"
             SelectionMode="Single"
             IsItemClickEnabled="False"
             Margin="0, 50, 0, 0" />
     </RelativePanel>
</SplitView.Pane>

En  nuestro ejemplo mostraremos un menu con varias opciones. Para ello, utilizaremos una lista de elementos MenuItem:

public class MenuItem
{
     public string Icon { get; set; }
     public string Title { get; set; }
     public Type View { get; set; }
}

Cada MenuItem (clase definida dentro de la carpeta Models) contará con
el icono y título a mostrar además del tipo de la vista a la que
navegará. Al entrar en la vista cargaremos nuestro listado de opciones:

private void LoadMenu()
{
     MenuItems = new ObservableCollection<MenuItem>
     {
         new MenuItem
         {
              Icon = "",
              Title = "Home",
              View = typeof(HomeView)
         },
         new MenuItem
         {
              Icon = "",
              Title = "Standings",
              View = typeof(StandingsView)
         },
         new MenuItem
         {
              Icon = "",
              Title = "About",
              View = typeof(AboutView)
         }
     };
 
     SelectedMenuItem = MenuItems.FirstOrDefault();
}

Además de cargar el listado, seleccionamos el MenuItem por
defecto, que será el primero. De todo lo anterior la parte más destacada
es el icono. Con la llegada de las herramientas de Windows 10 Technical
Preview, nos llega un nuevo tipo de fuente a utilizar en nuestras Apps,
Segoe MDL2 Assets:

NOTA: Podemos utilizar la herramienta Mapa de
carácteres para ver todos los iconos disponibles en la nueva fuente.
Para utilizar el icono en vuestras Apps bastará con copiar el icono
deseado.

Una vez establecido el elemento de Menu seleccionado, realizamos la navegación a la vista correspondiente:

public MenuItem SelectedMenuItem
{
    get { return _selectedMenuItem; }
    set
    {
        _selectedMenuItem = value;
        RaisePropertyChanged();
 
        Navigate(_selectedMenuItem.View);
    }
}

El Panel lateral del SplitView se mantendrá visible siempre modificando la vista que se muestra al cambiar el elemento seleccionado del panel.

¿Cómo gestionamos esto?

El Frame es el encargado de contener y gestionar cada una de las páginas (Page).Tenemos un Frame
creado durante el arranque de la App. Sin embargo, queremos gestionar
la navegación de páginas desde la página que contiene el SplitView.
Debemos crear otro frame dentro del control SplitView:

<Frame x:Name="SplitViewFrame"
       Margin="0, 10" />

Para gestionar correctamente la navegación desde nuestras ViewModels
debemos tener acceso al Frame. Todas nuestras páginas heredan de una PageBase donde estableceremos el Frame del SplitView:

private Frame _splitViewFrame;
 
public Frame SplitViewFrame
{
     get { return _splitViewFrame; }
     set
     {
          _splitViewFrame = value;
 
      if(_vm == null)
           _vm = (ViewModelBase)this.DataContext;
 
      _vm.SetSplitFrame(_splitViewFrame);
     }
}

Al establecer el Frame del SplitView en el PageBase se establecerá el mismo en una propiedad de tipo Frame de la ViewModelBase, clase de la que heredan todas nuestras ViewModels:

public Frame SplitViewFrame
{
     get { return splitViewFrame; }
}

De esta forma desde la ViewModel, de forma muy sencilla podemos gestionar la navegación:

private void Navigate(Type view)
{
    var type = view.Name;
 
    switch (type)
    {
        case "HomeView":
            SplitViewFrame.Navigate(view, _driverStanding);
            break;
        case "StandingsView":
            SplitViewFrame.Navigate(view, _driverStanding);
            break;
        case "AboutView":
            SplitViewFrame.Navigate(view);
            break;
    }
}
El resultado es el siguiente:
Al pulsar el botón Hamburguer, modificamos la propiedad IsPaneOpen de la ViewModel que modificará el estado del SplitView cerrándolo:

Pulsándo sobre cualquier elemento del Panel lateral provocará el
cambio del elemento seleccionado, se llamará al método Navigate de la ViewModel provocando la navegación en el Frame contenido dentro del SplitView.

Volver atrás

En las Apps Windows Phone contamos con el botón físico para navegar
atrás. En el caso de Apps Windows Store, gestionamos el botón volver en
pantalla.

¿Cómo gestionamos el botón volver en Apps UAP?

Añadimos en nuestra nuestra vista principal,
donde definimos el SplitView un nuevo elemento visual que nos permita
gestionar la navegación atrás:

<RadioButton
     x:Name="BackButton" 
     Command="{Binding BackCommand}"  
     Background="{StaticResource SystemControlBackgroundAccentBrush}"           
     Width="240"               
     Margin="0, 50, 0, 0">                  
     <RadioButton.Tag>                      
          <TextBlock Text=""                   
                     FontFamily="Segoe MDL2 Assets"                               
                     VerticalAlignment="Center"               
                     HorizontalAlignment="Left"                
                     Margin="24, 0, 0 ,0" />                     
     </RadioButton.Tag>     
</RadioButton>

Utilizamos la nueva fuente Segoe MDL2 Assets para mostrar una flecha hacia atrás y vinculamos con un comando llamado BackCommand definido en la ViewModel:

private DelegateCommand _backCommand;
 
public ICommand NavigateCommand
{            
     get { return _navigateCommand = _navigateCommand ?? new DelegateCommand<MenuItem>(NavigateCommandExecute); }
}
 
private void BackCommandExecute()
{
     SplitViewFrame.GoBack();
     var selectedMenuItem = SplitViewFrame.CurrentSourcePageType;
     SelectedMenuItem = MenuItems.FirstOrDefault(mi => mi.View.Equals(selectedMenuItem));
}    
 
private bool BackCommandCanExecute()
{
     return SplitViewFrame.CanGoBack;
}

Se verifica si hay elementos en la colección de páginas y podemos
navegar hacia atrás para habilitar el botón. En caso de poner navegar,
se realiza la navegación atrás (GoBack) además de seleccionar el elemento correspondiente del menu lateral.

El resultado:

Este nuevo control utilizado junto a otras novedades como los
Adaptive Triggers o el nuevo Panel RelativePanel nos permitirá definir
interfaces de usuario que se adapten ante cualquier tamaño de pantalla y
dispositivo:

<VisualStateManager.VisualStateGroups>
     <VisualStateGroup>      
          <VisualState x:Name="wideState">                   
               <VisualState.StateTriggers>     
                    <AdaptiveTrigger MinWindowWidth="641" />
               </VisualState.StateTriggers>      
               <VisualState.Setters>         
                    <Setter Target="Splitter.DisplayMode" Value="CompactInline"/>
               </VisualState.Setters>
          </VisualState>              
          <VisualState x:Name="narrowState">            
               <VisualState.StateTriggers>    
                    <AdaptiveTrigger MinWindowWidth="0" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                    <Setter Target="Splitter.DisplayMode" Value="Overlay"/>
               </VisualState.Setters>
          </VisualState>
     </VisualStateGroup>    
</VisualStateManager.VisualStateGroups>

Podemos modificar el modo del SplitView segun el tamaño de la ventana.
Si reducimos el tamaño, podemos ocultar el menu lateral para aprovechar
mejor el espacio:

De igual forma, podemos modificar la posición de los elementos entre sí segun ciertas condiciones.

El resultado final lo podéis ver directamente:

Podéis descargar el ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *