Windows Phone, Windows 8: Portable Library y subida a la tienda: desde cero


Rafael Ontivero, Desarrollador sistemas embebidos, Desarrollador Windows Desktop, Windows Phone y Windows 8 Modern UI


Código fuente en BitBucket: https://bitbucket.org/rfog/multibase-clock


En este artículo os vamos a contar cómo utilizar la Portable Library para realizar una aplicación que luego se pueda subir tanto a la tienda de Windows Phone como a la de Windows 8. También trataremos el proceso de subida en ambos lugares, dando una idea de cómo funciona todo el proceso.

Para ello desarrollaremos un reloj que nos va a mostrar la hora en diferentes bases numéricas y luego lo mandaremos a ambas stores. De hecho el lector se puede bajar ambas versiones si busca “Multibase Clock” (http://bit.ly/ZCNDfk [Windows Phone] y http://bit.ly/17opzxA [Windows 8]) en cada una de ellas. La aplicación en sí no es que sea de mucha utilidad, pero sí contiene elementos interesantes en su código fuente, que es propósito de todo esto.

Portable Library

Tradicionalmente, cuando uno quería compartir código entre distintas aplicaciones, creaba una DLL que contenía toda la parte común. De hecho incluso Windows está construido así, y nada nos impide continuar haciéndolo, incluso con .NET. De hecho, en esta última plataforma es incluso mucho más sencillo, porque nos olvidamos de importaciones y exportaciones de símbolos. Tan sólo hay que crear el tipo de proyecto adecuado, referenciarlo en las nuevas aplicaciones y poco más.

Pero una Portable Library va algo más allá. Aprovechando el hecho de que el resultado final de un ejecutable .NET no es nativo, sino que es código MSIL que luego estará interpretado por la máquina virtual de turno, se puede generar un archivo especial que será compatible entre plataformas tan distintas como Windows Phone y Windows 8.

Desde luego que no estamos limitados a esos dos sistemas operativos. Podemos elegir una biblioteca que sea compatible incluso con la XBOX. De hecho, la lista completa a fecha de hoy para Visual Studio 2012 da soporte para utilizarla con Silverlight 4 o superior, .NET Framework 4 o superior, Windows Phone 7 o superior, Windows Store (Windows 8 PRO y RT), Windows Azure y XBOX.

Es decir, podemos crear una biblioteca que sea compatible con cualquier combinación de lo descrito. Se pueden seleccionar también versiones mínimas de cada producto, como se muestra en la Figura 1.

Figura 1

Dn481340.4BDADEB3E56881ED48C0FA827E356B50(es-es,MSDN.10).png

El autor no tiene instalado el SDK de Azure, por lo que no le aparece en el listado, pero si nos fijamos en la parte inferior, se da la posibilidad de que se instale soporte para nuevas librerías. Actualmente el enlace nos lleva a una página web con el listado completo ( http://msdn.microsoft.com/en-US/hh487283.aspx)

Esto nos creará un proyecto dentro de nuestra solución, que deberemos referenciar desde los que la utilicen. A partir de ese momento todo es automático. De hecho, dentro de la librería Visual Studio sólo nos dejará utilizar el API que sea común a las plataformas seleccionadas, marcando como error cualquier otra cosa.

Y esa es una de las magias de todo esto: no tenemos que preocuparnos si luego, al utilizar nuestra librería en un proyecto, nos encontramos con que no podemos cargarla porque no es compatible o lo que es peor, una vez en ejecución recibimos una excepción de NotImplemented o un crash sin más.

También tiene su contrapartida, y es que, a veces, la parte común nos resulta insuficiente para nuestros propósitos, pero esto tiene una solución sencilla que explicaremos en su momento.

Interfaz y portable library

Nosotros vamos a crear un reloj que nos va a poner la hora justo en el centro de la pantalla y que nos marcará los segundos si así lo queremos. En la parte superior nos mostrará el texto HEX, DEC, OCT o BIN dependiendo de en qué base numérica se esté imprimiendo la hora. En todo lo posible, se usará el interfaz nativo por defecto sin realizar florituras visuales.

Tendrá una pantalla de configuración con tres desplegables que nos permitirán elegir entre mostrar horas, minutos y segundos o sólo minutos y segundos. En otro de ellos elegiremos la base numérica a mostrar entre hexadecimal, decimal, octal y binario. Finalmente el tercero nos permitirá seleccionar si queremos ver la hora y la fecha o sólo la hora.

Esas son las especificaciones funcionales de la aplicación. La Figura 2 contiene una captura de la pantalla principal de la versión de Windows Phone, y la Figura 3 la pantalla de configuración en Windows 8.

Figura 2

Dn481340.670B2930189DEA781AE4D9F61AA6AAFD(es-es,MSDN.10).png

Figura 3

Dn481340.467D7D55769DAA124D5A8231B7695247(es-es,MSDN.10).png

Es evidente que se requiere más análisis, pero de momento y a efectos prácticos es todo lo que necesitamos saber. Conforme vayamos avanzando en el artículo iremos viendo todo ello con mayor detalle.

La idea central de todo esto es tener una base de código común que será utilizado por todas las aplicaciones. Cuanto más código pongamos ahí, menos repetiremos en cada una de ellas.

A nivel teórico, nuestro ideal es tener la parte visual, que es diferente en cada plataforma, en un proyecto independiente del tipo adecuado, y luego todo el código compartido en la portable library. De este modo escribimos una sola vez y utilizamos dos. Veremos luego que la práctica difiere un poco de la teoría, pero no mucho.

Por lo tanto nos creamos tres proyectos. Una Portable Library que llamaremos BinHexClockCommon y que será compatible con Windows Phone 7.5 and higher y .NET for Windows Store apps, por lo que deberemos marcar sólo esas casillas (ver la Figura 1) cuando la creemos. Si tenemos problemas para encontrar este tipo de proyecto, debemos posicionarnos en la raíz de Visual C# dentro del asistente para crear un nuevo proyecto.

Un Blank App del tipo Windows Store que vamos a llamar BinHexClockW8 y finalmente otro Windows Phone App para Windows Phone OS 7.1 con el nombre de BinHexClockWP.

El siguiente paso es referenciar BinHexClockCommon desde cada uno de los otros dos proyectos. Una vez hecho esto, podemos ponernos a trabajar y, para hacernos una idea de cómo queda el asunto nos podemos fijar en la Figura 4.

Figura 4

Dn481340.78543DEE6E75C7C4922C48371C6D8858(es-es,MSDN.10).png


Interfaz y ferretería de Windows Phone (I)

Las interfaces de ambas aplicaciones son muy sencillas y minimalistas. En los dos casos contamos con una pantalla principal que contendrá el reloj y el título y por otro una segunda página con la configuración. Y para pasar de la principal a la de configuración, un botón oculto que se mostrará al tocar la pantalla.

Por lo tanto, para Windows Phone, abrimos MainPage.xaml y, omitiendo el diseñador visual, cambiamos toda la parte del LayoutRoot por el código XAML del Listado 1.


    <Grid x:Name="LayoutRoot" Background="Transparent" Tap="LayoutRoot_OnTap">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Name="ClockBase" HorizontalAlignment="Center" FontSize="60"/>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1">
            <TextBlock Name="ClockDate" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="40"/>
            <TextBlock Name="ClockTime" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="40"/>
        </StackPanel>
        <Button Name="ConfigureButton" Content="Configure" Visibility="Collapsed" Grid.Row="2" Tap="Configure_Tap"/>
    </Grid>

Tenemos, por tanto, un Grid de tres filas, la última de ellas oculta por defecto. En la primera se ha puesto un control de texto llamado ClockBase y que será el encargado de mostrar la cadena indicando la base numérica actual. En la segunda irá el reloj, compuesto de un StackPanel vertical conteniendo dos sendos controles de texto, uno con la fecha y otro con la hora. Finalmente la última fila contiene un botón que nos abrirá la pantalla de configuración.

Para aquellos no muy duchos en XAML, se pueden observar algunos detalles interesantes. Por un lado las definiciones de las celdas dentro del control Grid en las que la central tiene como altura un asterisco, y las otras dos la palabra Auto. Repartimos así la distribución de la pantalla, indicando que la central ocupará todo el espacio libre (el asterisco) que las otras dos no necesiten, que a su vez deben ajustarse de forma automática a su contenido. Como el botón tiene la visibilidad colapsada, la pantalla realmente se dividirá en dos partes hasta que se dispare el evento LayoutRoot_OnTap, que deplegará el botón al cambiar su visibilidad con el código siguiente:


private void LayoutRoot_OnTap(object sender, GestureEventArgs e)
{
            ConfigureButton.Visibility=Visibility.Visible;
            m_buttonTimer.Start();
 } 

También tenemos que crearnos un Timer, que nos servirá para volver a colapsar dicho botón transcurrido un tiempo. ¿Cómo? Pues insertando el siguiente código dentro del constructor de nuestra página principal, que si no hemos tocado nada se llamará MainPage:

            m_buttonTimer=new DispatcherTimer();
            m_buttonTimer.Interval = new TimeSpan(0,0,0,3);
            m_buttonTimer.Tick += (sender, e) =>
                {
                    ConfigureButton.Visibility=Visibility.Collapsed;
                    m_buttonTimer.Stop();
         };

De nuevo aquí tenemos alguna cosa que pudiera parecer extraña a quien no esté familiarizado con las últimas versiones de C#. El evento Tick del temporizador está indicado mediante un operador lambda en lugar de un método. En este caso no hay ventaja alguna en hacerlo así, pero en otros sí, ya que podríamos querer aprovechar las variables locales externas al lambda dentro del mismo.

El resultado final de todo esto consiste en que cuando tocamos la pantalla nos debe aparecer el botón de configuración y transcurrido un tiempo (tres segundos en nuestro caso), se vuelve a cerrar ella sola.

Antes de pasar a la página de configuración, debemos crear el evento que nos la va a lanzar y que se ha declarado en el código XAML:


private void Configure_Tap(object sender, GestureEventArgs e)
{
            NavigationService.Navigate(new Uri("/ConfigurePage.xaml", UriKind.Relative));
 }

Esto también nos da el nombre de la página de configuración, que debemos crear con el asistente adecuado.

Windows Phone toolkit

Pero a la hora de diseñar la página de configuración nos encontramos con un pequeño problema, y es que el control combo de Windows Phone es igual que el nativo de Windows, por lo que no es muy adecuado para ser tocado con los dedos y tampoco tiene los estilos adecuados. (Esto nos indica, de refilón, que debajo de Windows Phone hay un Windows normal y corriente. De hecho en Windows Phone 7.x es Windows CE y en la siguiente versión, un subconjunto de Windows RT.)

Es aquí cuando viene al rescate una librería que complementa al SDK de Windows Phone y que tiene el mismo nombre que se ha dado a esta sección. Podemos descargarlo e instalarlo a mano, pero la mejor forma de hacerlo es utilizando las extensiones que vienen integradas dentro del propio Visual Studio.

Para ello hacemos clic en Tools -> Extensions and Updates y buscamos Windows Phone toolkit. Una vez instalado, volvemos al menú Tools, pero esta vez elegimos Library package Manager y dentro de él, Manage NuGet Packages for Solution.

Es ahí dentro donde podremos elegir qué extensiones vamos a utilizar y en qué proyecto. Si nos fijamos en la Figura 5, podremos pulsar Manage y elegir en qué proyecto de nuestra solución queremos tener disponible, en este caso, el toolkit (Figura 6).

Figura 5

Dn481340.7C3FBF255483D7A094973BE5917D4132(es-es,MSDN.10).png

Figura 6

Dn481340.40BCE0FA8C2A2A99F409A32596637E9F(es-es,MSDN.10).png


Existe un motivo muy claro para realizar las cosas así. De este modo, cada vez que haya una actualización de las librerías utilizadas, nos aparecerá en el IDE y todo se gestionará de forma automática, de modo que siempre tendremos nuestras librerías al día.

En el caso que nos ocupa, el citado Toolkit, junto a las Codign4Fun son dos bibliotecas prácticamente imprescindibles para realizar cualquier proyecto en Windows Phone. De hecho Microsoft debería incluirlas de serie en el SDK. (Que es lo que realmente ha hecho con Windows Phone 8. El componente LongListSelector del toolkit es tan importante y tan útil que no sólo está disponible dentro de Windows Phone 8, sino que ha sido convertido en un elemento nativo, aumentando así el rendimiento general del mismo.)

Interfaz y ferretería de Windows Phone (II)

Tras la pausa, volvemos a la página (según la terminología de Windows Phone, las ventanas son páginas y se navega a través de ellas) de configuración. Como vamos a utilizar un componente del toolkit citado en la sección anterior, debemos añadir su referencia para que la página sepa de qué va la cosa. Esto se hace añadiendo una nueva cabecera al principio del fichero XAML:


    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

Después de esto ya podemos utilizar cualquier componente disponible en el toolkit dentro de la página actual. No vamos a poner el código completo aquí sino sólo el de uno de los desplegables:

<toolkit:ListPicker Name="ShowTimeList">
    <toolkit:ListPickerItem Content="Show Hours, Minutes and Seconds"/>
    <toolkit:ListPickerItem Content="Show Hours and Minutes"/>            </toolkit:ListPicker>

Si el lector desea ver el código completo, puede hacerlo mirando el código fuente, abriendo la solución y haciendo doble clic sobre ConfigurePage.xaml dentro del proyecto BinHexClockWP.

Y con esto terminamos la parte visual de Windows Phone. Se ha explicado con más o menos detalle todo el andamiaje visual y se ha incluido algo de ferretería (llamado code behind dentro del argot al uso) para que nuestras pantallas (perdón, páginas) sean funcionales y cumplan su objetivo.

Interfaz y ferretería de Windows 8

Windows 8 también utiliza XAML como lenguaje de descripción de interfaces pero sus elementos difieren bastante de los de Windows Phone. El autor supone que la idea de Microsoft es unificar ambos, pero eso deberá ocurrir casi seguro en una versión futura ya que ahora Windows Phone 8 debe mantener la compatibilidad con Windows Phone 7 y 7.5.

Es por ese motivo por el cual la parte de interfaz está separada en proyectos diferentes y, si no se ha tocado nada después de crear el proyecto de Windows 8, nuestra página principal se llamará igual que la de Windows Phone: MainPage.xaml.

Pese a todas las diferencias existentes, nuestro código XAML para la ventana principal de Windows 8 es prácticamente igual (Listado 2), pero como el envolvente difiere sensiblemente entre las dos plataformas, tenemos que separarlo en proyectos diferentes.

   <Grid x:Name="grid" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Name="ClockBase" Grid.Row="0" HorizontalAlignment="Center" FontSize="{Binding ActualWidth, ElementName=grid, Mode=OneWay, Converter={StaticResource FontSizeConverter}}" />
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1">
            <TextBlock Name="ClockDate" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ActualWidth, ElementName=grid, Mode=OneWay, Converter={StaticResource FontSizeConverter}}"/>
            <TextBlock Name="ClockTime" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ActualWidth, ElementName=grid, Mode=OneWay, Converter={StaticResource FontSizeConverter}}"/>
        </StackPanel>
        <Button Name="ConfigureButton" Content="Configure" HorizontalAlignment="Stretch" Visibility="Collapsed" Tapped="ConfigureButton_Tapped" Grid.Row="2" HorizontalContentAlignment="Center"/>
    </Grid> 

