Etiqueta: C#Sharp

Temas relacionados con C#

  • Actualizar el icono (tile) de una aplicación de Windows Store

    Pues eso… de lo que hoy te voy a hablar es de cómo actualizar el icono de nuestra aplicación para Windows Store. Y cuando digo actualizar me refiero a mostrar algún mensaje o alguna imagen o ambas dos cosas en el icono que está en la página de inicio de Windows 8. En concreto se trata de mostrar la hora actual y actualizarla cada minuto, por tanto el título podría haber sido: Actualizar el icono (tile) de una aplicación de Windows Store cada minuto.

     

    Para que entres en situación:

    En la pantalla de inicio de Windows hay algunos iconos (o tiles) que se actualizan, por ejemplo, el correo, el tiempo, los contactos, etc. Ver la figura 1.
    Esas actualizaciones pueden venir de la nube de un sitio web o de forma local (porque tú quieras actualizar el icono).

     
    Foto 1. Los iconos (tiles) de Windows 8 permiten mostrar notificaciones

     

    De forma predeterminada las notificaciones se pueden mostrar en el icono (o tile) de la aplicación, ya sea en modo alargado o cuadrado: el icono puede ser cuadrado (smaller) o alargado (larger), pero también se puede indicar que no haya "animaciones" en el icono (nosotros como usuarios podemos decidirlo). Para ello sólo hay que pulsar con el botón secundario del ratón en el icono y del menú, u opciones mostradas en la parte inferior, podemos indicar si no queremos esa animación (Turn live tile on/off) tal como vemos en la figura 2.

     

    notificar_tile_02
    Figura 2. El usuario puede activar/desactivar la notificación

     

    Nota:
    Sigue este enlace si quieres saber cómo capturar la pantalla en Windows 8 (tanto de la pantalla de inicio como del escritorio)

     

    ¿Cómo podemos hacer esas notificaciones?

    Sigue leyendo y sabrás cómo hacer algunas de esas notificaciones, aunque aquí te voy a explicar sólo la de notificar con texto, tanto para la imagen grande como la pequeña, si quieres saber cómo usar más tipos de notificaciones tendrás que leerte (entre otras cosas) la ayuda de la enumeración TileTemplateType o mirar en las páginas del centro de desarrollo de Microsoft para saber cómo desarrollar aplicaciones para la tienda de Windows.
    En mi caso particular, para este artículo me he basado en el inicio rápido (quickstart) de: enviar una actualización de icono (aplicaciones de la Tienda Windows con C#/VB/C++ y XAML) (Windows), aunque también estuve probando antes con la biblioteca NotificationsExtensions con idea de hacerlo más fácil y sin tanto manejo de código XML, pero no me funcionó… o lo mismo no la utilicé correctamente. Incluso estuve tentado de usar un temporizador en background, particularmente usando el código de Dave Smits: Tile Update every minute, que seguramente usaré en esta misma aplicación ya que según parece el temporizador se para cuando Windows pone en modo suspensión la aplicación (aún no lo he comprobado, pero me da la impresión de que ocurre algo de eso… ya te diré).

     

    Vamos a lo que vamos

    Lo  primero que he hecho es agregar dos importaciones de espacios de nombres al código de MainPage, el primero para usar las notificaciones y el segundo para usar el Xml.Dom:

    Imports Windows.UI.Notifications
    Imports Windows.Data.Xml.Dom
    
    using Windows.UI.Notifications;
    using Windows.Data.Xml.Dom;

    También necesito crear otro temporizador, ya que el que teníamos en nuestra aplicación (ver los enlaces al final de la página) se puede detener si se pulsa en cualquiera de los botones "stop" de la aplicación.

    Este segundo temporizador estará funcionando y haciendo comprobaciones cada 2 segundos (más o menos) e internamente comprobará si ha cambiado de minuto y en ese caso llamará al método asignarTile() para actualizar los textos del icono. Esos textos será la hora y minutos y además la fecha actual, si se muestra en el icono grande se mostrará en formato largo y si se muestra en el icono pequeño, la fecha estará reducida (porque si no, no se ve, así que).

    Dicho esto, al principio de la clase definimos el temporizador y un par de variables para indicar el intervalo y llevar la cuenta del último minuto mostrado.

    Visual Basic:

    Private timerNotif As DispatcherTimer
    
    ' Intervalo para realizar las notificaciones
    Private intervaloNotif As Integer = intervalo * 2
    ' para guardar el minuto en que se notificó
    Private minutoNotif As Integer = -1
    

     

    C#

    private DispatcherTimer timerNotif;
    
    // Intervalo para realizar las notificaciones
    // (en C# no se puede usar el valor de intervalo en la declaración)
    private int intervaloNotif = 900 * 2;
    // para guardar el minuto en que se notificó
    private int minutoNotif = -1;
    
    

     

    Si te preguntas porqué no he usado un intervalo de un minuto (o algo menos), decirte que el problema es que ese minuto se cuenta desde que activamos el temporizador, por tanto si esa activación del temporizador ocurre, digamos, en el segundo 40, hasta que no llegue el segundo cuarenta del próximo minuto no se actualizaría el contenido, así que… yo he usado 1800 milisegundos, es decir, dos veces el intervalo usado en el otro temporizador, llámame romántico si quieres, jeje.

    Fíjate también que en el código de C# he usado el valor 900 directamente en lugar del campo intervalo para asignar el valor de intervaloNotif, esto es así porque el compilador me decía que no se puede usar un campo (field) intervalo porque no es estático… o algo así…

    También lo podría haber declarado intervalo con el modificador "const" y así sí que podría usarlo como valor a asignar a intervaloNotif, hazlo como tu veas mejor.

     

    En el constructor tenemos que hacer las definiciones y demás cosillas para crear el nuevo temporizador. Yo lo he puesto después del código que ya teníamos (justo antes de acabar el método).

    Visual Basic:

    ' Crear el temporizador para las notificaciones
    timerNotif = New DispatcherTimer()
    AddHandler timerNotif.Tick, AddressOf timerNotif_Tick
    timerNotif.Interval = New TimeSpan(0, 0, 0, 0, intervaloNotif)
    

     

    C#:

    // Crear el temporizador para las notificaciones
    timerNotif = new DispatcherTimer();
    timerNotif.Tick += timerNotif_Tick;
    timerNotif.Interval = new TimeSpan(0, 0, 0, 0, intervaloNotif);
    

     

    Ahora veamos el código del método de evento para este nuevo temporizador.

    Aquí lo que hacemos es detener el temporizador, comprobar si tenemos que actualizar el icono (tile) y volver a poner en marcha el temporizador.

    Para lo del minuto de lapso, lo único que hago es comprobar si el valor de minutoNotif es distinto del minuto actual y en ese caso mostrar el mensaje actualizado. Te digo esto por si quieres hacer algo cada x segundos, en se caso, tendrías que utilizar alguna que otra comparación más "sofisticada".

    VB:

    Private Sub timerNotif_Tick(sender As Object, e As Object)
        timerNotif.Stop()
    
        ' actualizar el tile cada minuto
        If minutoNotif <> DateTime.Now.Minute Then
            minutoNotif = DateTime.Now.Minute
            asignarTile()
        End If
    
        timerNotif.Start()
    End Sub
    

     

    C#:

    private void timerNotif_Tick(object sender, object e)
    {
        timerNotif.Stop();
    
        // actualizar el tile cada minuto
        if (minutoNotif != DateTime.Now.Minute)
        {
            minutoNotif = DateTime.Now.Minute;
            asignarTile();
        }
    
        timerNotif.Start();
    }
    

     

    Como podrás imaginar el trabajo de notificación se realiza en el método asignarTile, pero antes de ver el código de ese método, veamos qué más tenemos que hacer… por ejemplo habilitar el tema de las notificaciones mediante colas, esto en realidad no haría falta, pero he comprobado que si no se usa esa llamada al método EnableNotificationQueue del método compartido CreateTileUpdaterForApplication de la clase TileUpdaterManager y tampoco se asigna un valor a la propiedad Tag del objeto TileNotification usado para hacer las notificaciones esas notificaciones no las hace como uno espera… al menos a mí me ha pasado que me mostraba el texto que inicialmente se usó al iniciar la aplicación y después el que se iba actualizando… así que…

    Dicho esto, agrega el siguiente código al principio del método OnNavigatedTo:

     

    Nota del 8 de diciembre:

    La llamada al método EnableNotificacionQueue(True) lo que en realidad hace es permitir que exista una cola de notificaciones (con un máximo de 5) y esas notificaciones se van alternando de forma automática. Por tanto, para la intención de nuestra aplicación no es precisamente lo más recomendable. Así que, comenta esa línea de código.

     

    VB:

    ' Permitir las notificaciones
    TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(True)
    
    minutoNotif = DateTime.Now.Minute
    timerNotif.Start()
    asignarTile()
    

     

    C#:

    // Permitir las notificaciones
    TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);
    
    minutoNotif = DateTime.Now.Minute;
    timerNotif.Start();
    asignarTile();
    

     

    Y ya sólo nos queda el código del método asignarTile, que (tal como su nombre indica) es el que se encarga de asignar el texto (o lo que indiquemos) a mostrar en el icono de la aplicación.

    Como ya te comenté, el icono (tile) puede tener dos tamaños, uno cuadrado y más pequeño (la imagen a usar en el icono es de 150 x150 píxeles) y otro alargado (la imagen es de 310 x 150), en este ejemplo utilizaré la notificación en esos dos tamaños, pero sólo con texto.

    Como veremos, cuando definimos la notificación para el icono ancho hay que definir también la del icono pequeño… aquí veremos cómo hacerlo.

    Y si lo tuyo son las imágenes en vez de los textos, también veremos cómo asignar imágenes, concretamente las de los iconos pequeños y grandes que están definidos en la aplicación (en la carpeta Assets), ya que haremos que se restauren las imágenes predeterminadas, si no hacemos esto, comprobarás que se queda el último texto que asignes… y en nuestro caso (un reloj que se actualiza cada minuto) no queda muy bien que digamos…

    Y ya sin más dilación (o rollo por mi parte) veamos el código del método asignarTile.

    VB:

    ''' <summary>
    ''' Asignar el texto a mostrar en el tile (icono) de la aplicación.
    ''' En el parámetro se indicará el texto a mostrar (la hora actual)
    ''' </summary>
    Private Sub asignarTile()
        Dim hora As String = DateTime.Now.ToString("HH:mm")
    
        ' La plantilla para el tamaño ancho: TileWideText01
        ' Esta plantilla soporta una cabecera y cuatro líneas de texto
        Dim tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideText01)
    
        Dim tileTextAttributes = tileXml.GetElementsByTagName("text")
        tileTextAttributes(0).InnerText = hora
        tileTextAttributes(1).InnerText = DateTime.Now.ToString("dddd, dd MMMM yyyy")
    
        ' La plantilla para el tamaño pequeño: TileSquareText01
        ' Esta plantilla es como la ancha
        Dim squareTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquareText01)
        Dim squareTileTextAttributes = squareTileXml.GetElementsByTagName("text")
        squareTileTextAttributes(0).InnerText = hora
        squareTileTextAttributes(1).InnerText = DateTime.Now.ToString("dd/MMM/yyyy")
    
        ' La plantilla del icono pequeño la agregamos a la del icono ancho
        ' y así el propio Windows mostrará el texto que corresponda según el tamaño del icono
        Dim node = tileXml.ImportNode(squareTileXml.GetElementsByTagName("binding").Item(0), True)
        tileXml.GetElementsByTagName("visual").Item(0).AppendChild(node)
    
        Dim tileNotification = New TileNotification(tileXml)
    
        tileNotification.Tag = "ClockW8_eGi"
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification)
    
    End Sub
    

     

    C#:

    /// <summary>
    /// Asignar el texto a mostrar en el tile (icono) de la aplicación.
    /// En el parámetro se indicará el texto a mostrar (la hora actual)
    /// </summary>
    private void asignarTile()
    {
        string hora = DateTime.Now.ToString("HH:mm");
    
        // La plantilla para el tamaño ancho: TileWideText01
        // Esta plantilla soporta una cabecera y cuatro líneas de texto
        var tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideText01);
    
        var tileTextAttributes = tileXml.GetElementsByTagName("text");
        tileTextAttributes[0].InnerText = hora;
        tileTextAttributes[1].InnerText = DateTime.Now.ToString("dddd, dd MMMM yyyy");
    
        // La plantilla para el tamaño pequeño: TileSquareText01
        // Esta plantilla es como la ancha
        var squareTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquareText01);
        var squareTileTextAttributes = squareTileXml.GetElementsByTagName("text");
        squareTileTextAttributes[0].InnerText = hora;
        squareTileTextAttributes[1].InnerText = DateTime.Now.ToString("dd/MMM/yyyy");
    
        // La plantilla del icono pequeño la agregamos a la del icono ancho
        // y así el propio Windows mostrará el texto que corresponda según el tamaño del icono
        var node = tileXml.ImportNode(squareTileXml.GetElementsByTagName("binding")[0], true);
        tileXml.GetElementsByTagName("visual")[0].AppendChild(node);
    
        var tileNotification = new TileNotification(tileXml);
    
        tileNotification.Tag = "ClockW8_eGi";
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification);
    }
    

     

    A ver que te explico de este código que ya no te haya explicado… hmmm… no se me ocurre nada…

    Bueno, sí, aclararte unas cosas:

    tileXml tiene la plantilla del icono ancho, asignamos el texto de la cabecera con la hora y en uno de los textos secundarios indicamos la fecha completa.

    squareTileXml tiene la plantilla del icono pequeño y también asignamos la fecha y la hora, pero como el icono es más "chico" la fecha la abreviamos.

    node lo usamos para contener el icono pequeño y agregarlo al grande.

    tileNotification es el objeto que tenemos que agregar a las notificaciones y como vemos en el constructor, lo creamos a partir de un objeto XmlDocument, concretamente el de la variable tileXml.

    Y finalmente llamamos al método compartido CreateTileUpdaterForApplication de la clase TileUpdateManager para actualizar las notificaciones.

    Si ejecutas el código tal como lo tenemos ahora, comprobarás que mientras el reloj esté funcionando, cada minuto se actualizará la hora en el icono mostrado en la página de inicio de Windows 8 (en la figura 1 lo puedes ver en la parte superior derecha).

    De todas formas, para que comprendas lo que quiero hacer, aquí tienes dos imágenes en las que se muestra el icono mientras se está ejecutando la aplicación (figura 3) con el texto a mostrar y cuando la aplicación está detenida (figura 4) con la imagen que he indicado para esta aplicación.

     

    notificar_tile_03

    Figura 3. EL icono mostrando la hora y fecha actual

    (actualizada durante la ejecución de la aplicación)

     

    notificar_tile_04

    Figura 4. La imagen "normal" de la aplicación.

     

    Como te decía antes, el problema es que al terminar la ejecución de la aplicación el icono mostrará el último valor que asignó (tanto al icono grande como al pequeño) mientras estaba en funcionamiento.

     

    Nota del 8 de diciembre:

    Esto que te comento a continuación no soluciona el problema.

    Dejo las explicaciones y el código, pero no sirve para lo que se busca, que no es otra cosa que dejar el logo original en el icono (o tile).

    Además de que al ponerlo en el método de evento OnSuspending resulta que cada vez que se suspendía la aplicación (el Windows 8 suspende las aplicaciones cuando "cree" que ya no están haciendo algo interesante) se mostraban los logos en el icono y no se actualizaba con la fecha y hora.

    Esto se puede arreglar de otra forma (anoche encontré la forma de hacer todo esto de las notificaciones sin siquiera tener un temporizador e incluso que se sigan mostrando cuando la aplicación termine.

    La forma de hacer esas notificaciones las publicaré en otro artículo, pero si quieres ir experimentando, decirte que es usando la clase ScheduledTileNotification y agregándola con el método AddToSchedule de TileUpdateManager.CreateTileUpdaterForApplication().

    El problema es que yo iba probando cosas, pero según parece cuando se "distribuyen" (deploy) las aplicaciones de Visual Studio 2012 en el propio equipo con Windows 8 no se hace correctamente, o al menos a mí me ha dado la impresión de que la aplicación seguía actuando de forma "distinta" a como yo tenía programado (de programar: escribir código).

    En fin…

    Nota del 10 de diciembre:

    Aquí tienes el código con un ejemplo que actualiza cada x tiempo el contenido del tile y lo hace hasta cuando tu le digas, incluso si la aplicación está finalizada.

    Actualizar cada minuto el icono (tile) de una aplicación de Windows Store hasta la hora indicada

     

    Ahora lo que hay que hacer es asignar a los iconos las imágenes de la aplicación, concretamente las dos usadas en el icono pequeño y en el grande.

    Esas imágenes las puedes ver en la carpeta Assets y el nombre predeterminado que tienen (tu se lo puedes cambiar, siempre que también se lo indiques en el fichero Package.appmanifest) son: WideLogo.png para el icono grande y Logo.png para el icono pequeño.

    Asignaremos estas imágenes cuando finalice la aplicación… sí, piensa dónde pondrías ese código y veremos si es así… o no…

    Veamos el código para asignar las imágenes predeterminadas de los dos iconos (tiles) que estamos modificando. Ese método lo he llamado restaurarTiles.

     

    VB:

    Private Sub restaurarTiles()
        Dim tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideImage)
    
        Dim tileImageAttributes = tileXml.GetElementsByTagName("image")
        TryCast(tileImageAttributes(0), XmlElement).SetAttribute("src", "ms-appx:///Assets/WideLogo.png")
        TryCast(tileImageAttributes(0), XmlElement).SetAttribute("alt", "WideLogo")
    
        Dim squareTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquareImage)
        Dim squareTileTextAttributes = squareTileXml.GetElementsByTagName("image")
        TryCast(squareTileTextAttributes(0), XmlElement).SetAttribute("src", "ms-appx:///Assets/Logo.png")
        TryCast(squareTileTextAttributes(0), XmlElement).SetAttribute("alt", "Logo")
    
        Dim node = tileXml.ImportNode(squareTileXml.GetElementsByTagName("binding").Item(0), True)
        tileXml.GetElementsByTagName("visual").Item(0).AppendChild(node)
    
        Dim tileNotification = New TileNotification(tileXml)
    
        tileNotification.Tag = "ClockW8clr_eGi"
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification)
    End Sub
    

     

    C#:

    private void restaurarTiles()
    {
        var tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideImage);
    
        var tileImageAttributes = tileXml.GetElementsByTagName("image");
        ((XmlElement)tileImageAttributes[0]).SetAttribute("src", "ms-appx:///Assets/WideLogo.png");
        ((XmlElement)tileImageAttributes[0]).SetAttribute("alt", "WideLogo");
    
        var squareTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquareImage);
        var squareTileTextAttributes = squareTileXml.GetElementsByTagName("image");
        ((XmlElement)squareTileTextAttributes[0]).SetAttribute("src", "ms-appx:///Assets/Logo.png");
        ((XmlElement)squareTileTextAttributes[0]).SetAttribute("alt", "Logo");
    
        var node = tileXml.ImportNode(squareTileXml.GetElementsByTagName("binding")[0], true);
        tileXml.GetElementsByTagName("visual")[0].AppendChild(node);
    
        var tileNotification = new TileNotification(tileXml);
    
        tileNotification.Tag = "ClockW8clr_eGi";
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification);
    }
    

     

    ¿Desde dónde llamar al método restaurarTiles?

    Como te habrás imaginado el sitio desde el que debemos llamar a ese método no es del método de evento equivalente al Form_Unload / Form_Closing de las aplicaciones de escritorio, que en la aplicación de Windows Store "supuestamente" es el método MainPage_Unloaded, pero por lo que yo he probado, a ese método de evento (de MainPage) no llega nunca… o al menos ahí no llega a pararse si pongo un breakpoint… así que… me puse a mirar en los distintos eventos que produce la página y no vi ninguno que me convenciera. Y cuando me pongo a buscar sobre el "life cicle" (ciclo de vida) de una aplicación de Windows Store sólo me he encontrado con que una aplicación puede estar en ejecución, en modo suspendido o no está en ejecución.

    También me he encontrado con la información de que si una aplicación el usuario la cierra (pulsando Alt+F4 o mediante la opción de cierre) ésta se suspende durante 10 segundos y después finaliza.

    Si quieres saber más sobre esto último puedes ver lo que dicen en este enlace:

    Administrar el ciclo de vida y el estado de la aplicación (aplicaciones de la Tienda Windows.

     

    La cuestión es que yo he puesto la llamada a ese método en el evento OnSuspending de la clase App, es decir en el código del fichero App.xaml.

    Después de la llamada a restaurarTiles hago una llamada al método Clear de CreateTileUpdaterForApplication para que el tile (o icono) se quede "quietecico" y no siga haciendo cambios entre las últimas notificaciones que ha tenido la aplicación.

    Y este es el código:

     

    VB:

    Private Sub OnSuspending(sender As Object, e As SuspendingEventArgs) Handles Me.Suspending
        Dim deferral As SuspendingDeferral = e.SuspendingOperation.GetDeferral()
        ' TODO: Save application state and stop any background activity
        deferral.Complete()
    
        restaurarTiles()
        ' Esto es necesario para que no siga actualizándose "a su bola"
        TileUpdateManager.CreateTileUpdaterForApplication().Clear()
    End Sub
    

     

    C#:

    private void OnSuspending(object sender, SuspendingEventArgs e)
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        //TODO: Save application state and stop any background activity
        deferral.Complete();
    
        restaurarTiles();
        // Esto es necesario para que no siga actualizándose "a su bola"
        TileUpdateManager.CreateTileUpdaterForApplication().Clear();
    }
    

     

    Nota del 8 de diciembre:

    Por favor lee los comentarios que he puesto hoy sábado sobre algunas cosas que en realidad no funcionan como (al menos yo) esperaba.

     

    Nota del 10 de diciembre:

    Mira este otro artículo y lo mismo ves que sí funciona esto:

    Actualizar cada minuto el icono (tile) de una aplicación de Windows Store hasta la hora indicada

     

    Y ya está… bastante por hoy ¿no?

    Pues… casi… pero lo que te voy a contar lo haré en otro "capítulo" de esta novela por entregas… y será para adaptar nuestro reloj a la ventana "snapped", es decir, cuando el usuario arrastra la ventana principal a uno de los laterales de la pantalla…

     

    Nos vemos.

    Guillermo

     

    Enlaces a los otros artículos de este "paso a paso":

    1. Usar un temporizador en las aplicaciones de Windows store (Visual Basic)
    2. Usar un temporizador en las aplicaciones de Windows store (C#)
    3. Pon una AppBar en tu aplicación de Windows Store
    4. Acceder a los recursos de una aplicación de Windows Store desde código (vb, c#, xaml)
    5. Actualizar el icono (tile) de una aplicación de Windows Store (cada minuto)
    6. Saber cuando la aplicación está en modo Snapped y actuar en consecuencia

    —

    Actualizaciones:

    Resulta que no todo lo dicho aquí es "operativo" y por tanto, hay que rectificar…

    El 7 de diciembre de 2012 a las 00:20 publiqué el original.

    El 8 de diciembre a las 19:50 publiqué las rectificaciones / actualizaciones.

    El 10 de diciembre a las 07:06 pongo el enlace al nuevo artículo con un ejemplo "operativo" y sin usar timer.

    …

  • Acceder a los recursos de una aplicación de Windows Store desde código (vb, c#, xaml)

    Pues eso… seguimos con la aplicación Reloj para Windows 8, en esta ocasión vamos a añadir un par de cadenas a los recursos de la aplicación y veremos cómo acceder a esos recursos tanto desde código de Visual Basic y C# como desde el código de XAML.

    El recurso es para usarlo como cadena de texto en varias partes de la aplicación, por tanto lo definiremos a nivel de aplicación, pero más que nada porque es más fácil de recuperar (o al menos a mí me ha parecido más fácil).

    De todas formas, tal como veremos en el código XAML de la página principal, el uso desde el código XAML no afecta dónde esté definido, pero ese sitio si que lo tendremos que tener en cuenta a la hora de acceder a dichos recursos desde el código de VB o C#.

     

    Recursos 00
    Figura 1. El reloj en funcionamiento

     

    Te cuento que a la hora de buscar cómo hacer esto me encontré con ejemplos que utilizan FindResource, entre otros sitios, los que vienen en el libro Windows Presentation Foundation Unleashed (que tengo desde febrero de 2007 y que no usaba desde hace unos cuantos años, pero que he tenido que retomar para consultar las posibles dudas que me vayan surgiendo en mi "pelea" con el código XAML que hay que usar para las aplicaciones de Windows Store).

    En ese mismo libro indicaba que se podían acceder indicando el nombre de la clase (página XAML) en la que están definidos los recursos, pero que, tal como te he dicho, a mi no me ha funcionado. Que a lo mejor funciona, que no digo yo que no, pero yo no he sido capaz de utilizarlos como yo quería, así que…

    Bueno, vamos al tema…

    Lo primero que tenemos que hacer es crear los recursos como cadenas, en mi caso, estoy usando dos cadenas, una para el título de una especie de MessageBox (especie, ya que no son avisos como los que estamos acostumbrados en las aplicaciones de escritorio) y el otro para usarlo como texto de ese aviso y como Tooltip del botón "Acerca de…"

    Dichas cadenas las defino de esta forma:

    <x:String x:Key="ClockTitle_en">Clock for Windows 8 app Store</x:String>
    <x:String x:Key="AboutClock_en">A simple clock for Windows 8 app Store by elGuille.info</x:String>
    

     

    Yo he definido estas dos cadenas en el fichero App.xaml, es decir, en la "aplicación".

    Si abrimos el fichero App.xaml veremos que justo después de la cabecera tenemos un elemento llamado Application.Resources y dentro de ese hay otro elemento llamado ResourceDictionary que es el que contiene la definición de ResourceDictionary.MergedDictionaries que es donde está indicado el fichero de los estilos que vimos en el artículo anterior (StandardStules.xaml).

    Pues bien, esas dos definiciones de las cadenas las pondremos justo después del inicio del elemento ResourceDictionary, de forma que nos quede de esta forma:

    <Application.Resources>
        <ResourceDictionary>
            <x:String x:Key="ClockTitle_en">Clock for Windows 8 app Store</x:String>
            <x:String x:Key="AboutClock_en">A simple clock for Windows 8 app Store by elGuille.info</x:String>
    
            <ResourceDictionary.MergedDictionaries>
    
                <!-- 
                    Styles that define common aspects of the platform look and feel
                    Required by Visual Studio project and item templates
                 -->
                <ResourceDictionary Source="Common/StandardStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
    
        </ResourceDictionary>
    </Application.Resources>
    

     

    Ahora vamos a modificar MainPage.xaml para asignar el Tooltip al botón buttonClock. Este será el código a usar para definir el botón que está en el segundo panel:

    <Button x:Name="buttonClock" Click="button_Clock" 
            Style="{StaticResource AboutClockAppBarButtonStyle}"
            ToolTipService.ToolTip="{StaticResource AboutClock_en}" />
    

     

    Como puedes comprobar, la forma de usar ese recurso desde XAML es igual que el resto de recursos definidos en nuestro proyecto.

    Pero al usarlo desde el código tendremos que indicar "dónde" está (obvio por otro lado).

    Así que, vamos a modificar el código, particularmente el evento Click del botón buttonClock.

    Lo que haremos es mostrar una especie de "popup" que viene a ser como el MessageBox de las aplicaciones de escritorio, pero al estilo de Windows 8.

    Para mostrar ese mensaje usaremos la clase MessageDialog que está definida en Windows.UI.Popups y que por medio del método ShowAsync mostraremos el mensaje.

    ¿Te has fijado en el nombre del método?

    Acaba con la palabra Async… lo cual significa que es un método asíncrono… y si queremos usarlo de esa forma (asíncronamente) tendremos que hacer la llamada desde un método asíncrono y por tanto usando Await (los que han visto mi presentación en la gira que hice el mes pasado por Perú sabrán de qué hablo, y tú también, claro, porque ya sabes de qué va esto de Async… ¡ejem! ¡ojú que carraspera tengo!)

    Bueno, dejemos las carrasperas y vamos a lo que vamos.

    Para acceder a la cadena del recurso ClockTitle_en y asignarlo al título del diálogo haremos algo así:

    En C#:

    dlg.Title = App.Current.Resources["ClockTitle_en"].ToString();

     

    En Visual Basic:

    dlg.Title = App.Current.Resources("ClockTitle_en").ToString()
    

     

    Es decir, accedemos a la colección Resources de la aplicación y le indicamos el nombre del recurso al que queremos acceder. Como es una cadena, usamos el ToString para convertirlo adecuadamente.

     

    Si en lugar definirlo en el fichero App.xaml queremos definirlo en la propia página en la que tenemos el código (y el diseño de la aplicación), es decir en MainPage.xaml, podemos hacerlo de esta forma:

    dlg.Title = Me.Resources("ClockTitle_en").ToString()
    

     

    dlg.Title = this.Resources["ClockTitle_en"].ToString();

     

    En ese caso, la definición de las cadenas las tendremos que poner justo después de la definición de la cabecera de la página y encerrarlas en el elemento Page.Resources tal como podemos ver a continuación (aquí sólo he puesto una de las dos cadenas):

    <Page.Resources>
        <x:String x:Key="ClockTitle_en">Clock for Windows 8 app Store</x:String>
    </Page.Resources>
    

     

    Por supuesto, también podemos definirlo en el fichero StandardStyles.xaml, y la forma de usarlo desde el código (o desde XAML) será de la misma forma que definiéndolo en el fichero App.xaml.

    De hacerlo ahí, tendrás que definir las cadenas justo después del "ResourceDictionary" que hay en la parte superior, por tanto lo tendrías que añadir a partir del cierre del elemento, concretamente después de esta línea:

    </ResourceDictionary.ThemeDictionaries>.

     

    Bien, dicho todo esto, veamos el código completo del evento Click del botón buttonClock y una captura de la pantalla al mostrar el mensaje.

    Visual Basic:

    Private Async Sub button_Clock(sender As Object, e As RoutedEventArgs)
        ' Mostrar la imagen del SplashScreen o el mismo texto del Tooltip...
        Dim dlg = New Windows.UI.Popups.MessageDialog(
    App.Current.Resources("AboutClock_en").ToString()) dlg.Title = App.Current.Resources("ClockTitle_en").ToString() Await dlg.ShowAsync()

     

    C#:

    private async void button_Clock(object sender, RoutedEventArgs e)
    {
        // Mostrar la imagen del SplashScreen o el mismo texto del Tooltip...
        var dlg = new Windows.UI.Popups.MessageDialog(
    App.Current.Resources["AboutClock_en"].ToString()); dlg.Title = App.Current.Resources["ClockTitle_en"].ToString(); await dlg.ShowAsync();

     

    Como la llamada al método ShowAsync queremos que sea asíncrona, tenemos que definir el método como asíncrono (con el modificador Async / async) y para que dicho método "realmente" sea asíncrono, tenemos que realizar la llamada con Await / await.

     

    Al pasar el ratón por el botón de la derecha (el que estamos modificando) se mostrará el ToolTip tal como vemos en la figura 2, y al pulsar en el botón se mostrará el mensaje que vemos en la figura 3.

    Recursos 02

    Figura 2. El mensaje (Tooltip) al pasar el cursor del ratón por el icono

     

    Recursos 03

    Figura 3. El mensaje mostrado al pulsar en el botón de la derecha

     

     

    Y esto es todo por ahora… espero que te sea de utilidad… 😉

     

    Nos vemos.

    Guillermo

     

    Temas relacionados:

    1. Usar un temporizador en las aplicaciones de Windows store (Visual Basic)
    2. Usar un temporizador en las aplicaciones de Windows store (C#)
    3. Pon una AppBar en tu aplicación de Windows Store
    4. Acceder a los recursos de una aplicación de Windows Store desde código (vb, c#, xaml)
    5. Actualizar el icono (tile) de una aplicación de Windows Store (cada minuto)
    6. Saber cuando la aplicación está en modo Snapped y actuar en consecuencia

     

    …

  • Pon una AppBar en tu aplicación de Windows Store

    Pues eso… te voy a explicar cómo agregar una AppBar en una aplicación para Windows 8, pero de las que se descargan desde Windows Store (o tienda de Windows).

    Una AppBar es la barra de opciones que se muestra en las aplicaciones para Windows 8 cuando pulsas con el botón derecho (o secundario) del ratón (o mouse).

    La definición de la clase AppBar (en inglés):
    Represents the container control that holds app UI components for commanding and experiences.

    Que traducido al español viene a decir:
    Representa el control contenedor que contiene los componentes de interfaz de usuario para aplicaciones de mando y experiencias.

    Pero mejor lo vemos.

    En las figura 1 tienes esa barra de opciones en la aplicación de Reloj para Windows 8 en la que me estoy basando en estos "consejos" o trucos y que al final parece que va a ser un paso a paso para crear una aplicación para la tienda de Windows 8, si no me crees… ¡ya verás, ya! jejeje.

    AppBar_01
    Figura 1. La AppBar de la aplicación de Reloj para Windows 8

     

    Como puedes comprobar en la figura 1, la aplicación muestra dos botones en la parte izquierda de la barra de opciones y otro botón a la derecha, veremos qué fácil es hacer ese diseño con los StackPanel.

    Fíjate también que estoy usando los iconos predeterminados de Windows 8, ahora veremos cómo usarlos y lo fácil que resultará, ya que todos esos "estilos" están definidos en la plantilla usada para crear aplicaciones de la tienda de Windows.

    Aunque también debo avisarte que dos de esos iconos están modificados para indicar un texto alternativo, ya verás cómo modificar un "estilo" existente y lo fácil que puede ser… sobre todo si alguien te lo explica, ¡ejem! jeje.

     

    Lo primero que haremos es abrir el archivo MainPage.xaml, y posicionar el cursor justo después de la declaración inicial de Page y antes de que se defina el Grid que contiene todo lo que se muestra en la aplicación. Una vez ahí añadimos un "control" AppBar.

    Si añadimos ese "control" desde el Toolbox, decirte que AppBar te lo mostrará en la sección All XAML Controls (ver la figura 2).

    AppBar_02
    Figura 2. El control AppBar está en otra sección de la barra de controles

     

    Pero en nuestro caso lo escribiremos manualmente ya que nos interesa que esa barra sea la de la aplicación y que se muestre en la parte inferior, por tanto escribe lo siguiente y deja que el intellisense de Visual Studio 2012 te ayude…

    Lo primero es definir que es una AppBar para mostrarse en la parte inferior:

    <Page.BottomAppBar>
    

    Dentro de esta definición añadimos la AppBar propiamente dicha:

    <AppBar x:Name="bottomAppBar1" Padding="10,0,10,0">
    

    Ahora añadimos un Grid con dos StackPanel:

    <Grid>
        <StackPanel>
        </StackPanel>
        <StackPanel>
        </StackPanel>
    </Grid>
    

    Le indicamos que el primer panel se debe ajustar a la izquierda y el segundo a la derecha y que la orientación es horizontal. Una vez hecho esto, el código de nuestra AppBar será como te muestro a continuación:

    <Page.BottomAppBar>
        <AppBar x:Name="bottomAppBar1" Padding="10,0,10,0">
            <Grid>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                </StackPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                </StackPanel>
            </Grid>
        </AppBar>
    </Page.BottomAppBar>
    

    Ahora le añadimos dos botones al primero de los dos StackPanel y un botón al segundo panel.

    Los dos primeros botones servirán para parar y reanudar el temporizador, el tercero es para mostrar un mensaje del tipo "About…" (sobre la aplicación), pero de ese botón ya nos encargaremos en otra ocasión.

    Como te comenté antes, esos botones usarán recursos definidos en la aplicación, concretamente los que se incluyen de forma predeterminada con las plantillas de las aplicaciones para Windows Store. Concretamente esos recursos están definidos en el fichero StandardStyles.xaml que se encuentra en la carpeta Global Common.

    Y como te darás cuenta aunque están definidos no están todos disponibles, ahora veremos cómo ponerlos a nuestra disposición y cómo modificarlos.

    Pero primero vamos a añadir el código XAML con la definición de los tres botones a los dos paneles. Te comento que sólo está definido el segundo de ellos el de parar (stop), pero aunque está definido no está disponible, por tanto te mostrará un error en los nombres de los recursos.

    Pero no te preocupes, ahora lo arreglamos.

    AppBar_03

    Figura 3. Los botones usan recursos que "supuestamente" no están definidos.

    Como te he dicho, sólo el segundo de los recursos está definido, mientras que los otros dos están basados en dos ya existentes, es decir, dos recursos que utilizan esos iconos pero se llaman de otra forma.

    Para solucionar esos errores vamos a hacer que los tres recursos estén disponibles.

    Abre el fichero StandardStyles.xaml que está en la carpeta Common (antes te dije que era la carpeta Global, pero no, es la Common, de hecho la carpeta Global no existe en este tipo de proyectos, bueno, es igual, lo corrijo y seguimos).

    Busca la cadena StopAppBarButtonStyle y verás que es la definición de un estilo que está comentado (el editor de Visual Studio te lo mostrará en color verde).

    Para habilitarlo puedes hacer dos cosas:

    1. Quitar todos los comentarios de esas definiciones, o
    2. Copiar ese código y ponerlo fuera de los comentarios.

    Yo he hecho lo segundo, más que nada para no tener tantos estilos disponibles y que en realidad no voy a usar.

    Por tanto selecciona esa definición y ponla fuera de los comentarios, por ejemplo antes del <!– que está un poco más arriba de dicha definición. De esa forma la definición del recurso para el botón Stop quedará así:

    <Style x:Key="StopAppBarButtonStyle" TargetType="ButtonBase"

    BasedOn="{StaticResource AppBarButtonStyle}"> <Setter Property="AutomationProperties.AutomationId" Value="StopAppBarButton"/> <Setter Property="AutomationProperties.Name" Value="Stop"/> <Setter Property="Content" Value="&#xE15B;"/> </Style>

    Ahora vamos a definir los otros dos recursos para los botones Play y About Clock.

    Como te dije estos están basados en dos recursos existentes, el primero es: PlayAppBarButtonStyle y el segundo es: ClockAppBarButtonStyle.

    Por tanto, busca en ese mismo fichero de estilos las dos definiciones que te acabo de indicar, las copias una a una y las pegas fuera de los comentarios (junto a la definición del botón Stop).

    Después las modificas para que queden tal como te muestro a continuación:

    <Style x:Key="StartAppBarButtonStyle" TargetType="ButtonBase" 
           BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="StartAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Start"/>
        <Setter Property="Content" Value="&#xE102;"/>
    </Style>
    
    <Style x:Key="AboutClockAppBarButtonStyle" TargetType="ButtonBase" 
           BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="AboutClockAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="About Clock"/>
        <Setter Property="Content" Value="&#xE121;"/>
    </Style>
    

    Los cambios realizados son básicamente en el nombre (x:Key), en la propiedad AutomationId y en Name, el segundo es para indicar el texto a mostrar bajo el dibujo, el cual es el valor indicado en la propiedad Content.

    Esos dibujos son caracteres del tipo de letra Segoe UI Symbol, y debemos indicar el valor del "icono" a mostrar.

     

    Una vez que tenemos las definiciones de los estilos ya podemos poner nuestro código y veremos que se muestran los dibujos y textos adecuados.

    En este código también están los nombres de los métodos de evento que usaremos al pulsar en ellos. Ahora veremos el código de esos eventos.

    <Page.BottomAppBar>
        <AppBar x:Name="bottomAppBar1" Padding="10,0,10,0">
            <Grid>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                    <Button x:Name="buttonStart" Click="button_Start"
                            Style="{StaticResource StartAppBarButtonStyle}" />
                    <Button x:Name="buttonStop" Click="button_Stop" 
                            Style="{StaticResource StopAppBarButtonStyle}" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <Button x:Name="buttonClock" Click="button_Clock" 
                            Style="{StaticResource AboutClockAppBarButtonStyle}" />
                </StackPanel>
            </Grid>
        </AppBar>
    </Page.BottomAppBar>
    

    En el código de evento de los botones de iniciar (start) y detener (stop) el temporizador es donde vamos a poner el código para iniciar o detener el temporizador (en el ejemplo anterior estaba en el evento Click del botón btnStop, también modificaremos ese código para que utilice los nuevos métodos).

    También haremos que se habiliten o deshabiliten adecuadamente, es decir, una vez que el temporizador está funcionando no hace falta el botón de iniciar (start), por tanto lo deshabilitaremos, ídem de lo mismo en el botón de detener (stop), si está parado el temporizador ¿para qué tener una opción de parar? pues eso…

    Este es el código para VB y C# de esos dos eventos (el de button_Clock no te lo muestro, ya que, como te he dicho antes, es un tema para otro artículo).

    El código mostrado a continuación incluye las modificaciones de (digámoslo así) la primera entrega de este paso a paso (aquí te muestro tanto el código de Visual Basic como el de C# para que elijas el que más te guste, que tampoco es plan de mostrarte sólo el que yo prefiero… jeje).

    Código para Visual Basic:

    Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs) actualizarHora() button_Start(Nothing, Nothing) End Sub Private Sub btnStop_Click(sender As Object, e As RoutedEventArgs) ' En el ejemplo anterior, aquí usaba Handles ' pero ahora al usar la propiedad Click en el código XAML ' ya no es necesario indicar el Handles If btnStop.Content.ToString = "Stop" Then button_Stop(Nothing, Nothing) Else button_Start(Nothing, Nothing) End If End Sub Private Sub button_Stop(sender As Object, e As RoutedEventArgs) timer1.Stop() btnStop.Content = "Start" buttonStart.IsEnabled = True buttonStop.IsEnabled = False End Sub Private Sub button_Start(sender As Object, e As RoutedEventArgs) timer1.Start() btnStop.Content = "Stop" buttonStart.IsEnabled = False buttonStop.IsEnabled = True End Sub Private Async Sub button_Clock(sender As Object, e As RoutedEventArgs) ' Mostrar la imagen del SplashScreen o el mismo texto del Tooltip...

    ' (este código para otra ocasión) ' si te da error, quítale el Async de la definición End Sub

     

    Nota:

    Fíjate que en la definición del evento Click del botón btnStop ahora no uso Handles para indicar que ese es el método de evento de dicho evento, ya que ahora en el código XAML está indicado cuál será el método para interceptar el evento Click de dicho botón.

     

    Código para C#:

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        actualizarHora();
    
        button_Start(null, null);
    }
    
    private void btnStop_Click(object sender, RoutedEventArgs e)
    {
        if (btnStop.Content.ToString() == "Stop")
            button_Stop(null, null);
        else
            button_Start(null, null);
    }
    
    private void button_Stop(object sender, RoutedEventArgs e)
    {
        timer1.Stop();
        btnStop.Content = "Start";
        buttonStart.IsEnabled = true;
        buttonStop.IsEnabled = false;
    }
    
    private void button_Start(object sender, RoutedEventArgs e)
    {
        timer1.Start();
        btnStop.Content = "Stop";
        buttonStart.IsEnabled = false;
        buttonStop.IsEnabled = true;
    }
    
    private async void button_Clock(object sender, RoutedEventArgs e)
    {
        // Mostrar la imagen del SplashScreen o el mismo texto del Tooltip...
    
        // (este código para otra ocasión)
        // si te da error, quítale el async de la definición
    }
    

     

    Y esto es todo por ahora… ¡pronto más! 😉

     

    Espero que te sea de utilidad.

    Nos vemos.

    Guillermo

     

    Enlaces a los otros artículos de este "paso a paso":

    1. Usar un temporizador en las aplicaciones de Windows store (Visual Basic)
    2. Usar un temporizador en las aplicaciones de Windows store (C#)
    3. Pon una AppBar en tu aplicación de Windows Store
    4. Acceder a los recursos de una aplicación de Windows Store desde código (vb, c#, xaml)
    5. Actualizar el icono (tile) de una aplicación de Windows Store (cada minuto)
    6. Saber cuando la aplicación está en modo Snapped y actuar en consecuencia

    …