> 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-8-la-construccion-proyectos-tareas-y-pomodoros/proyectos.md).

# Proyectos

Un proyecto es una agrupación o categoría de tareas y constituye el tipo base en la que se apoya el resto de entidades. A partir de su representación en la interfaz de usuario se navegará al resto, es decir, la representación de un proyecto dará lugar tanto a su edición y detalle como a la navegación de sus tareas, listado y edición de las mismas.

Para el acceso a datos de una API con Azure Storage Explorer es necesario tener como requerimiento la cadena de conexión “MS\_AzureStorageAccountConnectionString” en el web.config.

Añadir la referencia al paquete NuGet: “WindowsAzure.Storage. Lo que evitará errores del tipo: “*Method not found: 'System.Threading.Tasks.Task Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.UploadFromStreamAsync(System.IO.Stream)'*”

## ­­Edición de un proyecto (ProjectEdiction)

La edición de un proyecto requiere de la implementación de una pantalla simple para la creación y modificación de los datos que lo forman. El conjunto de métodos, comandos y propiedades que su tratamiento se encuentran en su *ViewModel* tal y como se agrupa en la siguiente figura:

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

**Figura 02.- Métodos, comandos y propiedades del ViewModel de edición de proyectos**

Y a continuación la representación de la edición de los datos para un proyecto.

**Tabla 01.- Entidad Proyecto y pantalla de edición**

| Entidad Proyecto                                                                                                                                                                                     | Representación gráfica en IU                                                                                                                                                                         |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-433468285f7695a8d616637f39d23443bf703a46%2Fimage3.PNG?alt=media) | ![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-670988434ecd1e57753c30ccf752f94706cc7b47%2Fimage4.PNG?alt=media) |

Nos referiremos a la pantalla de edición de proyectos, al igual que a cualquier otra página dividiéndola en tres partes: Cabecera, cuerpo o contenido y pie u opciones de menú.

### Cabecera

La cabecera para la edición de un proyecto, al igual que en cualquier otra pantalla, contiene el título de la aplicación. El cual está representado por un control de tipo “TextBlock”, cuyo valor es recuperado del fichero de recursos “Resources.resw” a través de la clase “LocalizedString”.

```
<StackPanel Grid.Row="0" Margin="19,0,0,0">
  <TextBlock Text="{Binding Path=AppTitle, Source={StaticResource LocalizedStrings}}"
         Margin="0,12,0,0"
         Style="{ThemeResource TitleTextBlockStyle}" />
  <TextBlock Text="{Binding Path=NewProject, Source={StaticResource LocalizedStrings}}"
         Visibility="{Binding IsNew, Converter={StaticResource BooleanToVisibilityConverter}}"
         Style="{ThemeResource TitleTextBlockStyle}" />
</StackPanel>
```

De igual forma, al crear un nuevo proyecto mostraremos el literal “Nuevo proyecto” (o “New Project”), indicándolo. Conseguiremos este comportamiento gracias al convertidor “**BooleanToVisibilityConverter**” el cual, trasforma un valor booleano en uno de valores del enumerado “Visibility”. Es decir, el valor “true” lo trasforma en “Visible” y el valor “false” en “Collapsed”.

{% hint style="info" %}
**Nota**: Usaremos la nomenclatura “**\<TipoOrigen>To\<TipoDestino>Converter**” en el nombre de un convertidor para denotar el tipo de conversión que realiza.
{% endhint %}

### Contenido

El cuerpo o contenido, es la parte principal de cada pantalla y es la que tiene mayor complejidad a la hora de llevar a cabo su construcción.

Hasta ahora, en nuestra pantalla “Acerca de” no hemos hecho uso de controles TextBox, ComboBox, ni otros controles, es a partir de aquí cuando comenzaremos a implementar la interacción con el usuario y es donde mayor atención tendremos que poner. Es donde comenzaremos a profundizar en estilos, acciones y comportamientos, es decir, nos encontramos frente a la implementación de los elementos visuales y prototipado, de acuerdo a nuestro “Microsoft Design Style”. Tendremos que comenzar a tomar decisiones o alternativas en cuanto al camino a seguir para optar a la mejor opción de codificación frente a determinadas situaciones.

{% hint style="info" %}
**Nota**: Hay veces que nos obsesionamos con hacer algo de cierta manera sin darnos cuenta de que puede llegar a ser extremadamente complejo. Llegado este momento lo mejor es levantar la cabeza, despejarnos y pasar a otra tarea. ¡Podremos volver a retomarla nuevamente más tarde!

Intentemos perseguir siempre el principio KISS, (¡del inglés *Keep It Simple, Stupid*! “¡Hazlo sencillo, estúpido!”). El cual, nos indica que todo funciona mejor si se mantiene simple. ¡Una vez más, recordémonos a nosotros mismos, ser eficientes!
{% endhint %}

Comenzaremos, modificando el estilo de las cajas de texto (o *TextBox*). En primer lugar, cambiaremos el fondo blanco, para que éste sea negro, o lo que es lo mismo, el mismo fondo que el resto de la aplicación. Para conseguir este efecto tendremos que aplicar nuevos estilos:

1. Obtener el estilo/plantilla predeterminada del control TextBox y generarla en el fichero “**AppStyles.xaml**” del proyecto Windows Phone. La siguiente figura resume brevemente estos pasos:

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

**Figura 03.- Obtención del estilo predeterminado de TextBox desde Blend**

2. Modificar este nuevo estilo “DefaultTextBoxStyle”, sobrescribiendo las siguientes instrucciones:

```
<Setter Property="Foreground" Value="{ThemeResource TextBoxForegroundThemeBrush}"/>
<Setter Property="Background" Value="{ThemeResource TextBoxBackgroundThemeBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource TextBoxBorderThemeBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundColor}" />
<Setter Property="Background" Value="{StaticResource PhoneBackgroundColor}"/>
<Setter Property="BorderBrush" Value="{ThemeResource PhoneForegroundBrush}"/>
```

Y añadiendo esta otra para el estado (o “VisualState”) de “Focused”:

```
<VisualState x:Name="Focused">
  <Storyboard>
    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement" Storyboard.TargetProperty="Foreground">
      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxForegroundThemeBrush }" />
    </ObjectAnimationUsingKeyFrames>
  </Storyboard>
</VisualState>
```

Tras los cambios realizados, podemos comprobar los cambios de estilo tanto para el tema "Dark" como para el “Light”.

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

**Figura 04.- Resultado de los TextBox personalizados en temas Dark y Light**

Finalmente, definiremos algunos estilos más, basados en el recientemente creado para fijar la longitud máxima de su contenido, alto, si acepta o no retorno de carro, etc. El siguiente código, define varios estilos básicos que utilizaremos en las pantallas de edición:

```
<Style x:Key="TextTitleMaxLength75" BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
  <Setter Property="MaxLength" Value="75" />
</Style>

<Style x:Key="TextDetailMaxLength100" BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
  <Setter Property="TextWrapping" Value="Wrap" />
  <Setter Property="MaxLength" Value="100" />
  <Setter Property="MaxHeight" Value="86" />
  <Setter Property="VerticalAlignment" Value="Stretch" />
  <Setter Property="AcceptsReturn" Value="True" />
</Style>

<Style x:Key="TextDetailMaxLength250" BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
  <Setter Property="TextWrapping" Value="Wrap" />
  <Setter Property="MaxLength" Value="250" />
  <Setter Property="MaxHeight" Value="172" />
  <Setter Property="VerticalAlignment" Value="Stretch" />
  <Setter Property="AcceptsReturn" Value="True" />
</Style>

<Style x:Key="TextMaxLength" BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
  <Setter Property="TextWrapping" Value="Wrap" />
  <Setter Property="VerticalAlignment" Value="Stretch" />
  <Setter Property="AcceptsReturn" Value="True" />
</Style>
```

De forma equivalente, crearemos controles desplegables (o “ComboBox”) específicos para que estos incluyan una imagen asociada al texto.

A diferencia con los estilos de los *TextBox*, incluiremos estos otros en un nuevo fichero/diccionario de recursos denominado “**CommonAppStyles.xaml**”, el cual situaremos en el proyecto “Shared” y cuyo objetivo es el de contener todos aquellas plantillas o estilos comunes para Window Phone y Windows. Podríamos haber hecho lo mismo con los *TextBox*, sin embargo, utilizan internamente estilos concretos de Windows Phone y, por tanto, este no es su sitio.

Para la selección de un tipo de proyecto o tarea recurriremos a *ComboBox*es, que diseñaremos manualmente a través del siguiente estilo e identificaremos con la clave “**ComboBoxSchema**”:

Utilizar FontIcons en lugar de BitmapIcons, como tenía pensado en un momento inicial

Recordemos para usar iconos u otras imágenes en nuestra aplicación, tenemos las alternativas: SymbolIcon, FontIcon, PathIcon y BitmapIcon.

Se recomienda utilizar siempre que sea posible las alternativas *SymbolIcon* o *FontIcon*. Además de garantizar un mejor rendimiento, evita el tener que generar distintas imágenes para los distintos temas utilizados en la aplicación.

Representaremos los mismos de la manera más recomendada ganando así en rendimiento y evitando tener varias imágenes para los temas Claro y Oscuro (*Light* y *Dark*). Utilizaremos, por tanto, la alternativa de ***FontIcons.*** Las tablas 02 y 03 representan estos iconos con sus respectivos valores Hexadecimales y Unicode.

**Tabla 02.- Iconos para tipos de proyectos**