No vamos a comentar mucho aquí, ya que prácticamente todo es lo mismo. El evento disparado al tocar la ventana tiene exactamente el mismo código, y el que nos lanza la página de configuración apenas varía:


  private void ConfigureButton_Tapped(object sender, TappedRoutedEventArgs e)
  {
            Frame.Navigate(typeof (ConfigurePage));
  }

Y el caso del temporizador también es idéntico, de hecho es una parte que podríamos haber puesto dentro de la Portable Library, pero no lo hemos dispuesto así porque aunque el código es similar, las instancias (el objeto) sobre las que se trabaja son diferentes y habría que haberlas pasado y contenido como diferentes variables en la parte de la biblioteca: demasiado código para tan poco resultado.

Finalmente la página de configuración, que hemos llamado igual, ConfigurePage.xaml, contiene, esta vez sí, tres controles Combo de Windows 8 que tienen todo el comportamiento adecuado. De nuevo nos referimos al proyecto de ejemplo suministrado si el lector tiene interés en ver cómo está construido todo.


Y con esto y un bizcocho, hemos finalizado la parte visual de ambas aplicaciones. Todavía tendremos que volver a ellas para añadir más code behind para enlazar las páginas de configuración con la configuración en sí, ya que cada página debe mostrar la opción activa para cada elemento y a su vez, indicar al sistema que se ha modificado cuando así ocurra.

En la pantalla principal también tendremos que tocar varias cosas. La más importante es el hecho de que el reloj debe recibir la fecha y la hora de algún modo, así como su reacción ante los cambios de configuración.

Pero eso lo haremos después de construir nuestra Portable Library.

Dentro de la Portable Library. Diseño básico.

Un análisis somero de la funcionalidad de la aplicación nos dice qué vamos a necesitar. Ya tenemos construido el interfaz visual, por lo que nos faltan tres cosas:

  • Un metrónomo que nos marque los cambios de segundo o de minuto e informe a la parte visual del mismo.
  • Algo que nos guarde y recupere la configuración de la aplicación. A saber: en qué base numérica estamos enseñando la hora y si se muestra al cambio del minuto o del segundo, y si mostramos también la fecha o no.
  • Un conversor entre bases numéricas, que dependerá de la configuración actual y de la base elegida.

Si pensamos un poco, la aplicación deberá cargar la configuración almacenada o una por defecto si fuera la primera vez que se ejecute. Después inicializará el metrónomo con los parámetros de tiempo adecuados. Y a su vez éste, una vez arrancado, deberá despertarse cada cierto tiempo, mirar la fecha y hora actuales, convertirlas a la base numérica adecuada e informar de ello a la aplicación para que dibuje esos cambios sobre la pantalla.

Como vemos, la cosa se va organizando de forma más o menos adecuada. Nos queda determinar quién se encargará de qué roles. Esto también es bastante sencillo.

