> For the complete documentation index, see [llms.txt](https://elguerre.gitbook.io/de0an/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://elguerre.gitbook.io/de0an/capitulo-5-la-construccion.-implementando-requisitos/estructura-de-una-pagina.md).

# Estructura de una Página

Una página o vista se compone de la página en sí y de un ViewModel que es quien le suministra los datos. A su vez, un ViewModel incluye: un constructor, métodos de inicio o carga, comandos o métodos para la ejecución de acciones, propiedades, etc. Como podemos prever muchos de ellos utilizarán parte de la misma lógica, por lo que agruparemos y unificaremos todo ello en una clase base *ViewModelBase*. Reduciremos código, tiempo de implementación y obtendremos homogeneización de código, entre otras muchas mejoras.

La estructura principal de cada página será la misma para todas, por lo que también estableceremos un diseño homogéneo para ellas.

## ViewModel Base

Un ViewModel *ViewModelX* precisa de una interfaz *IViewModelX* para la resolución de dependencias por el contenedor de inyección de dependencias (ID) *ViewModelLocator*. Por tanto, un ViewModel base (en adelante, *ViewModelBase)* es la clase base que implementará cada uno de ellos. De forma análoga, una interfaz base *IViewModel*, es la interfaz principal o base que, a su vez, implementará la interfaz específica de cada *ViewModelX*. La siguiente figura ilustra más claramente este propósito.

![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-be3abed0e5086a3d0e18b7de910467aa8e1cec45%2Fimage6.png?alt=media)

**Figura 06.- ViewModelBase e IViewModels**

**IViewModel**.

```csharp
public interface IViewModelBase
{
    void Initialize(Func<object> initDelegate);
    Task LoadDataAsync();
    void Refresh();
}
```

**ViewModelBase**.

```csharp
public class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase, INavigable, IViewModel
{
    public virtual Task OnNavigatedFromAsync(
        IDictionary<string, object> state,
        bool suspending)
        => Task.CompletedTask;
    public virtual async Task OnNavigatedToAsync(object parameter,
        NavigationMode mode,
        IDictionary<string, object> state)
        => await Task.CompletedTask;
    public virtual async Task OnNavigatingFromAsync(
        NavigatingEventArgs args)
        => await Task.CompletedTask;
    public INavigationService NavigationService
    {
        get { return BootStrapper.Current.NavigationService; }
        set { }
    }
    public IDispatcherWrapper Dispatcher { get; set; }
    public IStateItems SessionState { get; set; }
    public virtual void Initialize(Func<object> initDelegate) {
    }
    public virtual Task LoadDataAsync() => Task.CompletedTask;
    public virtual void Refresh() { }
}
```

Como estamos utilizando ***MVVM Light***, asumimos que *ViewModelBase* tiene que heredar también de *GalaSoft.MvvmLight.ViewModelBase*.

El método *Initialize* recibe como parámetro, **Func\<object>**, lo que significa que delega la responsabilidad en la clase que lo invoca. *ViewModelBase,* por tanto, tendrá que ejecutar (“invoke”) cada delegado y trabajar con el valor devuelto.

```csharp
var arg = initializeDelegate.Invoke();
if (null != arg)
{
    . . .
}
```

La ventaja de este parámetro radica en la facilidad para la creación de pruebas unitarias, donde vamos a poder simular una carga de datos o, proporcionar un listado de valores estáticos.

```csharp
[TestMethod]
public void InitializeTest()
{
    var vm =
        ViewModelLocator.Current.GetInstance<IProjectViewModel>();
    vm.Initialize(() =>
    {
        // Acceso a datos para cargar un listado de proyecto.
    });
    Assert.IsNotNull(vm.SelectedProject, "Proyecto no cargado");
    Assert.IsNotNull(vm.Tasks, "Tareas no disponibles");
    Assert.IsTrue(vm.Tasks.Count > 0, "Tareas no encontradas");
}
```

Como hemos podido observar, la interfaz *IViewModel*, implementa también una interfaz *INavigable*, que es la interfaz que utiliza **Template 10** para la navegación entre páginas. Donde:

* OnNavigatedFromAsync. Son los métodos invocados al iniciar la navegación en una página. La página destino utiliza la sobrecarga de este método para cargar e inicializar sus datos.
* OnNavigatedToAsync. Es el método invocado al finalizar la navegación en otra página. La página origen, puede sobrecargar este método para liberar sus recursos.

Como *ViewModelBase* implementa la interfaz *IViewModel*, no necesitaremos incluir código en nuestras clases *ViewModel*. Bastará con hacer que todos ellos, sin excepción, hereden de *ViewModelBase*.

En adelante, utilizaremos los términos ***ViewModel o ViewModels***, para referirnos a Vista-Modelo o Vistas-Modelos, por tratarse de términos ampliamente conocidos y compartidos por la comunidad de programadores.

## Página

Cada página la dividiremos en tres secciones: cabecera, cuerpo o contenido y pie incluidas dentro de un control *Grid* principal al que denominaremos ***ContentRoot***, donde:

* Cabecera. Incorpora las opciones de menú con los comandos o acciones disponibles para cada página. Está compuesta por un control de tipo *CommandBar*, denominado *PageHeader* (perteneciente a los controles de *Template 10)* que se integra perfectamente con el marco base de nuestra aplicación (incluido con el menú *Hamburger*). Nos referiremos a ésta con el nombre ***TopAppBar***.
* Cuerpo. Contendrá la parte específica de cada página pudiendo cambiar entre todas ellas.
* Pie. Incorpora las opciones de menú o comandos de cada página y es un control *CommandBar* situado en la parte inferior de cada página. Nos referiremos a él con el nombre ***BottomAppBar***.

Existe otra sección, propia de cada página XAML, donde se sitúan sus recursos y viene determinada por la etiqueta XML *Page.Resources*. La incluiremos al comienzo de cada página antes de nuestro *ContentRoot*. Como primer elemento dentro del *ContentRoot*, estará el control *VisualStateManager* que lo utilizaremos para modificar el comportamiento y la disposición de otros controles en tiempo de ejecución.

El siguiente fragmento de código ejemplifica el esqueleto de cada página.

```xml
<Grid x:Name="ContentRoot">
    <VisualStateManager.VisualStateGroups>
    </VisualStateManager.VisualStateGroups>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <ctrl10:PageHeader x:Name="TopAppBar"
        Text="Page1 Title"
        PrimaryCommandsVisibility="Visible">
    </ctrl10:PageHeader>
    <!-- Contenido propio de cada página -->
    <CommandBar x:Name="BottomAppBar"
        Grid.ColumnSpan="2"
        Grid.Row="2"
        Visibility="Collapsed">
    </CommandBar>
</Grid>
```

Diseñaremos nuestras páginas con una cabecera o un pie, pero no con ambos. Dependerá de si la página se presenta en un PC (o *Desktop*) o en un teléfono móvil, en tiempo de ejecución.

### Disparadores adaptativos

El control *VisualStateManager* nos permitirá mostrar una de las dos barras de comandos dependiendo del tipo de plataforma en la que se ejecute la aplicación. Es decir, tendremos dos estados o dos escenarios, uno para PCs y otros para móviles. Por tanto, crearemos un grupo *DeviceVisualStateGroup* al que añadiremos estos dos estados que representaremos como *DesktopVisualState* y *MobileVisualState* respectivamente.

Para determinar en qué escenarios nos encontramos (en lugar de utilizar un *Storyboard*, como vimos al redondear el botón en el ejemplo del capítulo 2), necesitaremos disparadores o desencadenadores de estado (o *StateTrigger*), que son componentes que se caracterizan por la interacción con el sistema sin la necesidad de tener que especificar código C# en el ViewModel. Disponemos de uno conocido como adaptativo (o *AdaptiveTrigger)*, que nos facilita la interacción con los puntos de interrupción (320, 720 y 1024) ya conocidos.

Podremos cambiar la disposición y comportamiento de los controles a medida que se cambia el tamaño de la ventana en relación a estos tres puntos. Por ejemplo, la aplicación de correos de Windows, presenta dos vistas diferentes dependiendo del tamaño de la ventana. La siguiente figura es un ejemplo de ello.

![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-234740cee94577674581b09ef88344503629a24e%2Fimage7.png?alt=media)

**Figura 07.- Aplicación de correo con diferentes vistas según puntos de interrupción**

Entendiendo esta funcionalidad, podemos mostrar la barra *BottomAppBar* cuando el ancho de la pantalla sea inferior a 320 pixeles y para el resto de tamaños mostrar la barra *TopAppBar*. Sin embargo, un usuario en un PC, unas veces la verá en la parte superior y otras en la parte inferior. Lo que puede llevarse a confusión. Igualmente ocurriría en un móvil si permitimos el cambio de orientación de la pantalla.

Lo que realmente buscamos son comportamientos distintos en un PC y en un teléfono móvil, por lo que la solución pasa por implementar un disparador personalizado, al que denominaremos ***DeviceStateTrigger***.

```csharp
public class DeviceStateTrigger : Windows.UI.Xaml.StateTriggerBase
{
    private static DeviceFamilies deviceFamily;
    private bool isActive;
    public event EventHandler IsActiveChanged;
    static DeviceStateTrigger()
    {
        deviceFamily = Template10.Utils.DeviceUtils.CurrentDeviceFamily;
    }
    public DeviceStateTrigger() { }
    public DeviceFamilies Family
    {
        get { return (DeviceFamilies)GetValue(FamilyProperty); }
        set { SetValue(FamilyProperty, value); }
    }
    public static readonly DependencyProperty FamilyProperty =
        DependencyProperty.Register(nameof(Family), typeof(DeviceFamilies), typeof(DeviceStateTrigger),
            new PropertyMetadata(DeviceFamilies.Unknown, OnDeviceFamilyPropertyChanged));
    private static void OnDeviceFamilyPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var obj = (DeviceStateTrigger)d;
        var val = (DeviceFamilies)e.NewValue;
        obj.IsActive = (val == deviceFamily);
    }
    public bool IsActive
    {
        get { return isActive; }
        private set
        {
            if (isActive != value)
            {
                isActive = value;
                base.SetActive(value);
                IsActiveChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
}
```

Como vemos en el código, la propiedad *IsActive* es la responsable de que el disparador se lance. Adicionalmente, hemos incluido un evento *IsActiveChanged*, para tener la posibilidad de añadir lógica en el ViewModel.

De no haber utilizado Template 10, tendríamos que haber definido un nuevo enumerado para denotar a los distintos dispositivos y, haber utilizado la propiedad *Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily (en lugar de CurrentDeviceFamily)* para obtener una cadena con el identificador de cada familia de dispositivos.

### Comportamientos

Ahora que ya sabemos cómo mostrar u ocultar una u otra barra de comandos, nos encontramos con nuevos inconvenientes:

1. La barra de comandos *TopAppBar* no se puede ocultar dado que es parte del marco de la aplicación y, además, muestra el título de cada página.
2. Tenemos dos barras de comandos con los mismos botones, y código XAML repetido.

La solución al primer problema es fácil, utilizamos la propiedad *PrimaryCommandsVisibility* para ocultar los botones y problema resuelto.

Para resolver el segundo, podemos pensar en mover la barra de comandos de posición, sin embargo, *TopAppBar* está integrada con el menú *Hamburger* y siempre aparecerá como cabecera. Entonces ¿Cómo lo solucionamos? Una nueva plantilla *DataTemplate* o una nueva plantilla de control (o *ControlTemplate*) puede ser la solución, pero una barra de comandos no permite estas asignaciones o cambios de estilos.

Recurriremos, a los comportamientos (o *Behaviors*), que no son ni más ni menos que componentes que van a permitir agregar conductas a un elemento únicamente en código XAML.

En el SDK de Windows se encuentra disponibles los siguientes **comportamientos**:

* DataTriggerBehavior. Permite lanzar las acciones en base al comportamiento de los datos.
* EventTriggerBehavior. Permite lanzar las acciones en base a la ejecución de eventos.
* IncrementalUpdateBehavior. Permite asignar fases a los distintos componentes de los elementos de una lista para otorgar prioridad para su carga.

Cada uno de los comportamientos pueden, a su vez, desencadenar **acciones**:

* CallMethodAction. Invocar a un método del ViewModel.
* ChangePropertyAction. Cambiar una propiedad del elemento que desencadenó el comportamiento.
* GoToStateAction. Cambiar el estado visual de la página.
* InvokeCommandAction. Ejecutar un comando determinado del ViewModel.
* NavigateToPageAction. Navegar a otra página.

Es muy probable que utilizando alguno de estos comportamientos consigamos nuestro objetivo. A pesar de ello, tendríamos que incluir parte de lógica en cada ViewModel, en algún Servicio o en clase común. Para evitarlo y conseguir una solución más genérica, implementaremos un nuevo comportamiento al que designaremos ***CommandBarTemplateBehavior*** y, cuya finalidad será crear una barra de comandos a partir de una plantilla *DataTemplate*.

```csharp
public class CommandBarTemplateBehavior : DependencyObject, IBehavior
{
    public DependencyObject AssociatedObject { get; set; }
    public void Attach(DependencyObject associatedObject)
    {
        this.AssociatedObject = associatedObject;
        if (Template != null)
        {
            var obj = Template.LoadContent();
            if (obj is CommandBar)
            {
                var templateCmdBar = (CommandBar)obj;
                var cmdBar = AssociatedObject as CommandBar;
                if (cmdBar.PrimaryCommands?.Count == 0)
                    foreach (var b in templateCmdBar.PrimaryCommands)
                        cmdBar.PrimaryCommands.Add(b);
                if (cmdBar.SecondaryCommands?.Count == 0)
                    foreach (var b in templateCmdBar.SecondaryCommands)
                        cmdBar.SecondaryCommands.Add(b);
            }
        }
    }
    public void Detach() { }
    public DataTemplate Template
    {
        get { return (DataTemplate)GetValue(TemplateProperty); }
        set { SetValue(TemplateProperty, value); }
    }
    public static readonly DependencyProperty TemplateProperty =
        DependencyProperty.Register(
            nameof(Template),
            typeof(DataTemplate),
            typeof(CommandBarTemplateBehavior),
            new PropertyMetadata(null));
}
```

Es importante destacar la implementación de los métodos *Attach* y *Detach*, correspondientes a la interfaz *Microsoft.Xaml.Interactivity.IBehavior* y la propiedad *Template*. El método *Attach*, recibe por parámetro el control que lanza el comportamiento y la propiedad *Template*, que define de la barra de comandos mediante una plantilla *DataTemplate*.

Una vez implementado el disparador adaptativo y este nuevo comportamiento, el formato de la página es el siguiente:

```xml
<Page x:Class="ElGuerre.Taskin.Uwp.Views.Sample1"
    ...
    DataContext="{Binding Page1, Mode=OneWay, Source={StaticResource Locator}}">
    <Page.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="MainCommandBarTemplate">
                <CommandBar>
                    <CommandBar.PrimaryCommands>
                        <!-- Añadir botones -->
                    </CommandBar.PrimaryCommands>
                </CommandBar>
            </DataTemplate>
        </ResourceDictionary>
    </Page.Resources>
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="DeviceVisualStateGroup">
                <VisualState x:Name="DesktopVisualState">
                    <VisualState.StateTriggers>
                        <t:DeviceStateTrigger Family="Desktop" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!-- Cambios cuando Desktop -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="MobileVisualState">
                    <VisualState.StateTriggers>
                        <t:DeviceStateTrigger Family="Mobile" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter
                            Target="TopAppBar.PrimaryCommandsVisibility"
                            Value="Collapsed" />
                        <Setter Target="TopAppBar.EllipsisVisibility"
                            Value="Collapsed" />
                        <Setter Target="BottomAppBar.Visibility"
                            Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ctrl10:PageHeader x:Name="TopAppBar"
            Text="Page1 Title"
            PrimaryCommandsVisibility="Visible">
            <i:Interaction.Behaviors>
                <b:CommandBarTemplateBehavior
                    Template="{StaticResource MainCommandBarTemplate}" />
            </i:Interaction.Behaviors>
        </ctrl10:PageHeader>
        <!-- Contenido propio de cada página -->
        <CommandBar x:Name="BottomAppBar"
            Grid.ColumnSpan="2"
            Grid.Row="2"
            Visibility="Collapsed">
            <i:Interaction.Behaviors>
                <b:CommandBarTemplateBehavior
                    Template="{StaticResource MainCommandBarTemplate}" />
            </i:Interaction.Behaviors>
        </CommandBar>
    </Grid>
</Page>
```

Esta misma solución será la que apliquemos a todas las páginas por igual. Como podemos observar, únicamente tendremos que preocuparnos de incluir el contenido propio de cada página.

{% hint style="info" %}
**Nota**: Hemos utilizado un control *Grid* para definir y homogeneizar de manera estándar las páginas, sin embargo, un control *RelativePanel* también suele ser un control muy apropiado para este propósito.
{% endhint %}

Otra alternativa para determinar la disposición y/o comportamiento de los controles podría haber sido la creación de distintas vistas por familias de dispositivos. Donde para cada uno de ellos definimos una vista (página sin código C# asociado), con una nomenclatura específica:

* Por nombre de fichero. \[NombrePágina].DeviceFamily-\[FamiliaDispositivo].xaml
* Por nombre de carpeta. DeviceFamily-\[ FamiliaDispositivo]\NombrePagina.xaml

Esta solución igualmente buena, nos obligaría a tener que crear una vista por cada tipo de dispositivo. Está pensada especialmente para aquellos casos donde existe una alta complejidad o disparidad entre las interfaces de las familias de dispositivos.

Para más información sobre la personalización con XAML, visitar la página: <https://docs.microsoft.com/es-es/windows/uwp/layout/layouts-with-xaml>.