| Icono | Nombre           | <p>Hexadecimal</p><p>(XAML)</p> | <p>Unicode</p><p>(C#)</p> |
| ----- | ---------------- | ------------------------------- | ------------------------- |
|      | Default          | \&#xE179;                       | \uE179                    |
|      | Privado/Personal | \&#xE1E0;                       | \uE1E0                    |
|      | Trabajo          | \&#xE822;                       | \uE822                    |
|      | Deporte          | \&#xE805;                       | \uE805                    |
|      | Viajes           | \&#x E709;                      | \uE709                    |
|      | Otros            | \&#xEA86;                       | \uEA86                    |

**Tabla 03.- Iconos para tipos de tareas**

| Icono | Nombre        | <p>Hexadecimal</p><p>(XAML)</p> | <p>Unicode</p><p>(C#)</p> |
| ----- | ------------- | ------------------------------- | ------------------------- |
|      | Llamar a      | \&#xE13A;                       | \uE13A                    |
|      | Email para    | \&#xE119;                       | \uE119                    |
|      | Vacaciones    | \&#xE1C3;                       | \uE1C3                    |
|      | Vinculo a     | \&#xE167;                       | \uE167                    |
|      | Comer con     | \&#xED56;                       | \uED56                    |
|      | Otros         | \&#xEA86;                       | \uEA86                    |
|      | Leer          | \&#xE7BC;                       | \uE7BC                    |
|      | Deporte       | \&#xE805;                       | \uE805                    |
|      | Hablar con    | \&#xE939;                       | \uE939                    |
|      | Hacer / To-Do | \&#xE930;                       | \uE930                    |
|      | Viajar        | \&#x E709;                      | \uE709                    |

```
<Style x:Key="ComboBoxSchema" TargetType="ComboBox">
  <Setter Property="VerticalAlignment" Value="Bottom" />
  <Setter Property="ItemTemplate">
    <Setter.Value>
      <DataTemplate>
        <StackPanel Orientation="Horizontal">
          <Border Width="35" Height="35">
            <Image Width="32" Height="32" Stretch="Fill"
                 Source="{Binding Converter={StaticResource ThumbnailSchemaTypeConverter}}"/>
          </Border>
          <TextBlock Margin="12,0"
                 Text="{Binding Converter={StaticResource EnumToLocalizatedConverter}}" />
        </StackPanel>
      </DataTemplate>
    </Setter.Value>
  </Setter>
</Style>
```

Este estilo incluye dos convertidores (o Converters): ThumbnailSchemaTypeConverter y EnumToLocalizatedConverter:

* **ThumbnailSchemaTypeConverter**. Transforma un proyecto o una tarea en una *Uri* que representa a un icono según su tipo.

```
public object Convert(object value, Type targetType, object parameter, string culture)
{
  if (value != null)
  {
    object url = null;
    if (value is Data.Schemas.TaskType)
    {
      url = string.Format(Constants.TASK_TYPES_ICON_PATH_FILES_FORMAT, (Data.Schemas.TaskType)value);
    }
    else if (value is Data.Schemas.ProjectType)
    {
      url = string.Format(Constants.PROJECT_TYPES_ICON_PATH_FILES_FORMAT, (Data.Schemas.ProjectType)value);
    }
    if (null != url)
      return new ThumbnailUriConverter().Convert(url, targetType, parameter, culture);
  }
  return DependencyProperty.UnsetValue;
}

public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
  throw new NotImplementedException();
}
```

A su vez, este convertidor se apoya en otro, “**ThumbnailUriConverter**”, que no es ni más ni menos que un convertidor de imágenes predeterminado utilizado a lo largo de toda la aplicación. Transforma una *Uri* relativa en *Uri* absoluta y en caso de que ésta sea nula se obtiene la *Uri* indicada por el parámetro (“parameter”), que deberá coincidir con la *Uri* de una imagen predeterminada y fija para la aplicación. De esta manera, siempre se mostrará una imagen.

```
public class ThumbnailUriConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    if (value != null && value.ToString() != String.Empty)
    {
      Uri uri = null;
      string url = value.ToString();
      if (url.StartsWith("/"))
      {
        uri = new Uri(string.Concat("ms-appx://", url), UriKind.Absolute);
      }
      else
      {
        uri = new Uri(url, UriKind.Absolute);
      }
      return uri;
    }
    else
    {
      if (null != parameter && parameter is string)
        return new Uri(string.Concat("ms-appx://", parameter.ToString()), UriKind.Absolute);
      else
        return DependencyProperty.UnsetValue;
    }
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    if (value is BitmapImage)
    {
      var image = value as BitmapImage;
      return image.UriSource.AbsoluteUri;
    }
    return string.Empty;
  }
}
```

Para que la conversión sea correcta, se requiere que tanto Proyectos como tareas, se encuentren ubicados físicamente en dichas Uris, tal y como puede verse en la siguiente figura:

![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-6e3ce1ba6bd5637fffaca4bfabd300f2746c63b9%2Fimage8.PNG?alt=media) ![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-8d1f03086ece7a1e35b44a8e9743dcd9f999e5b6%2Fimage9.PNG?alt=media)

**Figura 05.- Iconos para tipos de Proyectos y Tareas**

* **EnumToLocalizatedConverter**. Transforma un enumerado en la correspondiente cadena de texto indicada en el fichero de recursos de cada idioma. Requiere de un fichero de recursos por cada uno de los idiomas que soporta la aplicación: “es” y “en-US”.

**Tabla 04.- Ficheros de recursos para los tipos de proyectos**

| Español (es)                                                                                                                                                                                          | Inglés (en-US)                                                                                                                                                                                        |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-e1e89906d5a9d3eab7d9838b3660cee8b8010580%2Fimage10.PNG?alt=media) | ![](https://240651724-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfrvHBNivOTy0fVKROgzo%2Fuploads%2Fgit-blob-23f05112e42c18c2bd058f5c15609c7c6aa60d47%2Fimage11.PNG?alt=media) |

Su implementación es la siguiente:

```
public class EnumToLocalizatedConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    return LocalizedStrings.GetForCurrentView(parameter as string).GetString(value.ToString());
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    throw new NotImplementedException();
  }
}
```

Una vez implementados todos los estilos y convertidores, el aspecto final del control *ComboBox* para la selección del tipo de proyecto, pasa a ser el siguiente:

```
<ComboBox x:Uid="ComboProjectType"
      Header="Project type"
      ItemsSource="{Binding ProjectTypes}"
      SelectedItem="{Binding SelectedProjectType, Mode=TwoWay}"
      Style="{StaticResource ComboBoxSchema}">
  <i:Interaction.Behaviors>
    <i:BehaviorCollection>
      <core:EventTriggerBehavior EventName="SelectionChanged">
        <core:InvokeCommandAction Command="{Binding ProjectTypeChangedCommand}" />
      </core:EventTriggerBehavior>
    </i:BehaviorCollection>
  </i:Interaction.Behaviors>
</ComboBox>
```

Para la previsualización de la imagen personalizada una vez seleccionada, utilizaremos un control “**Image**”, con un “**BitmapImage**” como *Source*, para así obviar el uso de cache (CreateOptions="IgnoreImageCache"), y permitir al usuario cambiar la imagen tantas veces como quiera.

```
<Border Grid.Column="2" Width="60" Height="60" HorizontalAlignment="Right"
BorderThickness="2" BorderBrush="{ThemeResource AppBarItemForegroundThemeBrush}">
  <Image Stretch="Fill">
    <Image.Source>
      <BitmapImage UriSource="{Binding ProjectPreview, Converter={ StaticResource ThumbnailProjectConverter}, Mode=TwoWay}"
      CreateOptions="IgnoreImageCache" />
    </Image.Source>
    <i:Interaction.Behaviors>
      <i:BehaviorCollection>
        <core:EventTriggerBehavior EventName="Tapped">
          <core:InvokeCommandAction Command="{Binding AddImageCommand}" />
        </core:EventTriggerBehavior>
      </i:BehaviorCollection>
    </i:Interaction.Behaviors>
  </Image>
</Border>
```

Si una imagen ha sido personalizada por el usuario prevalecerá ésta, frente a la predeterminada para cada tipo de proyecto. Para conseguirlo, haremos uso de un nuevo convertidor “**ThumbnailProjectConverter**”.

```
internal sealed class ThumbnailProjectConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string culture)
  {
    if (value != null)
    {
      if (value is Data.Schemas.Project)
      {
        var proj = (Data.Schemas.Project)value;
        string url = String.Empty;
        if (!string.IsNullOrWhiteSpace(proj.ImageUrl))
        {
          url = proj.ImageUrl;
        }
        else
        {
          url = string.Format(Constants.PROJECT_TYPES_ICON_PATH_FILES_FORMAT, proj.ProjectType);
        }
        return new ThumbnailUriConverter().Convert(url, targetType, parameter, culture);
      }
    }
    return DependencyProperty.UnsetValue;
  }

  public object ConvertBack(object value, Type targetType, object parameter, string culture)
  {
    throw new NotImplementedException();
  }
}
```

Tras analizar este código, la pregunta a hacernos aquí es, si la propiedad “ImageUrl” no ha sido obtenida a partir del repositorio, **¿Cómo la obtenemos si nuestra entidad proyecto utiliza un conjunto de bytes (*****byte\[]*****)?**. Tanto en una situación como en cualquier otra, salvo en aquellos casos en que el repositorio sea el “Storage”, utilizaremos un sistema de cacheado de imágenes basado en ficheros locales, así, al descargar las imágenes de un servicio Web, o bien al utilizar imágenes como conjuntos de *bytes*, agilizaremos la visualización de las mismas teniendo en cuenta, además, que los controles de imágenes gestionan más racionalmente las mismas y evitan altos consumos de memoria.

Partiendo de la premisa anterior, tendremos que aprovisionar las URLs de las imágenes en el momento en que el usuario haga su elección a partir de la librería de imágenes del dispositivo. Por lo que, para completar el proceso de selección de imágenes, al igual que comentamos en el capítulo 2, son necesarios algunos pasos:

* Activar y configurar la declaración “FileOpenPicker” e incluir las extensiones de ficheros soportadas.

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

**Figura 06.- Declaración "FileOpenPicker" para la selección de imágenes**

* Incluir en fichero “App.xaml.cs”, en el evento “**OnActivated**”, el siguiente código. Este evento se encarga permitir el acceso a librería de imágenes y devolver el control nuevamente a la aplicación para poder tratar la imagen seleccionada.

```
protected override void OnActivated(IActivatedEventArgs args)
{
  Frame rootFrame = appManager.DisplayOnActivateView(args);
  if (args.Kind == ActivationKind.PickFileContinuation)
  {
    var page = rootFrame.Content as ProjectEditionPage;
    var viewModel = page.ViewModel as IProjectEditionViewModel;
    viewModel.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
  }
}
```

* Incluir en el ViewModel “ProjectEditionViewModel”, el método que tomará el control tras la selección de la imagen para tratar la misma.

public async void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args) { //... }

**Nota**: La implementación de este método, así como sus particularidades las veremos en el siguiente apartado, en las opciones de menú.

Como vamos a añadir, modificar o borrar imágenes de la librería, no tendremos que activar las capacidades “Pictures Library”, por lo que no la marcaremos.

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

**Figura 07.- Selector de imágenes desde la librería del dispositivo**

Llegado este punto, hemos diseñado el *ComboBox* para la selección de tipos de proyectos, hemos configurado la selección de imágenes desde la librería del dispositivo, hemos creado una imagen para la pre-visualización, y ya tenemos los *TextBox* para la introducción del título, el detalle del proyecto y la dirección. Añadiremos ahora un botón para la obtención automática de la dirección a partir de la localización actual haciendo uso del GPS. Lo cual implicará algunos pasos previos:

* Habilitar la capacidad de localización (o *Location*).

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

**Figura 08.- Habilitación de la capacidad de localización en el manifiesto de la aplicación**

* Crear un nuevo servicio “**GeolocationService**” para el acceso a la locación para el que implementaremos los siguientes métodos:

```
public async Task<string> GetAddressFromCoordinatesAsync()
{
  var locator = new Geolocator();
  var position = await locator.GetGeopositionAsync();
  return await this.GetAddressFromCoordinatesAsync(position);
}

public async Task<string> GetAddressFromCoordinatesAsync(Geoposition geoposition)
{
  string address = null;
  try
  {
    var location = await MapLocationFinder.FindLocationsAtAsync(geoposition.Coordinate.Point);
    if (location.Status == MapLocationFinderStatus.Success)
    {
      bool requireNewLine = !String.IsNullOrWhiteSpace(location.Locations[0].Address.Town.Trim())
        && (!String.IsNullOrWhiteSpace(location.Locations[0].Address.StreetNumber.Trim())
        || !String.IsNullOrWhiteSpace(location.Locations[0].Address.Street.Trim()));
      if (System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "es")
      {
        address = String.Format("{0}{1}{2} {3}",
          location.Locations[0].Address.Town.Trim(),
          requireNewLine ? Environment.NewLine : String.Empty,
          location.Locations[0].Address.Street.Trim(),
          location.Locations[0].Address.StreetNumber.Trim());
      }
      else
      {
        address = String.Format("{0}{1}{2} {3}",
          location.Locations[0].Address.Town.Trim(),
          requireNewLine ? Environment.NewLine : String.Empty,
          location.Locations[0].Address.StreetNumber.Trim(),
          location.Locations[0].Address.Street.Trim());
      }
    }
  }
  catch { /* Nothing to do. Return null. */ }
  return address;
}
```

* Enlazar a este nuevo botón, el comando “GetCurrentPositionCommand” con el código:

```
private RelayCommand<string> getCurrentPositionCommand;
public RelayCommand<string> GetCurrentPositionCommand
{
  get
  {
    return getCurrentPositionCommand
    ?? (getCurrentPositionCommand = new RelayCommand<string>(
    async (search) =>
    {
      bool errorFound = false;
      string addressFound = String.Empty;
      try
      {
        await StatusBarService.ShowProgressAsync();
        // get the first address that matches the current position
        var geoService = new GeolocationService();
        addressFound = await geoService.GetAddressFromCoordinatesAsync();
        errorFound = (null == addressFound);
      }
      catch (UnauthorizedAccessException) { /* Noting to do. When User not
      accept GPS access */}
      finally
      {
        await StatusBarService.HideProgressAsync();
        if (!errorFound)
          this.Address = addressFound;
        else
          await this.dialogService.ShowMessageBox(LocalizedStrings.MsgCurrentAddressNotFound, LocalizedStrings.MsgCurrentAddressTitle);
      }
    }));
  }
}
```

Finalmente, y para que todos los convertidores anteriores entren en acción, los registraremos en el diccionario de recursos a nivel de aplicación. En el fichero “App.xaml”.

```
<converters:EnumToLocalizatedConverter x:Key="EnumToLocalizatedConverter" />
<converters:ThemedImageConverter x:Key="ThemedImageConverter" />
<converters:ThumbnailUriConverter x:Key="ThumbnailUriConverter"/>
<converters:ThumbnailProjectConverter x:Key="ThumbnailProjectConverter" />
```

### Opciones de menú

Las opciones de menú que constituyen la edición de un proyecto son las siguientes:

* Guardar (o *Save*). Como su nombre indica guarda los datos del proyecto y además cierra la pantalla actual volviendo
* Eliminar (o *Remove*). Elimina el proyecto previo aviso de confirmación. Su comando el siguiente.

```
private RelayCommand appBarDeleteCommand;
public RelayCommand AppBarDeleteCommand
{
  get
  {
    return appBarDeleteCommand ?? (appBarDeleteCommand = new RelayCommand(
    async () =>
    {
      bool pushButtonOk = await this.dialogService.ShowMessage(
        LocalizedStrings.MessageBoxDeleteProjectMessage,
        LocalizedStrings.MessageBoxDeleteTitle,
        LocalizedStrings.ButtonOK,
        LocalizedStrings.ButtonCancel,
        null);
      if (pushButtonOk)
      {
        if (await this.service.DeleteProject(this.projectId) == 0)
        {
          this.navigationService.GoBack();
          this.navigationService.GoBack();
        }
      }
    }));
  }
}
```

* Foto (o *Photo*). Esta opción, posibilita la elección de una imagen personalizada para el proyecto escogiéndola a partir de la biblioteca de imágenes del dispositivo.

```
private RelayCommand<object> addImageCommand;
public RelayCommand<object> AddImageCommand
{
  get
  {
    return addImageCommand ?? (addImageCommand = new RelayCommand<object>(
#if ! WINDOWS_PHONE_APP
    async
#endif
    (arg) =>
    {
      var openPicker = new FileOpenPicker
      {
        ViewMode = PickerViewMode.Thumbnail,
        SuggestedStartLocation = PickerLocationId.PicturesLibrary
      };
      openPicker.FileTypeFilter.Add(".jpg");
      openPicker.FileTypeFilter.Add(".png");
#if WINDOWS_PHONE_APP
      openPicker.PickSingleFileAndContinue();
#else
      await openPicker.PickSingleFileAsync();
#endif
    }));
  }
}
```

Una vez seleccionada la imagen, es el método “ContinueFileOpenPicker” el encargado de llevar a cabo su trasformación para:

1. Reducir el tamaño original. No necesitamos la imagen con su total calidad ni tamaño, una imagen en miniatura es más que suficiente.
2. La asignación al proyecto. A partir de la imagen obtenida del fichero (.jpg o .png), asignaremos a la propiedad “Image” del proyecto su conjunto de bytes (*byte\[]*).
3. Gestionar su almacenamiento. Aunque disponiendo del conjunto de bytes, podemos visualizar la imagen, existen algunas recomendaciones importantes a tener en cuenta. Veamos el código para comprender mejor a qué nos estamos refiriendo.

```
public async void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args)
{
  if (args.Files != null && args.Files.Count > 0)
  {
    var imgFile = args.Files[0];
    if (null != imgFile)
    {
      this.ChangeProjectPreview(null, null);
      var imgContent = await BitmapHelper.GetThumbnailAsync(imgFile, SettingsService.ImageSize);
      await FileCache.AddItemAsync(this.projectId, imgContent, true);
      var imgUrl = FileCache.GetItem(this.CurrentProject.Id);
      this.ChangeProjectPreview(imgContent, imgUrl);
    }
  }
}
```

La particularidad reside en las siguientes acciones:

* BitmapHelper.GetThumbnailAsync. Método que implementaremos en una nueva clase “BitmapHelper” en el proyecto “InTools” y que reutilizaremos en cualquier otro proyecto. Su código es muy sencillo y se basa en la obtención de una imagen en miniatura (o *Thumbnail*) a partir de la imagen real. No queremos una imagen con la más alta calidad, sin embargo, sí una imagen pequeña y ligera que pueda ser cargada rápidamente para identificar a cada proyecto de una manera más personalizada. Para más información sobre la obtención de imágenes en miniatura, visitar la página: <https://msdn.microsoft.com/es-es/library/windows/apps/hh701643.aspx>.

```
public static async Task<byte[]> GetThumbnailAsync(StorageFile sourceFile, uint size)
{
  if (sourceFile != null)
  {
    using (StorageItemThumbnail thumbnail = await sourceFile.GetThumbnailAsync(ThumbnailMode.PicturesView, size, ThumbnailOptions.None))
    {
      if (thumbnail != null && thumbnail.Type == ThumbnailType.Image)
      {
        Windows.Storage.Streams.Buffer buffer = new Windows.Storage.Streams.Buffer(Convert.ToUInt32(thumbnail.Size));
        IBuffer iBuf = await thumbnail.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.None);
        return iBuf.ToArray();
      }
    }
  }
  return null;
}
```

* FileCache.AddItemAsync y FileCache.GetItemPath. Métodos de una nueva clase “**FileCache**”, que también incluiremos en el proyecto “InTools”, en una carpeta “Cache”. Su funcionalidad es la de cachear ficheros o imágenes en el repositorio local de ficheros temporales.

```
public static class FileCache
{
  private const string IMG_FILE_EXTENSION = ".img";
  private static Dictionary<string, string> cacheFileNames;
  private static InTools.Storage.StorageHelper storage = null;

  static FileCache()
  {
    storage = new InTools.Storage.StorageHelper(InTools.Storage.StorageType.Temp, "CacheProjectImages");
    cacheFileNames = new Dictionary<string, string>();
  }

  public static async Task AddItemAsync(string key, byte[] content, bool overrideContent = false)
  {
    if (null == content) return;
    string fileName = UpdateCacheKeys(key);
    if (overrideContent)
    {
      await storage.SaveBytesAsync(fileName, content, CreationCollisionOption.ReplaceExisting);
      return;
    }
    if (!await storage.FileExistsAsync(fileName))
      await storage.SaveBytesAsync(fileName, content, CreationCollisionOption.ReplaceExisting);
  }

  public static async Task AddItemAsync(string key, StorageFile file)
  {
    string fileName = UpdateCacheKeys(key);
    await file.CopyAsync(storage.Root, fileName);
  }

  public static async Task RemoveItemAsync(string key)
  {
    if (cacheFileNames.ContainsKey(key))
    {
      if (await storage.DeleteFileIfExistsAsync(GetFileName(key)))
        cacheFileNames.Remove(key);
    }
  }

  public static string GetItem(string key)
  {
    if (cacheFileNames.ContainsKey(key))
      return cacheFileNames[key];
    return null;
  }

  private static string GetFullFilePath(string key)
  {
    return Path.Combine(storage.Root.Path, GetFileName(key));
  }

  private static string GetFileName(string key)
  {
    if (!key.EndsWith(IMG_FILE_EXTENSION, StringComparison.CurrentCultureIgnoreCase))
      return String.Format("{0}{1}", key, IMG_FILE_EXTENSION);
    return null;
  }

  private static string UpdateCacheKeys(string key)
  {
    var fileName = GetFileName(key);
    if (cacheFileNames.ContainsKey(key))
    {
      cacheFileNames[key] = GetFullFilePath(key);
    }
    else
    {
      cacheFileNames.Add(key, GetFullFilePath(key));
    }
    return fileName;
  }
}
```

* Quitar imagen (o remove image). Elimina la imagen de un proyecto si previamente, ésta, ha sido personalizada.

```
RaisePropertyChanged ("ProjectPreview");
```

El código XAML que representa los botones anteriores puede verse a continuación:

```
<CommandBar.PrimaryCommands>
  <AppBarButton x:Uid="AppBarButtonSave" Label="save"
    Command="{Binding AppBarSaveCommand}"
    Style="{StaticResource AppBarButtonSave}"/>
  <AppBarButton x:Uid="AppBarButtonDelete" Label="remove"
    Command="{Binding AppBarDeleteCommand}"
    IsEnabled="{Binding IsEnabledToDelete, Mode=TwoWay}"
    Style="{StaticResource AppBarButtonDelete}"/>
  <AppBarButton x:Uid="AppBarButtonBrowseImage" Icon="BrowsePhotos"
    Label="image"
    Command="{Binding AddImageCommand}"
    Style="{StaticResource AppBarButtonBrowsePhotos}"/>
</CommandBar.PrimaryCommands>
<CommandBar.SecondaryCommands>
  <AppBarButton x:Uid="AppBarButtonRemoveImage" Label="remove image"
    Command="{ Binding AppBarRemoveImageCommand }" />
</CommandBar.SecondaryCommands>
</CommandBar>
```

Donde, cabe destacar la manera de crear sus iconos, donde para ello nos basamos en estilos o plantillas predefinidas tal y como podemos ver a continuación, que incluiremos en un nuevo fichero de estilos “**ButtonsStyles.xaml**”

```
<Style x:Key="AppBarButtonSave" TargetType="AppBarButton">
  <Setter Property="Icon" Value="Save" />
</Style>

<Style x:Key="AppBarButtonDelete" TargetType="AppBarButton">
  <Setter Property="Icon" Value="Delete" />
</Style>

<Style x:Key="AppBarButtonBrowsePhotos" TargetType="AppBarButton">
  <Setter Property="Icon" Value="BrowsePhotos" />
</Style>
```

Veremos más sobre estos estilos un poco más adelante, en este mismo capítulo.

## Detalle de un proyecto (ProjectDetail)

En el apartado anterior hemos visto la edición de un proyecto. Hemos diseñado la pantalla “ProjectEditionPage” en la que hemos incluido el tipo de proyecto y una imagen representativa y campos de texto. A continuación, vamos a crear la pantalla de detalle, en la que para cada proyecto mostraremos el listado de sus tareas.

Evidentemente podremos diseñarla de muchas maneras, aun así, recordemos nuestro diseño según el capítulo 3. Podríamos pensar utilizar la misma incluyendo un control “Pivot” y dividir la edición del listado de tareas en un control “PivotItem” cada una. El aspecto que decidimos darle, muestra en una sola ventana todo lo que necesitamos conocer de un proyecto, e incluye accesos directos para su edición y borrado. Todo ello en un simple vistazo.

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

**Figura 09.- Detalle y menú contextual de un proyecto**

Al igual que para la sección anterior, iremos descubriendo cada una de las partes que la forman y cómo implementarlas. Pero antes, recordemos que al crear el ViewModel “ProjectDetailViewModel”, seguiremos los pasos a los que ya hemos hecho referencia varias veces incluyendo la implementación de la interfaz **INavigable**, por lo que codificaremos, el método “GoBack” que restablecerá el valor de ciertos atributos y propiedades al abandonar la página.

Una vez más, creada la página y el ViewModel, veamos el conjunto de métodos y propiedades a codificar:

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

**Figura 10.- Métodos y propiedades del ViewModel de detalle de proyectos**

De todos ellos, cabe destacar los siguientes, además del método “Initialize” que ya comentamos en capítulos anteriores ~~(capítulo 5)~~.

* NotifyTaskCompletion\<Project> ProjectAsync. Propiedad de tipo “NotifyTaskCompletion”, utilizada para agilizar la apertura de una página y realizar la carga de datos en segundo plano mostrando mientras tanto, el mensaje “Cargando…”. La definiremos en el método *Initialize* y, estará destinada a la obtención de proyectos a partir de su repositorio. Ésta logra, por tanto, una respuesta responsiva y aporta una sensación de mayor fluidez. El siguiente código muestra la instanciación, así como la implementación del evento “NotifySuccessfullyCompleted”, que ocurrirá una vez completada la carga de proyectos:

```csharp
this.ProjectAsync = new NotifyTaskCompletion<Project>(this.service.GetProject(projectId));
this.ProjectAsync.NotifySuccessfullyCompleted += async () =>
{
    this.CurrentProject = this.projectAsync.Result;
    if (null != this.CurrentProject)
    {
        …
    }
};
```

De forma similar, usaremos esta clase en código XAML, para mostrar el botón de edición una vez se haya completado la carga de la página.

```xml
<Button Style="{StaticResource RoundedButtonEdit}"
    Visibility="{Binding ProjectAsync.IsSuccessfullyCompleted, Converter={StaticResource BooleanToVisibilityConverter}}"
    Command="{Binding EditProjectCommand}" />
```

* ObservableCollection\<Task> Tasks. Propiedad que representa a la colección de tareas de un proyecto.
* CollectionViewSource TaskCollection. Colección para mostrar y filtrar las tareas, es decir, las tareas identificadas por la propiedad anterior, Tasks.
* Project CurrentProject. Propiedad que recoge la información derivada de un proyecto y a partir de la cual se muestra su detalle.

### Cabecera

Resume la información del proyecto mostrando dos botones de acceso directo para su edición y borrado. Crearemos una plantilla, en la propia página puesto que sólo será usada en la misma.

```
<DataTemplate x:Key="ProjectEditionHeader">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="10" />
    </Grid.RowDefinitions>
    <Grid Background="{ThemeResource PhoneAccentBrush}">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>
      <Image Grid.RowSpan="2" Stretch="Fill" VerticalAlignment="Stretch" Margin="5,5,0,5" Height="80" Width="80">
        <Image.Source>
          <BitmapImage UriSource="{Binding CurrentProject, Converter={ StaticResource ThumbnailProjectConverter}, Mode=TwoWay}" CreateOptions="IgnoreImageCache" />
        </Image.Source>
      </Image>
      <StackPanel Grid.Column="1" Grid.ColumnSpan="2" Margin="10,5,0,0">
        <TextBlock Text="{Binding CurrentProject.Title, Mode=TwoWay}" TextTrimming="WordEllipsis" TextWrapping="Wrap" MaxLines="1" HorizontalAlignment="Left" Style="{ThemeResource ItemHeaderText}"/>
        <TextBlock Text="{Binding CurrentProject.Detail, Mode=TwoWay}" TextTrimming="WordEllipsis" TextWrapping="Wrap" MaxLines="1" HorizontalAlignment="Left" Style="{ThemeResource ItemSmallText}"/>
      </StackPanel>
      <StackPanel Grid.Column="3" Grid.Row="2" Margin="0,0,15,10" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
        <Button Style="{StaticResource RoundedButtonEdit}" Visibility="{Binding ProjectAsync.IsSuccessfullyCompleted, Converter={StaticResource BooleanToVisibilityConverter}}" Command="{Binding EditProjectCommand}" />
        <Button Margin="15,0,0,0" Style="{StaticResource RoundedButtonRemove}" Command="{Binding DeleteProjectCommand}" Visibility="{Binding IsDefaultProject, Converter={StaticResource BooleanToVisibilityNegationConverter}, Mode=TwoWay}" />
      </StackPanel>
      <StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" Margin="5,10,0,0">
        <TextBlock Text="{Binding CurrentProject.Tasks.Count}" HorizontalAlignment="Right" Style="{ThemeResource ItemSmallText}"/>
        <TextBlock Text="Tasks" Margin="5,0,0,0" HorizontalAlignment="Left" Style="{ThemeResource ItemSmallText}"/>
      </StackPanel>
    </Grid>
  </Grid>
</DataTemplate>
```

En esta recurrimos a un nuevo convertidor, “**BooleanToVisibilityNegationConverter**”, el cual realiza la conversión inversa al convertidor “**BooleanToVisibilityConverter**”, es decir, hace lo mismo, pero intercambiando los valores, donde “true” pasa a convertirse en “Collapsed” y “false” en “Visible”.

{% hint style="info" %}
**Nota**: Usaremos la nomenclatura “**\<TipoOrigen>To\<TipoDestino>NegationConverter**” en el nombre de un convertidor para denotar que éste niega o realiza la operación inversa.
{% endhint %}

El botón de borrado, únicamente se mostrará cuando el proyecto no sea el proyecto predeterminado/favorito.

Añadiremos por tanto ambos conversores a nuestro proyecto “Shared”.

Para la imagen del proyecto utilizaremos el convertidor “**ThumbnailProjectConverter**” de igual forma que en la pantalla de edición de proyectos pero con la diferencia de que a en este caso utilizaremos la propiedad “**CreateOptions="IgnoreImageCache**” para indicar así que la imagen no sea cacheada.

Los **botones de acceso directo** para la edición y borrado, merecen especial atención, debiendo destacar su específica plantilla, así como el icono de cada uno de ellos, para el que se ha usado un control “FontIcon” con el tipo de letra “Segoe UI Symbol” y los caracteres “*\&#xE104*” y “**” respectivamente para la representación de sus iconos. La siguiente figura muestra cómo obtener el carácter para la representación del icono de edición:

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

**Figura 11.- Selección de caracteres Segoe UI Symbol para los iconos de edición y borrado**

Para más detalle sobre los valores o textos para estos iconos, visitar la página: <https://msdn.microsoft.com/es-es/library/windows/apps/jj841126.aspx>

{% hint style="info" %}
**Nota**: Como recomendación y buena práctica, definiremos cada botón de la aplicación en un fichero de recursos, “**ButtonStyles.xaml**”, de manera que contenga todos y cada uno de los estilos relacionados con botones. Así, haremos uso de cada uno de ellos siempre a través de su estilo completamente centralizado.
{% endhint %}

La plantilla que define a los botones circulares o redondos tendrá la siguiente forma:

```
<Style x:Key="RoundedButtonStyle" TargetType="Button">
  <Setter Property="MinHeight" Value="{Binding}" />
  <Setter Property="MinWidth" Value="{Binding}" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="Button">
        <Grid>
          <Ellipse Name="Ellipse" Grid.Row="0" Height="35" Width="35"
               Stroke="White" StrokeThickness="1" Fill="{TemplateBinding Background}" />
          <ContentPresenter Name="Content" Grid.Row="0"
                    HorizontalAlignment="Center" VerticalAlignment="Center"
                    FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}"
                    Content="{TemplateBinding Content}" />
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal"/>
              <VisualState x:Name="PointerOver">
                <Storyboard>
                  <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity"
                                   Storyboard.TargetName="Ellipse">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="0.8"/>
                  </ObjectAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
              <VisualState x:Name="Pressed">
                <Storyboard>
                  <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill"
                                   Storyboard.TargetName="Ellipse">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="White"/>
                  </ObjectAnimationUsingKeyFrames>
                  <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                   Storyboard.TargetName="Content">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PhoneAccentBrush}"/>
                  </ObjectAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
          </VisualStateManager.VisualStateGroups>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
```

Y, en consecuencia, los botones de edición se definirán como sigue así como cualquier otro que vayamos necesitando a lo largo del desarrollo de nuestra aplicación.

```
<Style x:Key="RoundedBlackButtonStyle" TargetType="Button" BasedOn="{StaticResource RoundedButtonStyle}">
  <Setter Property="FontFamily" Value="{StaticResource FontFamilyButtonStyle}"/>
  <Setter Property="FontSize" Value="18"/>
  <Setter Property="Background" Value="{ThemeResource AppBarBorderThemeBrush}"/>
</Style>
<Style x:Key="RoundedButtonEdit" TargetType="Button" BasedOn="{StaticResource RoundedBlackButtonStyle}">
  <Setter Property="Content" Value="&#xE104;"/>
</Style>
<Style x:Key="RoundedButtonRemove" TargetType="Button" BasedOn="{StaticResource RoundedBlackButtonStyle}">
  <Setter Property="Content" Value="&#xE107;"/>
</Style>
```

Una vez creado este nuevo diccionario o fichero de recursos, la carpeta “Styles” de nuestro proyecto “Shared” mostrará el siguiente aspecto:

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

**Figura 12.- Carpeta Styles del proyecto Shared con el fichero ButtonStyles.xaml**

Tras esto, registramos éste en el fichero “App.xaml” al igual que cualquier otro diccionario de estilos o recurso.

```
<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      [..]
      <ResourceDictionary Source="Styles/ButtonsStyles.xaml" />
      [..]
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>
```

Además de los estilos, los botones “Edición” y “Borrado”, están vinculados/enlazados a los comandos “EditProjectCommand” y “DeleteProjectCommand” respectivamente, donde su código será el siguiente:

```
private RelayCommand<object> editProjectCommand;
public RelayCommand<object> EditProjectCommand
{
  get
  {
    return editProjectCommand ?? (editProjectCommand = new RelayCommand<object>(
      (e) =>
      {
        this.UnloadControls();
        this.navigationService.NavigateTo(EnumPage.ProjectEditionPage, this.CurrentProject);
      }));
  }
}

private RelayCommand deleteProjectCommand;
public RelayCommand DeleteProjectCommand
{
  get
  {
    return deleteProjectCommand ?? (deleteProjectCommand = new RelayCommand(
      async () =>
      {
        bool deleteOk = await this.dialogService.ShowMessage(
          LocalizedStrings.MessageBoxDeleteProjectMessage,
          LocalizedStrings.MessageBoxDeleteTitle,
          LocalizedStrings.ButtonOK,
          LocalizedStrings.ButtonCancel,
          null);
        if (deleteOk)
        {
          this.UnloadControls();
          this.RemoveProject(this.CurrentProject.Id);
          Messenger.Default.Send<Project>(this.CurrentProject, EnumAction.Delete);
          await FileCache.RemoveItemAsync(this.CurrentProject.Id);
          this.navigationService.GoBack();
        }
      }));
  }
}
```

Por último, remarcar que para el cambio de imagen, tras su selección, al igual que para la edición de proyectos, utilizaremos el siguiente código, cuya finalidad es forzar el refresco de la información del proyecto.

```
private void ChangeProject(Project proj)
{
  // Importat: Set to null before change the project to force Update image
  this.CurrentProject = null;
  this.CurrentProject = proj;
  this.RefreshTasksCollection();
}
```

En esta sección, hemos visto por primera vez el uso de “**VisualStateManager**” y el uso de “**Messenger**”, abordaremos ambas, en el siguiente capítulo. Donde veremos además cómo, cuándo y por qué utilizar un “VisualStateManager” así como las ventajas y facilidades que ofrece el uso de “Messenger”.

{% hint style="info" %}
**Nota**: Las opciones de menú de la barra de tareas no permiten el paso de parámetros, siendo estos otros controles
{% endhint %}

### Contenido: Listado de tareas

Cada proyecto está compuesto por un listado de tareas que se cargarán al acceder al mismo. Durante el tiempo que dure la carga, presentaremos al usuario un mensaje indicando que se está produciendo dicha acción, por ejemplo, con el mensaje, “Cargando…”. En caso de que un proyecto no contenga tareas o que todas ellas hayan sido resueltas o eliminadas, el listado se encontrará vacío. En este caso informaremos al usuario con el mensaje: “No se han encontrado tareas”.

Para conseguir estos comportamientos, crearemos y destinaremos un control exclusivamente para ello, lo que nos permitirá una reutilización del mismo en todas las páginas de nuestro proyecto que hagan uso de listados. Así mismo podremos reutilizar en todos aquellos proyectos que desarrollemos. A este nuevo control le denominaremos “**ListPlaceHolder**” y, pasaremos a incluirlo en una nueva carpeta “**Controls**”, dentro el proyecto “Shared”. Para su implementación, seguiremos los siguientes pasos:

1. Crearemos una clase y un diccionario de recursos (“.xaml”), pero en lugar de codificarlo partiendo desde cero, lo haremos a partir del proyecto QKIT de CodePlex (CodePlex ya no existe; copia archivada: <https://web.archive.org/web/20171223142914/http://qkit.codeplex.com/>). Es decir, obtendremos parte de código de éste y lo añadiremos al nuestro. Para ello, incluiremos la licencia “Microsoft Public License (Ms-PL)” al comienzo de dicha clase o bien incluiremos un fichero “.txt” con dicha licencia en la ruta raíz del proyecto.
2. Creamos un nuevo fichero de recursos “PlaceHolder.xaml” a partir del fichero “Generic.xaml” únicamente con el código necesario para este. Incluiremos también la licencia en la cabecera de dicho fichero:

```
<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:elGuerre.ePomo3.Controls">

  <!--ListPlaceholder-->
  <DataTemplate x:Key="ListPlaceholderContentTemplate">
    <TextBlock Text="{Binding}"
           Style="{ThemeResource ListViewEmptyStaticTextBlockStyle}"
           HorizontalAlignment="Left" />
  </DataTemplate>

  <Style TargetType="local:ListPlaceholder">
    <Setter Property="LoadingPlaceholderContent"
        Value="Loading..." />
    <Setter Property="EmptyPlaceholderContent"
        Value="Nothing to show." />
    <Setter Property="LoadingPlaceholderTemplate"
        Value="{StaticResource ListPlaceholderContentTemplate}" />
    <Setter Property="EmptyPlaceholderTemplate"
        Value="{StaticResource ListPlaceholderContentTemplate}" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:ListPlaceholder">
          <ContentPresenter Content="{TemplateBinding PlaceholderContent}"
                    ContentTemplate="{TemplateBinding PlaceholderTemplate}"
                    Visibility="{TemplateBinding PlaceholderVisibility}" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>
```

3. Creamos un nuevo fichero de recursos “Controls.xaml”

```
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:elGuerre.ePomo3.Controls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="PlaceHolder/PlaceHolder.xaml" />
<!-- Add more controls as needed -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
```

4. Registramos este fichero de recursos junto al resto, en el fichero “App.xaml”.

```
<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Styles/ButtonsStyles.xaml" />
      <!-- Controls -->
      <ResourceDictionary Source="Controls/PlaceHolder/PlaceHolder.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>
```

Para conseguir otro buen comportamiento en nuestro listado de tareas, así como minimizar la cantidad de código, crearemos otro nuevo control, “**MultiSelectListView**” procediendo de la misma manera, que para el anterior, pero para los ficheros “EdgeSelectButton.cs”, “MultiSelectListView\.cs”, “VisualTreeUtil” y “MultiSelectListView\.xaml” por lo que nuestro proyecto tendrá el siguiente aspecto:

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

**Figura 13.- Estructura del proyecto tras incorporar el control MultiSelectListView**

El control "MultiSelectListView" presenta algunos bugs, por lo que, realizaremos los siguientes ajustes, en la clase “MultiSelectListView\.cs”:

```
private static bool autoSelection = false;
private bool defaultIsItemClickEnabled = true;
[…]
private void MultiSelectionListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  if (SelectedItems == null || SelectedItems.Count == 0 && !autoSelection)
    SelectionMode = ListViewSelectionMode.None;
  if (SelectionMode == ListViewSelectionMode.Multiple)
    IsItemClickEnabled = !defaultIsItemClickEnabled;
  else
    IsItemClickEnabled = defaultIsItemClickEnabled;
  autoSelection = false;
}
```

Existe otro bug, en la activación de la selección múltiple, concretamente que el botón “EdgeSelectButton”, asociado a cada elemento de la lista, no puede ser pulsado si la lista define la propiedad “**ItemContainerStyle**”.

Finalmente, para hacer uso de éstos y aplicarlo al listado de tareas, incluiremos el espacio de nombre (namespace) “*xmlns:ctrl="using:elGuerre.ePomo3.Controls*”, en la página “ProjectDetailPage.xaml” y definiremos los siguientes controles:

```
<ScrollViewer x:Name="listViewScroll" Grid.Row="1" HorizontalScrollMode="Disabled">
<StackPanel Grid.Row="1">
<ctrl:ListPlaceholder Margin="19,0,10,0"
ListTarget="{Binding ElementName=lstViewTasks}"
LoadingPlaceholderContent="{Binding LoadingData, Source={StaticResource LocalizedStrings}}"
EmptyPlaceholderTemplate="{StaticResource PlaceHolderEmptyLinkTemplate}" />
<ctrl:MultiSelectListView x:Name="lstViewTasks"
Margin="5,0,5,10"
ItemsSource="{Binding TaskCollection.View, Mode=TwoWay}"
ItemTemplate="{StaticResource SmallImageDetailTemplate}"
ItemContainerStyle="{StaticResource ListViewItemContainerStyle}"
IsItemClickEnabled="{Binding IsListItemClickEnabled, Mode=TwoWay}"
SelectionMode="{Binding ListSelectionMode, Mode=TwoWay}"
ScrollViewer.HorizontalScrollMode="Disabled"
ScrollViewer.VerticalScrollMode="Disabled" >
<ListView.Transitions>
<TransitionCollection>
<AddDeleteThemeTransition/>
</TransitionCollection>
</ListView.Transitions>
<i:Interaction.Behaviors>
<i:BehaviorCollection>
<core:EventTriggerBehavior EventName="Holding">
<core:InvokeCommandAction Command="{Binding HoldingTaskActionCommand}" />
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="ItemClick">
<core:InvokeCommandAction Command="{Binding TasksItemClickCommad}" />
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding ListSelectionChangedCommad}" CommandParameter="{Binding ElementName=lstViewTasks}" />
</core:EventTriggerBehavior>
</i:BehaviorCollection>
</i:Interaction.Behaviors>
</ctrl:MultiSelectListView>
</StackPanel>
</ScrollViewer>
```

Donde:

* **LoadingPlaceholderContent**. Propiedad que recibe el texto a mostrar durante la carga de la lista.
* **PlaceHolderEmptyLinkTemplate**. Propiedad que recibe el estilo a mostrar cuando la lista no contiene elementos. La añadiremos como recurso en la página al ser utilizada únicamente desde aquí y utilizaremos en la misma el comando “**AppBarNewCommand**”, que veremos más adelante, en las opciones de menú.

```
<DataTemplate x:Key="PlaceHolderEmptyLinkTemplate">
  <StackPanel Orientation="Vertical">
    <TextBlock Text="{Binding NoItemsFound, Source={StaticResource LocalizedStrings}}"
           Style="{ThemeResource ListViewEmptyStaticTextBlockStyle}" />
    <HyperlinkButton HorizontalAlignment="Center"
             Content="{Binding EmptyListLinkToAddNewItem, Source={StaticResource LocalizedStrings}}"
             Command="{Binding AppBarNewCommand}"
             Foreground="{ThemeResource PhoneAccentBrush}"/>
  </StackPanel>
</DataTemplate>
```

La lista implementa los eventos “ItemClick”, “Holding” y “SelectionChanged” para las acciones a realizar sobre cada uno de sus elementos.

* **ItemClick**. Selección de una tarea y navegación a la pantalla de detalle de tareas (“TaskDetailPage.xaml”)
* **Holding**. Acciones directas sobre cada tarea que son ejecutadas al mantener pulsado el elemento. Esta acción depende del tipo de tarea y si tiene o no una acción asociada: “Call To”, “Email To”, etc, cuyas acciones son, telefonear a alguien, enviar un email, etc.
* **SelectionChanged**. Evento que se activa al pulsar el botón del menú “Select” y recoge todas las tareas marcadas por el usuario de manera que sea posible actuar sobre ellas en bloque; bien marcándolas como completadas o bien eliminándolas.

Veremos con más detalle sobre el listado de tareas y su implementación en la siguiente sección, donde veremos también la definición de las platillas/estilos “**SmallImageDetailTemplate” y “ListViewItemContainerStyle”.**

{% hint style="info" %}
**Nota**: Con la incorporación de parte del código de QKit a nuestro proyecto, y teniendo en cuenta que no se actualiza con normalidad, aseguraremos su corrección y mantenimiento futuro y, minimizaremos el número de DLLs ganando un mínimo en rendimiento. Añadiremos también las atribuciones pertinentes en la página “About” así como la “Licencia Pública de Microsoft (Ms-PL)”.
{% endhint %}

### Opciones de menú

Las opciones de menú, al contrario que los accesos directos de la cabecera, están dirigidos y afectan únicamente a las tareas y son los siguientes:

1. Nuevo (o New). Creación de una nueva tarea.
2. Seleccionar (Select). Habilita la selección múltiple de tareas.
3. Ordenar por prioridad (Order by priority). Realizar la ordenación por prioridad.
4. Ordenar por fecha (Order by date). Realiza la ordenación por fecha.
5. ¿\<Crear un submenú para las ordenaciones anteriores>?
6. Todas las tareas (All tasks…). Muestra una nueva ventana con todas las tareas, para el proyecto, incluso las ya completadas.