La aplicación contendrá una instancia del metrónomo y le pasará una referencia a la configuración y un delegado que va a recibir dos cadenas, conteniendo la fecha y la hora formateadas de forma adecuada. De este modo delegamos en dicha clase todo el trabajo ya que a la parte visual sólo le interesa el resultado.

El metrónomo guardará para sí el delegado y el intervalo y a su vez contendrá una instancia del conversor entre bases, al que le pasaremos la referencia a la configuración.

Todavía nos quedará más claro si echamos un vistazo a la Figura 7 en la que se nos representa el diagrama de clases generado por Visual Studio. Nuestra Portable Library contiene tres ficheros. El metrónomo está declarado en ClockController.cs, las opciones en ShowOptions.cs y el conversor entre bases en ClockConverter.cs. Más sencillo imposible.

Figura 7

Dn481340.92400995954DFE0463A25ED876243039(es-es,MSDN.10).png

Yendo de menos a más, podemos observar varias enumeraciones, que recogemos juntas en el Listado 3 y que se encuentran, como es lógico, declaradas en ShowOptions.cs.

    public enum ShowMinutesOrSeconds
    {
        Seconds=0,
        Minutes=1
    }

    public enum ShowBase
    {
        Decimal=0,
        Hexadecimal=1,
        Octal=2,
        Binary=3
    }

Poca descripción necesitan. La primera nos indica si debemos mostrar los segundos o no, y la segunda qué base numérica hemos elegido. Aquí sí que podemos observar un “truco”, y es que le hemos asignado números comenzando desde cero. El por qué lo veremos más adelante.

El Listado 4 recoge la declaración del delegado con el cual ClockController va a informar a la parte visual de que se ha producido un cambio y que es necesario actualizar la fecha y la hora.

    public delegate void DateTimeChangedDelegate(string strTime,string strDate); 

Dentro de la Portable Library. ClockController.

Ahora sí, ahora ya podemos pasar a todo el meollo del código. Como ya hemos visto, toda la Portable Library está centrada en la clase ClockController, que es el metrónomo que se va a encargar de avisar cada vez que tengamos que actualizar los valores de la hora o la fecha.

El Listado 5 recoge la clase en su totalidad y es muy fácil de entender.

using System.Threading;

namespace BinHexClockCommon
{
    public delegate void DateTimeChangedDelegate(string strTime,string strDate);

    public class ClockController
    {
        private readonly Timer m_timer;
        private int m_interval;

        private readonly ClockConverter m_clockConverter;
        private readonly DateTimeChangedDelegate m_delegate;

        public ClockController(DateTimeChangedDelegate callback,Options options)
        {
            m_delegate += callback;

            m_clockConverter=new ClockConverter(options);
            m_timer = new Timer(TimerCallback, null, 0, Timeout.Infinite);

            ConfigureInterval(options.ShowMinutesOrSeconds);
        }

        public void ConfigureInterval(ShowMinutesOrSeconds interval)
        {
            if (interval == ShowMinutesOrSeconds.Minutes)
                m_interval = 60 * 1000;   //One minute
            else
                m_interval = 1000;  //One second

            StartTimer();
        }

        public void StartTimer()
        {
            m_timer.Change(0, m_interval);
        }

        public void StopTimer()
        {
            m_timer.Change(0, Timeout.Infinite);
        }

        private void TimerCallback(object obj)
        {
            if (m_delegate != null)
                m_delegate(m_clockConverter.ConvertTime(),m_clockConverter.ConvertDate());
        }
    }
}

La mayor parte del código útil está en el constructor. En primer lugar almacenamos el delegado recibido porque luego tendremos que utilizarlo cada vez que sea necesario informar del cambio de hora.

Después creamos una instancia de la clase ClockConverter, que es la encargada de transformar la fecha y la hora en la base adecuada. Le pasamos las opciones, para que sepa en qué base se debe realizar la conversión. Luego veremos por qué pasamos el objeto completo y no el valor que nos interese.

El siguiente paso es construir el temporizador que será el encargado de marcarnos el tiempo y, finalmente, configuramos el intervalo de disparo, que se realiza en un método aparte ya que también será utilizado por el configurador cuando se cambie entre mostrar segundos y no.

Los métodos StartTimer() y StopTimer() hablan por sí mismos, y finalmente TimerCallback() es el método que se ejecutará en cada intervalo de tiempo, que a su vez ejecutará el delegado con la conversión.

¿Por qué estamos utilizando este Timer y no un DispatcherTimer, que es mucho más fácil de manejar (y que encima se ejecuta en el hijo de la interfaz)? El motivo no es otro que se trata del único temporizador que se encuentra disponible como elemento común a todos los frameworks utilizados, y si queremos que nuestra librería portable esté disponible para ellos es la única forma de hacerlo.

Acabamos de realizar un compromiso. Quizás no sea la solución más óptima si la cogemos desde el punto de vista de una de las dos aplicaciones, pero sí que es la mejor cuando necesitamos que esté disponible para ambas sin tener que rizar el rizo.

Y esto nos obliga a mantener el código de una forma o de otra. En nuestro caso, la clase Timer del .NET Framework es bastante limitada y apenas cuenta con métodos y opciones. El método ConfigureInterval() recibe el valor ShowMinutesOrSeconds de la configuración y cambia la variable interna m_interval al valor numérico adecuado, que debe estar en milisegundos, que en la forma que tiene el temporizador de recibir el lapso de disparo. Y después de la conversión, se pone en marcha.

Por lo tanto debemos tener en cuenta que, cada vez que la configuración cambie, hemos de llamar a este método. El autor no ha encontrado una forma más directa de realizar el cambio. Quizás podríamos haber utilizado una propiedad que podríamos haber llamado Enable y que habría hecho el trabajo en sus métodos get/set, pero uno piensa que no es adecuado en este contexto puesto que estamos realizando una acción, no la lectura o escritura de un valor con efectos laterales.

Con StopTimer() paramos el disparo de eventos. ¿Cómo? Pues dadas las limitaciones de nuestro Timer, le decimos que el intervalo de disparo es infinito. A su vez StartTimer() arranca el temporizador con el m_interval que hemos convertido y guardado.

Finalmente, en cada disparo se ejecuta el método TimerCallback(), que a su vez comprueba si nuestro delegado es válido y si es así, lo ejecuta, no sin antes haber realizado la conversión a la base adecuada mediante sendas llamadas a los métodos ConvertTime() y ConvertDate() del objeto m_clockConverter.

¿Por qué cambiamos el período de tiempo base en lugar de tener, por ejemplo, el temporizador programado para un segundo y simplemente contar hasta el cambio de minuto? ¿Por qué lo paramos y lo arrancamos de forma manual? La explicación es tremendamente sencilla: ahorro ciclos de CPU y por ende de batería. Es decir, cuanto más ligero sea nuestro programa, menos batería gastará cuando esté en ejecución. Aquí puede parecer trivial, pero en otras situaciones quizás no lo sea. La forma elegida es la óptima para tener un temporizador, ya que internamente Windows lo que hace es programarse un evento y dispararlo de forma adecuada, ahorrando también ciclos de CPU dentro del núcleo del sistema.

Dentro de la Portable Library. ClockConverter.

Veamos ahora la implementación del conversor entre bases, que podemos ver en el Listado 6.

using System;
using System.Linq;
using System.Text;

namespace BinHexClockCommon
{
    public class ClockConverter
    {
        private readonly Options m_options;

        public ClockConverter(Options options)
        {
            m_options = options;
        }

        public string ConvertDate()
        {
            return ConvertToBase(DateTime.Now.ToString("d"));
        }

        public string ConvertTime()
        {
            var format = m_options.ShowMinutesOrSeconds == ShowMinutesOrSeconds.Minutes?"t":"T";

            return ConvertToBase(DateTime.Now.ToString(format));
        }

        private static char FindSeparator(string origin)
        {
            if (origin.Contains("/"))
                return '/';
            if (origin.Contains(":"))
                return ':';
            if (origin.Contains("-"))
                return '-';

            if (origin.Contains(" "))
                return ' ';

            for (var index = 0; index < origin.Length; index++)
                if (!Char.IsDigit(origin[index]))
                    return origin[index];

            return '|'; //Never here. 
        }

        private static string Resize(string numberAsString)
        {
            return numberAsString.PadLeft(numberAsString.Length > 2 ? 4 : 2, '0');
        }

        private string ConvertToBase(string origin)
        {
            var separator = FindSeparator(origin);

            var tokens = origin.Split(new[] {separator});

            var baseNum=10;
            switch (m_options.ShowBase)
            {
                case ShowBase.Hexadecimal:
                    baseNum = 16;
                    break;
                case ShowBase.Octal:
                    baseNum = 8;
                    break;
                case ShowBase.Binary:
                    baseNum = 2;
                    break;
            }

            var sb = new StringBuilder();
            foreach (var token in tokens.Where(token => !String.IsNullOrEmpty(token)))
            {
                int number;
                if (Int32.TryParse(token, out number))
                {
                    if (sb.Length != 0) //Omitted only for first number
                        sb.Append(separator);
                    sb.Append(Resize(Convert.ToString(number,baseNum)));
                }
                else
                {
                    sb.Append(' ');
                    sb.Append(token);
                }
            }

            return sb.ToString();
        }
    }
}

Ya hemos comentado que el conversor almacena una referencia a las opciones, que todavía no hemos visto en detalle. ¿Por qué una referencia y no tomar directamente la base numérica sin más? Es una pregunta un tanto delicada y cuya respuesta podría ser discutible.

En primer lugar, la configuración contiene tres parámetros, de los que esta clase utiliza dos, por lo que tenemos una sola variable para ambos. En segundo lugar, cuando el usuario cambie algo en las pantallas de configuración, el cambio es inmediato ya que, como todos sabemos, en C# las clases funcionan por referencia y no por valor. De ese modo nos ahorramos tener que cambiar manualmente la configuración cuando ésta cambie, y el objeto instanciado siempre tomará los valores correctos.

Los dos métodos miembro públicos, la interfaz, son ConvertDate() y ConvertTime(), que devuelven una cadena con sus respectivos valores convertidos y formateados de forma adecuada. El funcionamiento del primer método es directo: devuelve el resultado de la llamada a ConvertToBase() al que se le pasa la fecha actual en formato corto.

Para el segundo método primero asignamos el formato de hora adecuado (con o sin segundos) y devolvemos el resultado de la llamada al mismo método que antes.

Por lo tanto, todo el meollo de la clase está en ConvertToBase(), que recibe una cadena conteniendo la fecha o la hora y devuelve otra con su valor convertido a la base configurada.

Aunque a simple vista pueda parecer algo complicado, lo cierto es que realizar la conversión es una tarea poco menos que trivial. Lo primero que hacemos es encontrar el separador entre los diferentes valores numéricos de la cadena. No podemos asumir que siempre será una barra de división o dos puntos. Por ejemplo, algunos países usan el guión como separador de fecha:

var separator = FindSeparator(origin);

Observemos ciertos detalles de este método. En primer lugar buscamos los caracteres posibles, y si no están, comenzamos a recorrer la cadena y devolvemos el primer valor que no sea numérico. Si aun así no hemos encontrado nada, estamos ante un grave problema ya que la cadena que hemos pasado no es válida. Como sabemos a ciencia cierta por nuestro código que la misma proviene de una fecha, le damos un valor aleatorio que nos sirva de separador y lo devolvemos ya que en principio nunca debería ocurrir, y en el hipotético caso de que sí (por ejemplo, una hipotética futura versión de Windows que devuelva la fecha en marciano, al menos nuestra aplicación no se cerrará de forma inesperada.

Estamos ante una disyuntiva. Si disparamos una excepción debemos controlarla en un nivel superior, y si devolvemos un valor inadecuado seguro que algo falla más arriba. En este caso que nos ocupa el autor ha preferido devolver un valor general ya que en ese caso específico la cadena es un único número, que será convertido y separado por nuestro valor arbitrario.

También podríamos haber dejado sólo el bucle y olvidarnos de las primeras comparaciones, pero en este caso se ha preferido dejarlo así por mor de claridad.

Luego tenemos que dividir la cadena recibida en un array de cadenas que va a contener sólo los valores numéricos, cosa que .NET Framework nos lo da gracias al método Split() de la clase String. Aprovechando que ya sabemos cuál es el separador, nuestro código es:

var tokens = origin.Split(new[] {separator});

El bloque de código siguiente asigna el valor numérico de la base a la que vamos a convertir la cadena a la variable baseNum mediante una sentencia swtich:


          var baseNum=10;
            switch (m_options.ShowBase)
            {
                case ShowBase.Hexadecimal:
                    baseNum = 16;
                    break;
                case ShowBase.Octal:
                    baseNum = 8;
                    break;
                case ShowBase.Binary:
                    baseNum = 2;
                    break;
            }

Ya tenemos todo lo necesario para iniciar la conversión. En separator está el separador de los diferentes números, que deberemos volver a integrar en el resultado. En tokens están la lista de valores numéricos a convertir, que pueden ser dos o tres dependiendo de si es una fecha, o una hora con o sin segundos. Y en baseNum la base para realizar la conversión.

El resto del método recorre todos y cada uno de los tokens, realiza la conversión mediante el método TryParse() de la clase Int32 que tiene la particularidad de aceptar un segundo parámetro como base numérica a la que convertir, y los va juntando en una nueva cadena a partir de un StringBuilder(). Recordemos que esta clase es la óptima cuando estamos trasteando de forma repetida con una cadena para evitar grandes consumos de memoria y tener que construir y destruir cadenas de forma continuada.

También utilizamos un método estático que nos rellena con ceros a la derecha el valor numérico a partir de su tamaño. Si es inferior a un dígito, se rellena a dos, y si es superior a dos, lo hará a cuatro. De este modo, tanto si es un año de dos dígitos o de cuatro, la función hará su tarea de forma adecuada.

Y con esto terminamos la parte de la conversión.

Dentro de la Portable Library. ShowOptions.

Ya solo nos queda la clase que mantiene, guarda y recupera las opciones elegidas por el usuario. Miramos en la documentación y ¡oh miseria¡ ¡Guardar y recuperar el estado de una aplicación se hace de forma diferente en Windows Phone y en Windows 8!

Que no panda el cúnico, hay solución a esto. Lo primero que nos viene a la mente es sacar esta clase de la Portable Library y ponerla en cada aplicación y que luego sea la aplicación la que instancie una clase común y le pase los valores cargados.

No obstante hay otra mucho mejor que aunque no aporte muchas ventajas a lo descrito arriba en lo que nos ocupa ahora, sí que las tiene cuando las cosas se convierten en más complejas. Para algo C# es un lenguaje orientado a objetos que soporta polimorfismo. Nuestro acercamiento va a ser justo el contrario que el del párrafo anterior.

Vamos a trabajar con una clase normal llamada Options y otra abstracta llamada LocalSettings (ver Figura 7) Esta última contendrá unos métodos genéricos que deberán ser implementados por cada una de las aplicaciones, utilizando su API interno, y la clase Options utilizará la instancia no virtual de dicha clase para acceder a los métodos y así recuperar y guardar su estado.

De esta forma, la Portable Library continua sin saber nada de los detalles de las diferentes implementaciones (y que la harían incompatible con la otra aplicación), y a su vez comparte la mayor parte del código. (En el ínterin, es lo que podríamos haber hecho con el temporizador que nos oculta de forma automática el botón de configuración.)

Dicho y hecho. El Listado 7 recoge íntegramente la parte que atañe a la Portable.


    public class LocalSettingsParametersNames
    {
        public const string ShowTime = "ShowMinutesOrSeconds";
        public const string ShowBase = "ShowBase";
        public const string ShowOnlyTime = "ShowOnlyTime";
    }

    public abstract class LocalSettings
    {
        public abstract void SetValue(string name, object value);
        public abstract object GetValue(string name,object defaultValue);

        public static LocalSettings Instance { get; set; }
    }

    public class Options
    {
        private bool m_modified;
        private ShowBase m_showBase;
        private ShowMinutesOrSeconds m_ShowMinutesOrSeconds;
        private bool m_showOnlyTime;

        public ShowBase
        {
            get { return m_showBase; }
            set
            {
                if(m_showBase==value)
                    return;

                m_showBase = value;
                m_modified = true;
            }
        }

        public ShowMinutesOrSeconds
        {
            get { return m_ShowMinutesOrSeconds; }
            set
            {
                if (m_ShowMinutesOrSeconds == value)
                    return;

                m_ShowMinutesOrSeconds = value;
                m_modified = true;
            }
        }

        public bool ShowOnlyTime
        {
            get { return m_showOnlyTime; }
            set
            {
                if(m_showOnlyTime==value)
                    return;

                m_showOnlyTime = value;
                m_modified = true;
            }
        }

        public Options()
        {
            ShowBase = ShowBase.Decimal;
            ShowMinutesOrSeconds= ShowMinutesOrSeconds.Minutes;
            ShowOnlyTime = false;

            Load();
            m_modified = false;
        }

        public Options(ShowBase b, ShowMinutesOrSeconds t)
        {
            ShowBase = b;
            ShowMinutesOrSeconds = t;
            ShowOnlyTime = false;

            Load();
            m_modified = false;
        }

        ~Options()
        {
            Save();
        }

        private void Load()
        {
            ShowBase = (ShowBase)LocalSettings.Instance.GetValue(LocalSettingsParametersNames.ShowBase, ShowBase.Decimal);
            ShowMinutesOrSeconds = (ShowMinutesOrSeconds)LocalSettings.Instance.GetValue(LocalSettingsParametersNames.ShowTime, ShowMinutesOrSeconds.Minutes);
            ShowOnlyTime =(bool) LocalSettings.Instance.GetValue(LocalSettingsParametersNames.ShowOnlyTime, false);
        }

        public void Save()
        {
            if (!m_modified) 
                return;

            LocalSettings.Instance.SetValue(LocalSettingsParametersNames.ShowBase, (int)ShowBase);
            LocalSettings.Instance.SetValue(LocalSettingsParametersNames.ShowTime, (int)ShowMinutesOrSeconds);
            LocalSettings.Instance.SetValue(LocalSettingsParametersNames.ShowOnlyTime,ShowOnlyTime);
            m_modified = false;
        }

        public string GetBaseAsString()
        {
            switch (ShowBase)
            {
                case ShowBase.Hexadecimal:
                    return "HEX";
                case ShowBase.Octal:
                    return "OCT";
                case ShowBase.Binary:
                    return "BIN";
                default:
                    return "DEC";
            }
        }
    }


Primero definimos las cadenas de cada uno de los valores que vamos a almacenar. Para ello declaramos la clase LocalSettingsPatametersName y dentro de ella tres valores cadena constante. Así siempre nos referiremos a las mismas cadenas en todo el código y evitamos errores de tecleo.

El siguiente paso consiste en declarar la clase abstracta, que llamamos LocalSettings y que contiene dos métodos virtuales puros, uno para obtener un valor y otro para almacenarlo, con los originales nombres de SetValue() y GetValue(). También declaramos una instancia de la propia clase llamada Instance a través de una propiedad.

El código de Options contiene las tres propiedades públicas que ya se han visto a lo largo del código y que cuando se escriben, si cambian su valor actual, activan una variable llamada m_modified que luego se utilizará para guardar los valores si han sido modificados, para así evitar escribir aunque sus valores no hayan cambiado, alargando la vida de las memorias Flash de nuestros teléfonos y tabletas. (Es evidente que unas miles de escrituras redundantes no van a estropear nuestro aparato, pero también lo es que el número de las mismas es limitado y, sin entrar en detalles internos sobre si realmente el sistema operativo sobreescribe o no un valor que ya esté, es mejor curarnos en salud y tener buenas prácticas.)

La parte interesante de esta clase se sitúa en los métodos Load() y Save(), que utilizan la Instance de la clase abstracta para guardar y recuperar los valores necesarios. Vea el lector el Listado 7.

Para terminar esta sección tenemos que volver a los proyectos de cada sistema operativo y declarar la parte no virtual de LocalSettings. Para ello, en cada uno, creamos una clase que herede de la citada y que implemente los métodos virtuales. De este modo, cuando Options llame, por ejemplo, a GetValue() a través de Instance, estará ejecutando el código de cada proyecto y no el de la Portable Library. Vamos a llamar a dicha clase Settings y la vamos a almacenar en Settings.cs dentro de cada proyecto. De manual.

El código de Settings para Windows Phone está recogido en el Listado 8 y el de Windows 8 en el Listado 9. Un simple vistazo nos muestra cómo dentro de cada método utilizamos el API local de cada sistema para guardar y recuperar los valores.

using System.IO.IsolatedStorage;
using BinHexClockCommon;

namespace BinHexClockWP
{
    class Settings: LocalSettings
    {
        public override void SetValue(string name, object value)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains(name))
                IsolatedStorageSettings.ApplicationSettings[name] = value;
            else
                IsolatedStorageSettings.ApplicationSettings.Add(name, value);
        }

        public override object GetValue(string name, object defaultValue)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains(name))
                return IsolatedStorageSettings.ApplicationSettings[name];

            return defaultValue;
        }
    }
}

using BinHexClockCommon;
using Windows.Storage;

namespace BinHexClockW8
{
    class Settings: LocalSettings
    {
        public override void SetValue(string name, object value)
        {
            if(ApplicationData.Current.LocalSettings.Values.Keys.Contains(name))
                ApplicationData.Current.LocalSettings.Values[name] = value;
            else
                ApplicationData.Current.LocalSettings.Values.Add(name,value);
        }

        public override object GetValue(string name, object defaultValue)
        {
            if (ApplicationData.Current.LocalSettings.Values.ContainsKey(name))
                return ApplicationData.Current.LocalSettings.Values[name];
            return defaultValue;
        }
    }
}

Una vez que se han declarado las clases, tenemos que encontrar un lugar para instanciarlas. Aquí de nuevo nos tenemos que remitir a cada proyecto. El lugar más adecuado es en los respectivos constructores de la aplicación, que están situados en ambos casos en el fichero App.xaml.cs.

En ambos casos el código es idéntico. Declaramos una variable pública y estática del tipo Options, que será donde realmente estén almacenados los valores en cada aplicación y declaramos un constructor estático. El código debería quedarnos así:

      public static Options;

        static App()
        {
            LocalSettings.Instance = new Settings();
            Options=new Options();            
        }

¿Qué estamos haciendo aquí? En primer lugar, como sólo debe haber unas opciones globales a la aplicación, declaramos Options como estático. Al declarar también un constructor estático, éste se llamará la primera vez que se haga referencia a App, por lo que nuestras opciones estarán disponibles desde el inicio del programa. No es necesaria ningún tipo de sincronización para evitar código reentrante porque sólo existe una App, cosa que nos garantiza el entorno de ejecución.

Dentro del constructor creamos nuestra Instance estática de LocalSettings a partir de la clase hija propia, con lo que Instance realmente contiene un objeto diferente en cada aplicación y que es compatible con el API local. Y finalmente creamos Options, que usará el objeto anterior para sacar y meter datos del almacenamiento local de la aplicación.

El último paso para que todo funcione correctamente es localizar el constuctor de ambas MainPage.xaml.cs e insertar la línea que construye la clase ClockController, justo debajo de InitializeComponent(). Tampoco debemos olvidar declarar una variable privada de ese tipo en la clase:

m_clock=new ClockController(Timer,App.Options);

Con esto terminamos todo el meollo de los proyectos que llevamos entre manos. En principio deberían ejecutarse cada uno en su sistema, aunque de momento no podremos cambiar la configuración.

Dejar al usuario que cambie la configuración

El acercamiento para guardar y recuperar la configuración es en principio el mismo: la recuperamos y asignamos a cada desplegable en el evento de página cargada y justo al revés en el de página descargada. En ambos caso los eventos se llaman igual, Loaded y Unloaded, aunque cuando los asignamos de forma visual el nombre del evento cambia.

Incluso si hemos sido cuidadosos con los nombres de los desplegables de tipo combo, el código puede terminar siendo idéntico. El Listado 10 recoge dicho código para Windows Phone.

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            ShowTimeList.SelectedIndex = (int) App.Options.ShowMinutesOrSeconds;
            ShowBaseList.SelectedIndex = (int) App.Options.ShowBase;

            ShowOnlyTimeList.SelectedIndex = App.Options.ShowOnlyTime ? 1 : 0;
        }

        private void PhoneApplicationPage_Unloaded(object sender, RoutedEventArgs e)
        {
            App.Options.ShowBase = (ShowBase)ShowBaseList.SelectedIndex;
            App.Options.ShowMinutesOrSeconds = (ShowMinutesOrSeconds) ShowTimeList.SelectedIndex;
            App.Options.ShowOnlyTime = ShowOnlyTimeList.SelectedIndex != 0;

            App.Options.Save();
        }

No hay mucho secreto aquí, salvo que hemos hecho coincidir el índice de cada combo con el valor numérico entero de la opción, de modo que sólo es necesaria una asignación directa.

Este truco es un poco peligroso y hay que ser cuidadosos en extremo, sobre todo si hay más de una mano tocando el código, porque un cambio inadvertido de posición puede trastocar todo el asunto. Aquí no lo hemos hecho, pero podría haber sido conveniente, si queremos hacerlo así, declarar un array de cadenas justo al lado de la declaración de cada enumeración y asignarlo manualmente en el constructor de la página o en el evento de carga.

Bueno, ahora sí, ahora ya tenemos listas nuestras aplicaciones y podemos probarlas.

Consideraciones sobre el código del ejemplo

Quizás el lector se pregunte por qué se ha hecho tanto hincapié en los detalles sobre si guardamos una referencia a la configuración o sólo el valor, o si esta clase ha de encargarse de este o aquél punto en lugar de este otro si el programa en cuestión se puede escribir en una tarde. Efectivamente, para un programa de estas características quizá (y sólo quizá) sobren la mitad de las elucubraciones descritas en el texto, pero debemos ser conscientes de algo que a poca experiencia que uno tenga se convierte en una aterradora realidad: un programa pequeño y sencillo, puede convertirse en un monstruo intratable cuando va creciendo conforme se le van añadiendo más y más características, por lo que preguntarse por todas y cada una de las facetas de cada línea de código no es algo que debamos descuidar. Incluso haciéndolo así siempre se suele terminar con código bastante parcheado e ininteligible. Recordemos que este es un ejemplo básico, pero desde luego si quieres que tu aplicación tenga éxito en la tienda, tu aplicación debe ser buena y completa y verás cómo el código sube de complejidad de forma exponencial.

También se habrá preguntado por qué no hay comentarios en el texto. El autor considera que en este ejemplo no son necesarios y que es el propio código el que se auto describe sin más. ¿Enum ShowMinutesOrSeconds? ¿DateTimeChangedDelegate? ¿ConfigureInterval? ¿Necesita eso algún tipo de comentario? El autor piensa que no. Con solo echar un vistazo al código se entiende todo. De hecho, uno prefiere organizar las cosas en carpetas jerárquicas y pequeños ficheros conteniendo una sola clase o enumeración que justo al contrario, ponerlo todo en un mismo sitio y llenarlo de miles de comentarios que muchas veces terminan estando desincronizados con el código.

Otro detalle que podría haberle llamado la atención al lector es el uso del prefijo m_ en las variables miembro de las clases. Es una preferencia tomada de C++ y que el autor conserva desde que aprendió a programar. Quizás no sea moderno, quizás no sea óptimo, pero desde luego en cualquier sitio, si uno ve m_loquesea, sabrá que es una variable miembro, y si ve s_loquesea que estática privada, y S_loquesea, estática pública… sin necesidad del uso de IntelliSense ni de nada más.

Finalmente está el tema de las pruebas automáticas y test varios que siempre se recomienda hacer y programar. Como se podrá comprobar, el código no trae nada de eso aunque estamos hablando de una aplicación que va a ir a una tienda oficial y que debería cumplir unos mínimos de calidad. En primer lugar la realización de test manuales es trivial, y en segundo, estamos ante un ejemplo y tampoco debemos alargar mucho el texto con detalles accesorios. (Tampoco hemos implementado el patrón MVVM, también por no alargarnos demasiado y también, hay que reconocerlo, porque el autor, por experiencia propia en proyectos de envergadura, sabe que cuando la cosa se complica de verdad, el patrón pierde bastante utilidad cuando, por ejemplo, la visualización de un elemento requiere un XAML de tres mil líneas con cincuenta o cien converters y cosas así.)

Preparándose para las tiendas

Esta es, quizás, la parte que más le pueda costar a un desarrollador pese a ser la más fácil de todas en cuanto a tiempo y complejidad de desarrollo. Ya tenemos hecha nuestra aplicación, ahora tenemos que añadirle toda la parafernalia que la rodea para convertirla en un producto vendible y que cumpla unos mínimos de calidad en cuanto a su aparición en la tienda.

Si bien la aplicación que se ha presentado en sí no tiene imágenes ni otra parafernalia multimedia, lo habitual en cualquier programa es que sí que lo haya, por lo que el programador se ha tenido que liar la manta a la cabeza y hacer sus propios gráficos o bien se ha apoyado en un tercero. Si estamos en una empresa lo normal es que haya algún tipo de diseñado previo y alguien que lo haga, pero si somos completamente amateur en general lo tenemos crudo.

Se ha de ser consciente de una cosa: no vale coger una imagen cualquiera de internet e incluirla en nuestro programa ya que lo más seguro es que ese gráfico tenga derechos de autor, por lo que lo lógico es que o bien compremos alguna librería de imágenes o nos las hagamos nosotros mismos.

Necesitamos preparar una serie de elementos gráficos obligatorios que tenemos que integrar en la aplicación y otros más disponibles a la hora de subirla. Sin contar, claro está, de todos aquellos que han sido necesarios para que el aspecto y funcionalidad de nuestra aplicación sean los correctos.

En el caso que nos ocupa tan solo necesitamos los que son obligatorios para que nos acepten la aplicación, a saber: un icono que será el que aparezca en el listado de aplicaciones, y una serie de tiles que son los que irán en la pantalla principal si así lo quiere el usuario. Necesitaremos una pantalla de splash, que aparece durante el tiempo que tarda a cargarse la aplicación. Eso para incluir en la misma. Luego, cuando subamos a la tienda, tenemos que preparar otras incluyendo una o varias capturas de pantalla.

Lo ideal es prepararlo todo de una tacada para luego no estar perdiendo el tiempo y teniendo que crear esta o aquella imagen que se nos olvidó.

También necesitaremos un texto descriptivo de la aplicación, que aparecerá cuando los clientes la busquen o la encuentren, y finalmente una Política de Privacidad si nuestra aplicación sale a internet. También es recomendable disponer de un correo de servicio técnico.

Una vez tengamos todo eso listo, subiremos nuestra aplicación a la tienda respectiva y a rezar para que no se nos haya pasado ningún punto obligatorio. La espera, salvo que sea rechazada, suele durar cinco días.

Dado que el proceso es completamente diferente para cada una de las dos tiendas, tanto la especificación de los metadatos como la subida, las trataremos de forma independiente.

No obstante, vamos a comenzar con una imagen que va a ser la base de todas las demás. Como estamos ante una aplicación que nos muestra un reloj en diferentes bases numéricas, qué mejor opción que la imagen de un reloj, a la que le hemos añadido las palabras BIN, HEX, OCT y DEC a lo largo de la esfera. La Figura 8 recoge el resultado. Recordemos que en general tanto Windows Phone como Windows 8 se basan mucho en fondos negros y transparencias, por lo que el resultado en un teléfono o tableta es diferente al mostrado aquí.

Figura 8

Dn481340.FF129BC6791223659CD4560549D329EA(es-es,MSDN.10).png

El autor ha utilizado un icono de 256x256 píxeles que tenía disponible en una biblioteca de iconos que compró hace tiempo para otros menesteres, y ha utilizado Icon Workshop para la edición de las mismas, pero el lector puede utilizar cualquier herramienta que tenga a su alcance. El único requisito es que soporte el formato PNG y transparencias.

Metadatos y gráficos para Windows Phone

Dependiendo de para qué versión mínima del sistema operativo estemos desarrollando, necesitaremos más o menos gráficos, aunque no sean imprescindibles. Hacemos notar que cuando se indica transparencia esta es más que recomendable para que nuestra aplicación quede lo más compatible posible con el concepto que Microsoft tiene de una aplicación. La lista válida para cualquier versión es:

  • El icono de la aplicación, PNG con transparencia, 62x62 píxeles. Este icono es el que se muestra en las búsquedas y en la lista de programas.
  • WP 7.5: La imagen del Tile básico, PNG con transparencia, 173x173 píxeles, que se muestra en la pantalla principal.
  • WP 7.8 y superior: La imagen del Tile de tamaño pequeño, 159x159, la del mediano, 336x336 y la del grande, 691x336.
  • El icono de la aplicación en la tienda, PNG con transparencia, 300x300.
  • Imagen de splash, 480x800, PNG sin transparencias o JPG, que es la imagen que se muestra mientras se carga la aplicación.
  • Capturas de pantalla, 480x800 pixeles, PNG sin transparencia. Hasta 7 diferentes.
  • Fondo temático para la tienda, PNG, hasta 1000x1000 píxeles. Opcional pero que hace muy bonito. Lo ideal es que una imagen colocada al lado de la otra genere un patrón recursivo, aunque no es imprescindible.

Básicamente vamos a necesitar eso. El lector puede encontrar los elementos en la carpeta Media\WP del código de ejemplo. En el caso de Windows Phone 7.8 y superior, tan sólo es obligatorio el tile de tamaño medio, pero es recomendable tener los tres. En nuestro caso, como la aplicación se va a ejecutar en la versión 7.5, sólo necesitamos uno.

Tenemos que decir que es posible, mediante reflexión, colocar más tiles y manejarlos por programa, e incluso colocar los de Windows Phone 8 en una aplicación firmada para la versión 7.5, de forma que cuando la aplicación se ejecute en una versión superior disponga de todos ellos, pero la manera de hacerlo escapa al contenido de este artículo.

Una vez que tenemos todos los gráficos, abrimos la rama Properties de nuestro proyecto y hacemos doble clic sobre el fichero WMAppManifest.xml, y se nos abrirá una pantalla similar a la de la Figura 9.

Figura 9

Dn481340.A1A364CA3B9F53BBDFC24E111594D0A2(es-es,MSDN.10).png

Allí debemos cargar cada uno de los gráficos, y editar de forma adecuada los textos que queramos que aparezcan en cada parte de nuestro programa una vez lo haya instalado el usuario en su teléfono.

En la Figura 10 se recogen las Capabilities de nuestro teléfono. Tenemos que marcar todas las que nuestra aplicación use. Por ejemplo, si accediéramos a la cámara de fotos, deberíamos marcar ID_CAP_ISV_CAMERA. En nuestro caso no hacemos uso de nada de eso.

Figura 10

Dn481340.3702771764D0A42EBBF3CA0E0E9934A7(es-es,MSDN.10).png

Finalmente, la pestaña de Packaging contiene otros metadatos que también debemos editar, como la versión y los datos de quién ha hecho la aplicación. No obstante editar estos, también es necesario y recomendable hacer lo mismo en el fichero AssemblyInfo.cs, ya sea editándolo directamente o a través de las propiedades del proyecto, abiertas haciendo clic con el botón derecho del ratón sobre el nombre del mismo. La Figura 11 recoge la pantalla en cuestión, a la que se llega mediante el botón de Assembly Information.

Figura 11

Dn481340.1B61E8FEDD1BF08DEAC78FAD3E2C0B0D(es-es,MSDN.10).png

Una vez hayamos rellenado todos estos elementos, es tiempo de elegir el tipo de construcción Release, reconstruir nuestra aplicación y comprobar que todo funciona perfectamente.

También podemos realizar toda una serie de test antes de enviar nuestra aplicación a la tienda. Éstos están disponibles al hacer clic con el botón derecho del ratón sobre el nombre del proyecto en el Solution Explorer y eligiendo la opción Open Store Test Kit.

Allí debemos de rellenar los detalles de la aplicación, que consiste en cargar la imagen de 300x300 píxeles y al menos una captura de pantalla. Como en la Figura 12. Después pasamos a la pestaña de Automated Tests, que se pueden ver en la Figura 13.

Figura 12

Dn481340.566EE7C4C5F9E28F3B730CE2DE91D2F3(es-es,MSDN.10).png

Figura 13

Dn481340.40D6C368CA381D0F6E2F10A43862A184(es-es,MSDN.10).png

Allí podremos y deberemos ejecutar los test que aparecen ya realizados en la figura anterior. De este modo nos aseguramos de que la subida a la tienda irá sin problemas.

Si tenemos una versión de Visual Studio 2012 anterior al Update 2 y nuestra aplicación tiene a Windows Phone 7.5 como sistema base, no podremos realizar ningún tipo de análisis de rendimiento, que se lanzan desde el botón Start Windows Phone Application Analysis (que se puede ver en la parte inferior de la Figura 13). Pero si tenemos la versión citada o superior, podremos realizar toda una serie de verificaciones de rendimiento y de uso de memoria que puede encontrarnos los cuellos de botella de nuestro programa.

Aquí no vamos a hablar más de ello, porque hacerlo sería crear otro artículo tanto o más largo que este, pero el autor recomienda encarecidamente al lector que se dé una vuelta por esa sección.

No debemos olvidar una descripción de nuestra aplicación, que debemos tener para todos y cada uno de los idiomas en que esté localizada, al menos en teoría. En nuestro caso sólo está en inglés, pero podría ser interesante hacerlo en castellano o en cualquier otro lenguaje. De nuevo esta tarea se escapa del cometido de este artículo.

Subiendo a la tienda de Windows Phone

Ya tenemos todo lo necesario para irnos a la tienda y subir nuestra aplicación. Bueno, realmente no. Necesitamos una cuenta de desarrollador para Windows Phone. El coste es nominal, inferior a los cien dólares USA, y eso si no te has agenciado alguna oferta o tienes una MSDN.

Suponiendo que tengamos la cuenta de desarrollador, nos vamos a http://dev.windowsphone.com y allí entramos en el Dashboard. Una vez introducidas las credenciales, estamos listos para la subida.

Justo a la derecha tenemos la opción de Submit app. Haciendo clic en ella se nos presenta una página web como la de la Figura 14. Debemos seguir los pasos marcados como Required, que son dos.

Figura 14

Dn481340.076DB9F0B466F9B486E6C7DDD22C41E1(es-es,MSDN.10).png

El primer paso consiste en indicar la información sobre la aplicación. Allí deberemos rellenar el alias, que es el nombre interno con el que nos referiremos a ella, la categoría y subcategoría si procede y el precio que sólo podremos poner si tenemos una cuenta de ingresos.

Luego indicamos si nuestra aplicación tendrá la opción de instalar en modo de prueba, que sólo es válido si le hemos puesto un precio y al final del todo, los países en donde se distribuirá. La Figura 15 recoge las opciones que hemos puesto.

Figura 15

Dn481340.99E874304267C36A6C9AD4AB71C3471F(es-es,MSDN.10).png

Si el lector se fija, el nombre interno que le hemos dado es Multibase Clock Beta. ¿Por qué beta? Es muy sencillo, si abrimos el desplegable More Options podremos indicar que nuestra aplicación es una versión Beta y que no saldrá a la tienda una vez aprobada. En el cajetín inferior, se deben incluir todos los correos de las cuentas que tendrán derecho a instalar la aplicación. Ojo, tienen que ser los correos con los que cada usuario se registró en la tienda, por lo que no vale cualquier correo.

Es decir, podemos indicar que nuestra aplicación es privada y exclusiva para la lista indicada, y nadie más podrá instalarla. Una vez realizada la aprobación, se recibe un correo con la URL de la aplicación. Si por accidente esa URL privada se hace pública, tan sólo podrán instalar el programa aquellos que estén en la lista de autorizados, recibiendo una denegación de instalación aquellos que no estén en ella.

Dicha opción nos posibilita ofrecer la aplicación a otra gente para que la verifique y nos encuentre problemas o simplemente la critique. Otro uso es instalársela uno mismo, ya que si no hemos especificado bien los metadatos de la sección anterior, el resultado de la instalación podría no ser el óptimo. En el caso que nos ocupa, la primera versión de la tienda salió con BinHexClockWP como nombre en el listado de aplicaciones. Una subsiguiente subida con los metadatos adecuados ya instalaba como Multibase Clock.

Este proceso de verificación suele tardar unas cuatro o cinco horas, menos si la aplicación es sencilla, será aprobada salvo que tenga errores y problemas flagrantes, ya que el proceso es completamente automático. Por supuesto, que pase la fase beta no quiere decir que vaya a hacerlo en la de producción, cuando es probada por gente.

Sin embargo, si queremos que nuestra aplicación no sea beta, en este desplegable también podemos indicar que no se publique una vez aprobada. De este modo podremos hacer pública nuestra aplicación cuando queramos. También podemos subir el certificado para las notificaciones Push, cosa que a nosotros no nos hace falta. La Figura 16 recoge esta parte.

Figura 16

Dn481340.2A908A169D8F807EDE207DD6CF23ECF9(es-es,MSDN.10).png

Una vez guardada esta parte (botón Save), podemos subir nuestro fichero XAP, que es el siguiente paso. Una vez que lo hemos hecho, la pantalla queda según la Figura 17.

Figura 17

Dn481340.794B317B01103641C9A71AA0B188ED94(es-es,MSDN.10).png

Si movemos esa página hacia abajo, veremos el cajetín para poner el texto que aparecerá en la tienda, y luego hasta cinco palabras clave para cuando la gente busque cosas específicas.

Y más abajo tenemos la zona en donde indicar el icono de 300x300 y las distintas capturas de pantalla.

A la derecha nos encontramos con otro desplegable para poner las URL de nuestra política de privacidad y los términos de la licencia, así como el correo electrónico de soporte. Estos campos sólo son obligatorios en determinadas condiciones, entre las que está el hecho de que la aplicación acceda a datos del usuario.

Una vez indicado todo esto, pulsamos el botón de Save y, si todo ha ido bien, estaremos listos para subir la aplicación en el caso de que no haya habido errores, cosa que se nos indica en la página principal de Submit app.

Existen otras opciones más, pero no son obligatorias y en general tienen que ver o bien con incluir publicidad o bien con la selección de países a la que está destinada, así como las claves para el servicio de mapas de Bing. Ninguna de estas opciones es utilizada por nuestra aplicación, aunque es recomendable echarle un vistazo.

Finalmente hacemos clic en Submit app y a esperar la aprobación o el rechazo de la misma.

Una vez que esté aprobada en la tienda, al acceder al Dashboard podremos ver ciertas estadísticas sobre la misma, como instalaciones o recogida de volcados de pila, así como usuarios activos.

Cuando queramos actualizar, el proceso es básicamente el mismo, pero ahora simplemente tenemos que cambiar el XAP anterior por el nuevo, o cambiar los textos o las capturas. Dependiendo de qué variemos, se lanzará un proceso de verificación completo o simplemente una actualización que estará lista en unas horas. Por ejemplo, si hemos añadido o quitado países el cambio es prácticamente inmediato si no hemos activado por primera vez uno que tenga algún tipo de restricción especial sobre la cual nuestra aplicación no haya sido verificada.

Como vemos, el proceso no es muy complicado, y lo peor de todo es la espera hasta que nos la aprueben o rechacen. El autor ha subido dos aplicaciones y las dos se las aprobaron a la primera. Otra cosa fue la versión de Windows 8.

Metadatos y gráficos para Windows 8

Windows 8 exige mayor cantidad de elementos gráficos obligatorios que Windows Phone aunque básicamente éstos sean del mismo tipo. Nosotros vamos a indicar los mínimos imprescindibles, pero dado que la aplicación puede correr en infinidad de tamaños de pantalla, cada elemento de los que se van a citar suele tener hasta tres tamaños más, que el sistema seleccionará en base a la resolución de ejecución:

  • Logo, 150x150 píxeles, PNG con transparencia, como imagen por defecto en la lista de Tiles.
  • Wide Logo, 310x150 píxeles, PNG con transparencia, para cuando el usuario quiera poner el logotipo ancho.
  • Small Logo, 30x30 píxeles, PNG con transparencia, que se muestra en aquellos lugares en los que haga falta un ícono diminuto, como cuando se muestran todos los tiles en pequeño.
  • Store Logo, 50x50 píxeles, PNG con transparencia, para colocar en la descripción de nuestra aplicación.
  • Splash Screen, 620x300, que es la imagen temporal que se muestra mientras se carga la aplicación. Si bien en Windows Phone 8 es opcional, en Windows 8 no lo es porque los tiempos de carga pueden ser bastante largos.
  • Capturas de pantalla, hasta 8. Se pueden tomar desde el propio emulador, que tiene un botón situado a la derecha para realizar dicha tarea. Debemos tomar al menos una.

Igual que en el caso de Windows Phone, las diferentes imágenes están situadas en la carpeta /Media/W8. Todos estos gráficos debemos colocarlos en la aplicación de forma más o menos similar que en Windows Phone.

En este caso el fichero se llama Package.appmanifest y está en el árbol raíz del proyecto en el Explorador de Soluciones. Haciendo doble clic sobre él se abre algo similar a lo de la Figura 18. Allí es donde tenemos que indicar todos estos elementos gráficos. Como puede observar el lector, si queremos tenerlo todo de forma adecuada, hacen falta muchos.

Figura 18

Dn481340.7197E0FC3D301717685D980E2C9AA474(es-es,MSDN.10).png

De igual forma que en Windows Phone, podemos y debemos seleccionar la pestaña de Capabilities que aparece en la figura anterior y marcar las que nuestra aplicación necesite. En nuestro caso, ninguna ya que no hacemos nada más que mostrar la hora.

La pestaña Declarations sirve para indicar al sistema cuando la aplicación se instale que se soportan ciertas acciones como abrir algún tipo de fichero, buscar o imprimir.

La que sí debemos modificar es la de Packaging, en la que se tienen que indicar varios datos como el nombre del programa que será visible por el usuario y el certificado del publicador en caso de tener varios. La Figura 19 muestra lo que estamos explicando.

Figura 19

Dn481340.5C58643DF13CE21EF9AD7CD72E128356(es-es,MSDN.10).png

Una vez hecho todo esto, debemos editar el fichero AssemblyInfo.cs ya sea directamente o a través de las propiedades del proyecto. Tampoco debemos olvidarnos de generar un build en release y verificar la aplicación así, sobre todo si estamos utilizando algo de código en C++ o C++/CX.

Todos estos pasos están disponibles o bien de forma manual (como lo hemos descrito) o bien desde el menú de Project -> Store. Allí dentro tenemos toda una serie de entradas para ir realizando los pasos de forma adecuada. Entre otras cosas podemos incluso reservar el nombre de nuestra aplicación antes de que esté disponible. Algunas de las opciones del menú nos llevan al navegador o a algún programa externo, pero es una buena cosa tenerlo todo junto.

Ahora nos toca hacer todos los test para que nuestra aplicación se pueda subir a la tienda. En contra de Windows Phone, aquí son automáticos. Desde el menú citado arriba se elige Create App Package, que nos abre un asistente y que será el encargado de crear toda la parafernalia para que se pueda subir. Lo primero que hace el asistente es preguntarnos si queremos crearlo. Al pulsar el botón de siguiente se nos abre una ventana de login para que introduzcamos nuestras credenciales de desarrollador.

Igual que en el caso de Windows Phone, necesitamos una cuenta, que también tiene un precio simbólico.

Tras la verificación de nuestra cuenta, se nos da la opción de elegir un nombre si todavía no lo habíamos reservado o si es la primera vez que construimos el paquete. Después nos indicará el tipo de configuración deseada. Tenemos opciones para indicar la arquitectura de destino, así como la opción de incluir los ficheros de símbolos para poder recibir información más detallada sobre los crashes. También se puede indicar la versión del paquete. Y debemos tener especial cuidado en la parte que nos dice dónde se dejará el paquete generado porque luego podríamos no encontrarlo. La Figura 20 recoge este paso.

Figura 20

Dn481340.9724ECCF655423A3401127D986CB0F88(es-es,MSDN.10).png

Cuando pulsemos Create Visual Studio generará el resultado caso de no haber ningún error en el proceso. Si lo hay se debe solucionar el problema y repetir hasta que todo salga de forma correcta.

Tras finalizar se nos da la opción de lanzar el Kit de Certificación, que realizará toda una serie de pruebas automáticas que comprobarán que nuestra aplicación cumple una serie de normas mínimas. Es más que recomendable ejecutarlo.

Tras aceptar el aviso del UAC si lo tenemos al nivel máximo, comienza el baile. Nuestro ordenador se volverá poco menos que loco lanzando y cerrando la aplicación y realizando tareas de lo más variopinto. Cuando termine así nos lo indicará el asistente, pero mientras lo ideal es dejar que se realice todo de forma automática. La Figura 21 recoge el resultado de nuestro Multibase Clock.

Figura 21

Dn481340.2CD9D8FC539643D312E9FEA11DED880E(es-es,MSDN.10).png

Subiendo a la tienda de Windows 8

Ya hemos realizado todos los pasos necesarios, ahora sólo nos queda subirla. Podemos hacer clic sobre el enlace que nos muestra el asistente o podemos ir directamente al Dashboard de Windows 8, que podemos realizar en español, cosa que no era posible en Windows Phone (por lo menos, en el momento de escribir este artículo). La contrapartida está en que no podemos subir una versión de prueba válida sólo para unos pocos usuarios seleccionados.

Una vez en la web, a la derecha, tenemos una serie de enlaces. El primero en la sección de Panel es Enviar una aplicación. Al hacer clic ahí se nos abre en la parte central un listado en forma de pasos y duración de cada uno de ellos. Debemos recorrerlo ordenadamente, aunque se pueden saltar si en el momento de la edición no sabemos qué poner, pero no podremos enviar la aplicación a certificación sin haberlas completado.

Si bien en la tienda de Windows Phone no es necesario tener un medio de pago en el caso de que nuestra aplicación sea gratuita, aquí es obligatorio, por lo que si no hemos metido al menos una tarjeta de crédito válida no podremos crear ninguna aplicación.

Algunos pasos son tremendamente sencillos, como el del Nombre de la aplicación. Sólo debemos indicarlo en él y si está libre, se nos aceptará. En Detalles de la venta tenemos que poner el precio de la aplicación (gratuito en nuestro caso), en qué países estará disponible y si vamos a publicarla de forma inmediata una vez aprobada o si vamos a poner una fecha en concreto. También la categoría y subcategoría, y los requisitos de hardware. Aquí se ha puesto Productividad y disponible para todos los sistemas.

Funciones Avanzadas no nos sirve a nosotros, pero se utiliza para indicar variaciones de precios y duración de las ofertas de los mismos. La Clasificación por edades y certificados de clasificación se utiliza para poner edades mínimas por países y subir ciertos certificados que son obligatorios en algunos de ellos. La explicación de todo esto se escapa del cometido de este artículo. Nosotros marcamos la edad mínima, 3 años.

En Criptografía declaramos que la aplicación no usa ningún sistema criptográfico y que cumple las restricciones que existen en algunos países sobre el tema.

La sección de Paquetes es la más interesante de todas, ya que es aquí donde tenemos que subir el que nos ha generado el asistente (de ahí que tengamos que recordar dónde lo guardó). Con arrastrar y soltar el o los archivos con extensión appxupload adecuados sobre el recuadro es suficiente.

La Figura 22 recoge el estado actual. Debemos hacer notar que o bien subimos un paquete de tipo AnyCPU o bien uno de cada arquitectura soportada, pero no debemos subirlos ambos. Si nuestro programa no contiene código nativo de ningún tipo y todo es C# ó VB.NET, lo más directo es subir el primero.

Figura 22

Dn481340.F9709878DCADC4A3F9A6BC8A837D0283(es-es,MSDN.10).png


Ya sólo nos quedan dos pasos para terminar. Uno de ellos es enormemente importante, ya que es la descripción que va a aparecer en la tienda, las capturas y otros datos interesantes para promocionar nuestra aplicación.

Tras la descripción se pueden poner una serie de las características más importantes que tiene la aplicación. Luego tenemos que describir todas y cada una de las capturas subidas. Finalmente, es recomendable indicar una serie de palabras clave de búsqueda y no debemos olvidarnos de un correo de soporte técnico (que es obligatorio) y una URL que describa la política de privacidad, que aunque no diga que es obligatoria, rechazarán la aplicación si no la lleva. Es decir, o bien se indica una en la descripción de la aplicación o bien se pone aquí una URL. En la Figura 23 se puede ver parte de este proceso.

Figura 23

Dn481340.CBE5B43E9F034FB0084FE0577E6A69B3(es-es,MSDN.10).png

El último paso consiste en poner una serie de indicaciones a los evaluadores para que puedan entender tu aplicación. No es obligatorio, pero sí recomendable, sobre todo en el caso de que nuestra aplicación tenga alguna característica rara.

Dado que estamos ante un programa muy básico, éste fue rechazado primero por motivos técnicos (se olvidó la política de privacidad) y luego porque “no aportaba nada útil al ecosistema Windows”, por lo que el autor añadió una nota al evaluador para que la aprobara ya que el destino de la aplicación no era ser útil para los usuarios de Windows, si no para los programadores.

Si se han introducido todos los datos de forma correcta, al final veremos que se han habilitado los botones para revisar toda la información o para enviar todo el tema a certificación. Se recomienda revisarlo bien todo antes, y si ya estamos seguros, enviamos. Tras unos instantes, se comienzan una serie de test automáticos hasta que se llega al Cumplimiento del contenido, que es la verificación manual y es donde se suelen rechazar. La Figura 24 muestra el citado estado.

Figura 24

Dn481340.D9FB23E3E9FEE0CA8AD82F7F1BCE634F(es-es,MSDN.10).png

Ya sólo nos queda esperar el correo de aprobación o de rechazo. No hay nada malo en que la rehúsen, y normalmente suelen indicarte el motivo y los pasos que generaron el fallo. Se corrige y se actualiza sin mayor problema y vuelve a comenzar el ciclo de revisión.

Conclusión

Y con esto hemos terminado. Esperamos que el lector disfrute leyendo y mirando el código tanto o más de lo que el autor ha disfrutado creándolo.

Mostrar: