Archivo de la etiqueta: xaml

Cómo usar el FileOpenPicker para seleccionar ficheros en las apps de Windows Store

 

Pues eso… ahora le toca el turno al FileOpenPicker el "seleccionador" de ficheros en las aplicaciones de la Tienda de Windows.

En las aplicaciones para la Tienda de Windows (Windows Store) esta es la única forma (que yo conozco) de acceder a una carpeta local (o de la red o de un disco extraíble o de un sistema de almacenamiento como Skydrive) y seleccionar un fichero.

Aunque antes hay que indicarle a la aplicación de que queremos acceder a esos sitios, para ello tendremos que abrir el fichero de manifiesto de la aplicación (Package.appxmanifest) y en la ficha Capabilities (ver figura 1) indicar que queremos utilizar el Private Networks y Removable Storage (por si queremos acceder a un disco externo/conectado por USB).

 

FileOpenPicker 02
Figura 1. Indicar las "Capabilities" de la aplicación

 

Lo siguiente que tenemos que hacer es indicar en la ficha Declarations que queremos usar el File Open Picker.

 

FileOpenPicker 03
Figura 2. En Declarations indicamos que queremos usar el File Open Picker

 

Tal como vemos en la figura 2 tenemos que indicar al menos un tipo de fichero (Supported file type) aunque no tiene porqué ser el mismo tipo que vamos a usar, en este ejemplo he indicado ".xml" y casualmente voy a buscar ese tipo de fichero, pero como veremos en el código, también voy a indicar que me muestre los que tengan la extensión .txt (además de los .xml).

La forma de usar ese control es desde dentro de un método asíncrono (async) ya que las llamadas a los métodos para acceder al fichero son llamadas asíncronas.

En el siguiente código vemos lo que necesitamos para usar esa clase. Dependiendo del tipo de ficheros a los que queramos acceder así lo indicaremos en la colección FileTypeFilter, en este ejemplo he añadido dos tipos: .xml y .txt, pero si quisiéramos acceder a todos los tipos, habría que indicar el asterisco (*), en el ejemplo el asterisco está comentado, pero si quieres hacer pruebas, quita el comentario.

Este es el código de Visual Basic, el de C# está más abajo.

Dim openPicker As New FileOpenPicker()
openPicker.FileTypeFilter.Add(".xml")
openPicker.FileTypeFilter.Add(".txt")
'openPicker.FileTypeFilter.Add("*")
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary
openPicker.ViewMode = PickerViewMode.List
Dim cfgFile As StorageFile = Await openPicker.PickSingleFileAsync()

If cfgFile Is Nothing Then Exit Sub

Dim sr = New StreamReader(Await cfgFile.OpenStreamForReadAsync)

 

Utilizando ese código podremos ver algo como la captura de la figura 3.

FileOpenPicker 01

Figura 3. El FileOpenPicker en acción

 

Y esto es prácticamente todo… aquí te dejo el código de ejemplo de C# para que no te quejes si prefieres los puntos y comas 😉

FileOpenPicker openPicker = new FileOpenPicker();
openPicker.FileTypeFilter.Add(".xml");
openPicker.FileTypeFilter.Add("*");
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
openPicker.ViewMode = PickerViewMode.List;
StorageFile cfgFile = await openPicker.PickSingleFileAsync();

if (cfgFile == null) return;

var sr = new StreamReader(await cfgFile.OpenStreamForReadAsync());

 

Comentarte que la clase FileOpenPicker está definida en Windows.Storage.Pickers y la clase StorageFile está definida en Windows.Storage.

Y esto ya si que es todo…

Nos vemos.

Guillermo

Dónde poner el ScrollViewer para poder hacer scroll (app para Windows Store)

 

Pues eso… que siempre me lío y no me aclaro nunca dónde tengo que poner el "dichoso" control ScrollViewer para poder hacer scroll al contenido… ahora que lo escribo tiene su lógica… es decir, si hago la pregunta: ¿dónde lo pongo para poder hacer scroll al contenido? La respuesta es: el ScrollViewer fuera, y dentro del control pondremos lo que haya que hacer scroll… si es que… :-/

Así que, ya sabes… defines el control ScrollViewer y dentro del control pones la caja de texto o lo que sea, y a eso es a lo que podrás hacer scroll (desplazamiento).

Por ejemplo, en el siguiente código XAML tenemos un ScrollViewer que está dentro de un Grid y que contiene una caja de texto (TextBlock) y al contenido de esa caja de textos es a lo que quiero poder hacer scroll. Ese TextBlock está a su vez dentro del un StackPanel, con idea de que ahí puedas meter más cosas y todas esas cosas estarán controladas por el control  de scroll. En este código de ejemplo también hay un botón que estará debajo de la caja de texto.

<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Visible">
    <StackPanel Margin="10" 
                HorizontalAlignment="Center" 
                VerticalAlignment="Center">
        <TextBlock x:Name="txtDatos" TextWrapping="Wrap" 
                   FontSize="16" FontWeight="SemiBold"
                   Text="El contenido del fichero"
                   MinWidth="800" MinHeight="600" />
        <Button Content="Leer un fichero" Margin="0,10,0,0"
                Click="Button_Click" />
    </StackPanel>
</ScrollViewer>

En esta captura puedes ver (por decir algo) cómo queda esto al funcionar en el emulador:

scrollviewer

Y ya está. Esto es todo amigos…

 

Nos vemos.

Guillermo

Mira que soy torpe o el Guille y los tiles de Windows 8.1 Preview

 

Pues eso… que estaba haciendo pruebas (en mi aplicación del Reloj para Windows 8) para utilizar los nuevos tamaños de los iconos de la aplicación en la página de Inicio (también conocidos como tiles) y no me mostraba el tamaño grande.

He estado mirando en la documentación y en los ejemplos (de C# porque de VB no hay todavía para la preview de Windows 8.1) a ver si había algo especial que hacer, me encontré que ahora se indica la versión (version) del elemento visual al número 2:

<visual version="2">

También he visto que se puede indicar fallback con el tipo de template xml anterior, con idea de que si el indicado no está que se use ese otro (que suelen ser iguales):

<binding template="TileSquare150x150Block" fallback="TileSquareBlock">

Pero nada. No me mostraba los nuevos tamaños, solamente los dos que ya tenía: cuadrado y alargado (ahora mediano y ancho).

 

Y después de hacer algunas miles de millones de pruebas (exageradillo que es el niño) resulta que me da por mirar el "manifiesto" de la aplicación y… ¡torpeeeeeeee! ¡queresmutorpe! (me lo digo yo a mi mismo)

Pues resulta que no había imágenes para el tamaño grande, y por tanto… ¡no se puede mostrar ese tamaño en el tile!

Así que… si quieres que tu aplicación soporte todos los tamaños en los tiles, debes tener relleno todos los campos de los tamaños (ver la figura 1).

 

Screenshot (16.1)
Figura 1. Los tamaños del icono / tile soportados por la aplicación

 

Lo dicho, que tengo una memoria de pez olvidadizo y que además de eso más de la mitad de lo que pesa mi cerebro en vez de corteza cerebral es de torpeza cerebral… en fin… :-/

 

Espero que te sirva… y ya ni te pido que dones algo porque se ve que no estás por la labor… yo tampoco lo haría… ¡pa que te voy a decir otra cosa!

 

Nos vemos.
Guillermo

Cambios radicales en Windows 8.1 Preview (para developers)

 

 

Nota del 08/Jul/13 17:00:
Ayer noche publiqué este artículo, y esta mañana he estado haciendo ciertas comprobaciones, y entre ellas he modificado parte del código que te muestro, además de un pequeño truco para seguir usando los elementos VisualState que ya tengamos en el código XAML, aunque ya no existan como "modos de visualización".
La forma de usar o indicar cuál de los elementos VisualState definidos en el código XAML queremos usar (si hubiera alguno, si no existe ese "aspecto visual" simplemente lo ignora y no produce ningún efecto ni excepción) es haciendo una llamada al método GoToState de la clase VisualStateManager. Esa llamada la haremos en el código del método que intercepta el evento SizeChanged. En el código que te muestro más abajo utilizo esto que te estoy comentando.

 

Pues eso… que las cosas han cambiado con Windows 8.1 Preview con respecto a la versión anterior y las cosas que antes nos dijeron que eran de una forma, ahora resulta que no es así, y lo peor es que si no las cambias en tus aplicaciones de Windows 8 para Windows Store (Tienda de Windows) no quedarán "cool" y por tanto darán mucho el cante, es decir, que tus aplicaciones van a destacar, pero no por lo buenas o interesantes que puedan ser, si no porque parecerán aplicaciones que ni siquiera te has molestado en testear (probar).

Bueno, a lo mejor he sido un poco exagerado, ya que esto será así sobre todo si tu aplicación actual para la Tienda de Windows utiliza cosas como acoplarse a la izquierda o derecha (snapped) o tiene en cuenta si tiene otra aplicación acoplada o utiliza actualización del icono de la pantalla de Inicio (live tiles) y algunas cosas más…

Pero aquí estoy yo para intentar darte un poco de claridad en estas cosas que nos vienen encima, así que… empecemos con algunas cosas.

 

Las aplicaciones de Windows 8.1 pueden cambiar de tamaño

Pues eso es así, aunque con la filosofía de Windows 8, es decir, a pantalla completa.
Sí, sí, no me he equivocado, espera y te lo explico.
Las aplicaciones de Windows 8.1 (las que se utilizan para la pantalla de inicio, estilo metro o Modern UI) no funcionan en una ventana independiente, esas son las que funcionan en el escritorio, las propias de Windows 8 cuando las inicias, las veremos a pantalla completa. En la versión actual de Windows 8, además de pantalla completa hay otras dos formas de verlas: acoplada a uno de los dos lados (Snapped) o rellenando lo que deja una aplicación que esté en el modo acoplado (Filled).
Pero esto ha cambiado en Windows 8.1 y ahora los modos Snapped y Filled no existen. La pantalla se puede dividir para que podamos ver dos aplicaciones a la vez (como antes), pero en esta ocasión la división no tiene porqué ser tan evidente como antes, por ejemplo, en la figuras 1 y 2 tenemos cómo se verían las dos mismas aplicaciones usando Windows 8 (fig. 1) o Windows 8.1 Preview (fig. 2).

 

screenshot_07072013_173613_8
Figura 1. Windows 8: Aplicaciones en modo Snapped y Filled

 

screenshot_07072013_173815_8.1
Figura 2. Windows 8.1 Preview: Aunque parecido a Snapped y Filled, internamente no es así

 

Si miramos las dos capturas parece que no hay diferencia en las dos versiones de Windows 8 (salvo por la rayita que hay en la separación), pero si miras la siguiente figura (la 3), verás que la aplicación de la izquierda (que no está preparada para Windows 8.1) sigue manteniendo los tamaños de lo que antes era Snapped, pero ocupando más pantalla. Por otro lado, la aplicación para el tiempo si que se adapta al nuevo tamaño.

 

screenshot_07072013_173841_8.1
Figura 3. Windows 8.1 Preview: El tamaño ya no es fijo como en la versión anterior

 

Como detalle, decirte que en modo Snapped el ancho solía ser de 320 pixel y que en Windows 8.1 cuando inicialmente acoplamos la aplicación a uno de los dos lados, ese tamaño suele ser variable, por ejemplo, en una resolución de 1024 (que antes no permitía el modo Snapped) el ancho sería de 501 pixel, y en una resolución de 1366 ese ancho inicial es de 672 pixel.

 

Saber cuando la aplicación está en los "antiguos" modos Snapped o Filled

Para nosotros los developers (voy a usar la palabra en inglés, ya que algunas veces hay discrepancia entre el uso de desarrollador y programador) lo que debemos saber es que en Windows 8.1 ya no se podrán usar esos modos de visualización, bueno, no sólo no usarlos, lo peor es que no tenemos una forma fácil de saber si nuestra aplicación está en modo acoplado (Snapped) o no.

Lo que Microsoft recomienda o dice en la advertencia (ver figura 4) al usar la enumeración ApplicationViewState, o cuando queremos usar ApplicationView.Value (que es el que nos indica si la aplicación está en modo Snapped o Filled), es que tengamos en cuenta el tamaño al que se muestra nuestra aplicación y actuar en consecuencia.

 

Screenshot (14.1)
Figura 4. Advertencia (warning) al usar las propiedades y enumeraciones obsoletas

Resumiendo, el texto de la advertencia es: que la propiedad Value está obsoleta y que comprobemos directamente el tamaño de la ventana (Instead, query for window layout sizes directly).

Esto está muy bien, particularmente para las nuevas aplicaciones que hagamos, pero el problema es cuando esos tamaños los estamos utilizando en las aplicaciones existentes. La prueba la tienes en la figura 3, que a pesar de tener más espacio disponible, no se adapta al nuevo tamaño.

 

Nota:
Estoy usando la aplicación Microsoft Mahjong en estas capturas, pero podría estar usando muchas otras de las aplicaciones que hay para Windows 8, por ejemplo la de mi Reloj para Windows 8, en las que no se comprueban más cosas, simplemente porque en el momento de crear/publicar la aplicación no había más posibilidades de tamaño que pantalla completa, Snapped o Filled.

 

Como veremos dentro de un momento, me he fabricado unas funciones para saber si estamos o no en modo Snapped (o Filled, etc.) o lo que es lo mismo, cuando la aplicación está acoplada a uno de los dos lados y tiene un tamaño menor del que yo he predeterminado para simular esos valores.

Aunque tengas esas funciones, el problema real será si hemos usado (como ellos nos recomendaron) que la mayoría de los cambios en el tamaño de los controles, etc. lo hiciéramos dentro del código XAML (como elementos VisualState), ya que en Windows 8.1 Preview esos valores los ignoran completamente.

Por tanto, si queremos dar la misma funcionalidad a nuestra aplicación que la que ya teníamos, tendremos que convertir esos valores indicados en el diseño de la ventana en código directo y asignarlos en el evento SizeChanged del control o ventana.

Aunque también podemos indicar cuál de los VisualState que tenemos definido en el código XAML queremos usar. Esta opción es más adecuada, aunque podemos usar una mezcla de los dos, tal como veremos dentro de poco en el ejemplo final.

Y como también hay dos valores que se tenían en cuenta para manipular los tamaños de los objetos mostrados, como son si la aplicación está en modo horizontal (landscape) o en modo vertical (portrait), pues yo también me he fabricado un par de funciones para esas dos ocasiones.

Así que, veamos el código de las funciones que he definido y así será más fácil de entender el código que ponga para realizar esos cambios en el evento de cambio de tamaño de la ventana.

 

Funciones para simular Snapped y Filled o cómo saber si nuestra app está acoplada a uno de los lados

La definición del código la he hecho en la clase App, pero puedes hacerlo donde quieras, ya que están definidos como métodos compartidos (Shared en Visual Basic, static en C#).

En total he definido 6 métodos, dos de ellos para saber si está en modo Snapped o Filled. En estos dos utilizo una constante con el valor 750 para indicar que si el tamaño de la ventana (de la aplicación actual) es menor de esos 750 px y además la aplicación está anclada a la derecha o a la izquierda es que está en modo Snapped, por otro lado, considero que está en modo de relleno (Filled) si está ajustada a uno de los lados y el tamaño es igual o superior a esos 750 pixel. Por supuesto , en esta segunda consideración entrarían los modos en que estaría una aplicación cuando el usuario la está redimensionando (y el tamaño que le asigna es 750 o más), pero para ir viendo los cambios, ya nos vale, o, al menos, a mí me vale.

Nota:
El código que te mostraré es para Visual Basic, pero a diferencia de lo que han hecho la gente de Microsoft, también te mostraré el código de C#, pero no en el momento en que estoy escribiendo esto, pero lo pondré.

 

El código de Visual Basic de los métodos IsSnapped y IsFilled sería el siguiente:

 

Public Const AnchoSnapped As Double = 750

''' <summary>
''' Simular el modo Filled en Windows 8.1
''' Comprueba si la aplicación está en uno de los costados
''' y el ancho es mayor o igual que <see cref="AnchoSnapped"/> px
''' </summary>
''' <remarks>07/Jul/13</remarks>
Public Shared Function IsFilled() As Boolean
    Dim aView = ApplicationView.GetForCurrentView()
    Dim aWidth = Window.Current.Bounds.Width

    ' Si está acomplado a ambos lados es que es pantalla completa
    ' (esto es así cuando se cambia el tamaño estando acoplado)
    If (aView.AdjacentToLeftDisplayEdge AndAlso aView.AdjacentToRightDisplayEdge) Then
        Return False

    ElseIf (aView.AdjacentToLeftDisplayEdge OrElse aView.AdjacentToRightDisplayEdge) AndAlso
        aWidth >= AnchoSnapped Then

        Return True
    Else
        Return False
    End If
End Function

''' <summary>
''' Simular el modo Snapped en Windows 8.1
''' Si el ancho es menor de <see cref="AnchoSnapped"/> pixels
''' y está en uno de los lados lo considero Snapped
''' </summary>
''' <remarks>06/Jul/13</remarks>
Public Shared Function IsSnapped() As Boolean
    Dim aView = ApplicationView.GetForCurrentView()
    Dim aWidth = Window.Current.Bounds.Width

    ' Amplio las posibilidades comprobando solo si el tamaño es menor de AnchoSnapped
    ' ya que esté o no acoplada, el tamaño y posición del contenido debe adaptarse
    '
    'If (aView.AdjacentToLeftDisplayEdge OrElse aView.AdjacentToRightDisplayEdge) AndAlso
    '    aWidth < AnchoSnapped Then
    If aWidth < AnchoSnapped Then
        Return True
    Else
        Return False
    End If

End Function

 

Un par de cosas que quiero comentarte sobre el código anterior.

La primera es que estoy usando Window.Current.Bounds.Width para saber el ancho de la pantalla, y seguramente dirás que podría usar la propiedad ActualWidth de la ventana principal (no se puede desde este código porque la clase App no se deriva de Page), entre otras cosas porque es la forma que seguramente has podido ver en los ejemplos (para C#) que se incluyen con el SDK de Windows 8.1 Preview. Pero, he de decirte que esa propiedad a mí NUNCA me ha servido, siempre que lo he intentado el valor devuelto es 0.0, así que… he optado por esa otra que sé que funciona.

La segunda es el uso del nuevo método GetForCurrentView de la clase ApplicationView. Ese método devuelve un objeto del tipo ApplicationView que entre otras cosas contiene los dos valores que estoy usando en esas dos funciones: AdjacentToLeftDisplayEdge que nos indica si nuestra aplicación está acoplada a la izquierda de la pantalla, y AdjacentToRightDisplayEdge que como podrás suponer es para saber si la aplicación está acoplada a la derecha.

Tiempos aquéllos en las las funciones tenían unos pocos caracteres… mejor así, sobre todo si sabes algunas palabras de inglés.

Creo que el código no necesita mayor explicación, salvo indicar los cambios realizados en esta versión del lunes 8 de julio:

En el código de la función IsSnapped la he reducido a una simple comprobación de si el ancho de la pantalla es menor del que queremos considerar como "acoplado", en este ejemplo estoy usando el valor de la constante AnchoSnapped que inicialmente la he definido a 750 pixel.

En la función IsFilled ahora compruebo que no esté acoplado a ambos lados, ya que eso supondría que la aplicación está a pantalla completa, por tanto si se cumple la primera comprobación devuelve un valor falso, y por tanto solamente será verdadero cuando esté acoplado a uno de los dos lados y el ancho sea igual o mayor del indicado en AnchoSnapped.

 

Saber si la aplicación está en modo apaisado o vertical

Otras dos funciones que me he fabricado, más que nada para evitar estar repitiendo las definiciones de las llamadas al API de Windows 8.1 Preview (por medio de las clases de .NET Framework), son las que me indican si la aplicación está a pantalla completa y cómo está orientada la tableta o el dispositivo en el que está funcionando.

En la versión actual de Windows 8 esto lo hacíamos mediante los correspondientes valores de la enumeración ApplicationViewState, pero como ya hemos comprobado, se considera obsoleta.

Por ejemplo, para saber si nuestra aplicación estaba en modo de pantalla completa y en horizontal, comprobábamos si el valor de la propiedad Value de ApplicationView era FullScreenLandscape.

Ahora esto se hace comprobando si el valor de IsFullScreen es cierto y además el valor de Orientation es Landscape. Las dos primeras son nuevas propiedades de la clase ApplicationView y el valor de la propiedad Orientation es del tipo enumerado ApplicationViewOrientation.

Una vez dicho esto, podemos comprobar que las dos funciones que me he fabricado lo único que hacen es reducir un poco el código cuando queramos hacer las comprobaciones pertinentes.

Este es el código de Visual Basic de esas dos funciones.

 

''' <summary>
''' Comprobar si la pantalla está en FullScreenPortrait
''' (vertical a pantalla completa)
''' </summary>
''' <remarks>06/Jul/13</remarks>
Public Shared Function IsFullScreenPortrait() As Boolean
    Dim aView = ApplicationView.GetForCurrentView()

    If aView.IsFullScreen AndAlso aView.Orientation = ApplicationViewOrientation.Portrait Then
        Return True
    Else
        Return False
    End If
End Function

''' <summary>
''' Comprobar si la pantalla está en FullScreenLandscape
''' (apaisada a pantalla completa)
''' </summary>
''' <remarks>06/Jul/13</remarks>
Public Shared Function IsFullScreenLandscape() As Boolean
    Dim aView = ApplicationView.GetForCurrentView()

    If aView.IsFullScreen AndAlso aView.Orientation = ApplicationViewOrientation.Landscape The
        Return True
    Else
        Return False
    End If
End Function

 

Te comentaba que en total eran 6 funcionen las que me había fabricado, las otras dos simplemente devuelven un objeto del tipo ApplicationView (el valor devuelto por GetForCurrentView); y la otra función indica el ancho de la pantalla. Pero en el ejemplo que pondré ahora no se utilizan, por tanto, mejor las dejo ahí para otra ocasión.

 

Ejemplo para usar las funciones definidas

Veamos cómo usar estas funciones para adaptar el tamaño, margen, etc. de los controles según nuestra aplicación esté "acoplada" o no.

Este ejemplo es muy simple y sólo tiene dos controles TextBlock con distinto texto, uno servirá para el título y el otro para el texto a mostrar.

El aspecto en modo diseño es el que podemos ver en la figura 5.

 

Screenshot (15)

Figura 5. La página principal en modo diseño

 

Primero definimos los valores de los elementos VisualState, que en el ejemplo que estoy usando serían los indicados en el siguiente código XAML:

 

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="251*"/>
            <ColumnDefinition Width="640*"/>
            <ColumnDefinition Width="475*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="107*"/>
            <RowDefinition Height="384*"/>
            <RowDefinition Height="277*"/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="txtTitle" Grid.Column="1" Text="El título" FontSize="110" />
        
        <TextBlock x:Name="txtMensaje" Grid.Column="0" Grid.Row="1" 
                   Grid.ColumnSpan="3" Grid.RowSpan="2"
                   VerticalAlignment="Center"
                   FontSize="140" FontWeight="Bold" TextWrapping="WrapWholeWords"
                   Text="El texto que ocupa la pantalla completa con cambio de línea" />

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ApplicationViewStates">
                <VisualState x:Name="FullScreenLandscape" />
                <VisualState x:Name="FullScreenPortrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtTitle" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="90"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtMensaje" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="70"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Filled">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtTitle" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="80"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtMensaje" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="100"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Snapped">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtTitle" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="80"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtMensaje" Storyboard.TargetProperty="FontSize">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="70"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>

        </VisualStateManager.VisualStateGroups>

    </Grid>
</Page>


 

Todo esto lo haremos en el método del evento SizeChanged del control o página/ventana en el que queramos tener estas cosas en cuenta.

 

Private Sub MainPage_SizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
    ' valor predeterminado de visualState                       (08/Jul/13)
    Dim visualState As String = "FullScreenLandscape"

    If App.IsFilled Then
        visualState = "Filled"
    ElseIf App.IsSnapped Then
        visualState = "Snapped"
    ElseIf App.IsFullScreenPortrait Then
        visualState = "FullScreenPortrait"
    Else
        visualState = "FullScreenLandscape"

        ' Si queremos adaptar los tamaños según el ancho
        ' lo podemos hacer aquí\f0
        ' Pero debemos tener en cuenta que el valor asignado en el código XAML
        ' tiene preferencia.
        ' En este ejemplo, esto sólo funciona cuando es FullScreenLandscape
        ' ya que no se asignan valores en el código XAML

        Dim ww = Window.Current.Bounds.Width
        If ww <= 800 Then
            txtTitle.FontSize = 70
            txtMensaje.FontSize = 70
        ElseIf ww <= 1024 Then
            txtTitle.FontSize = 80
            txtMensaje.FontSize = 90
        ElseIf ww < 1366 Then
            txtTitle.FontSize = 100
            txtMensaje.FontSize = 110

        Else
            ' Los valores predeterminados
            txtTitle.FontSize = 110
            txtMensaje.FontSize = 140
        End If

    End If

    ' Utilizar los valores del código XAML indicado al visualState indicado
    VisualStateManager.GoToState(Me, visualState, False)

    ' Si quisiéramos usar valores distintos a los inidcados en el código XAML
    ' tendremos que asignarlos aquí, después de llamar a VisualStateManager.GoToState

End Sub

 

Por favor lee los comentarios del código para que sepas cuando esas asignaciones al tamaño de la fuente del texto funcionan, ya que siempre tendrá preferencia el valor asignado en el código XAML y si queremos que el asignado manualmente sea el que "mande" debemos usar el truco que ahí indico.

 

Bueno, creo que con esto ya está bien para esta primera aproximación sobre los cambios que se incluyen en Windows 8.1 Preview y que los que queremos crear aplicaciones para la Tienda de Windows debemos tener en cuenta.

Hay más cosas, algunas te las contaré y de otras seguramente no te diré ni (las que no haya probado).

 

Espero que te se de utilidad y ya sabes, si quieres invitarme a un café virtual, puedes hacerlo en el botón "Donar" 😉

Gracias (incluso si no me invitas).

 

Nos vemos.

Guillermo

P.S.

Cuando tenga listo el código de C# pondré el proyecto de ejemplo en mi sitio de descargas (downloads) para que puedas verlo con más detalle.

Asignar el número de pista (track number) a un MP3

 

Pues eso… que no sé si a ti te pasará, pero yo tengo "algunos" ficheros de música en formato MP3 que a pesar de haberlo convertido a partir de un CD original (ejem) no siempre se guarda la información completa en la cabecera del susodicho fichero MP3.

Y como la mayoría de los reproductores de MP3 suelen clasificar las canciones por el número de pista, si dicho valor no está asignado, pues… imagínate qué follón…

En vista que los dos (o tres) últimos smartphone que he tenido se empeñan en ordenar por el número de pista y harto de ir asignando manualmente dicho número, me he fabricado una pequeña utilidad (muy simple, ya verás) usando WPF (Windows Presentation Foundation) o lo que es lo mismo: utilizando un proyecto en XAML.

Este programa te permite seleccionar (o procesar) todos los ficheros .mp3 de una carpeta (o directorio) y asignar el número de pista según exista en el nombre del fichero la siguiente secuencia:
guión, cifra, guión
Por ejemplo si tenemos este fichero: Slade-02-Coz I Love You.mp3:
El número de pista es el número 2 y el título de la canción es "Coz I Love You".

Además, también te permite asignar el nombre del álbum y del autor/cantante.
Estos dos últimos datos se obtienen automáticamente de la canción elegida para indicar el directorio (ya que tienes que seleccionar un fichero .mp3 y así se sabe qué directorio quieres procesar). De todas formas, si ese fichero no tuviese los datos de la cabecera asignados puedes indicarlos de forma manual, ya que la aplicación asignará esos datos si las respectivas cajas de texto no están vacías.
En cuanto al nombre o título de la canción sólo lo asignará si marcamos la opción correspondiente.

Después te pongo los enlaces al código fuente (por ahora sólo para Visual Basic .NET).

 

La información del MP3 está en lo que se conoce como la cabecera del fichero MP3 (MP3 header) y en su día busqué en la web ejemplos de cómo obtener esa información y di con un fichero ZIP que contenía una clase de Visual Basic .NET precisamente para acceder a dicha cabecera.
Esa clase se llama MP3Info.vb y estaba contenida en un ZIP con el nombre vbmp3header_src.zip y la verdad es que no recuerdo de dónde la descargué. El autor supongo que es alemán ya que en el fichero AssemblyInfo.vb que se incluía también en el zip estaba en alemán, pero sin datos personales.

Nota:/Actualización
Haciendo una nueva búsqueda he dado con el artículo en Code Project en el que está ese fichero ZIP y como internamente coinciden las fechas, pues… debe ser de donde lo bajé o al menos ese debe ser el autor "de verdad".
Dicho artículo (enlace incluido) es: Read MP3 header information and read/write the ID3v1 tag

(By Thommy Mewes, 1 Mar 2005)

El fichero (MP3Info.vb) que yo he usado está modificado con respecto al original, así que, ese será el que te muestre con el código y que publicaré en pastebin.

 

 

Aquí tienes una captura del "formulario" en modo de diseño y el código (para Visual Basic) te lo dejo inicialmente en mi cuenta de pastebin, después si decido dejar el proyecto completo en algún otro sitio (por ejemplo en mi sitio de descargas) ya te informaré o mejor aún, ya pondré los enlaces correspondientes.

 

Asignar MP3 Track MainWindows
El formulario en modo de diseño

 

Como ves, no te muestro (código con los) detalles de lo que hace el programa… pero de todas formas la parte importante es (o debería ser) el código para acceder a la cabecera de los ficheros MP3.
De todas formas no sé si te pasará lo mismo que a mí con los ficheros MP3, pero si te pasa y quieres el ejecutable… me lo dices en los comentarios y ya te lo dejo por ahí en mi sitio de descargas.

Sea como sea, te pongo el código completo en pastebin y ya lo vas compilando y esas cosas…

Aquí tienes los enlaces individuales para que puedas usar el código creando un proyecto nuevo del tipo WPF:

 

¡Que asignes los números de pista bien! 😉

 

Nos vemos.
Guillermo

Algunas diferencias entre WPF para escritorio y la Tienda de Windows

 

Pues eso… aunque el título más bien tendría que ser: algunas propiedades que están en las aplicaciones WPF para escritorio y que no está o son diferentes en las aplicaciones WPF para la Tienda de Windows.

 

Aquí te enumero algunas que me he ido encontrando:

1.- En las aplicaciones WPF para escritorio algunos controles tienen la propiedad Tooltip, en las aplicaciones para Windows Store hay que usar: ToolTipService.ToolTip.

2.- En WPF/desktop el control TextBlock tiene la propiedad IsEnabled, en las aplicaciones de Windows Store podemos usar: IsTextSelectionEnabled.

En este ejemplo se asigna a esa propiedad el valor que tenga la propiedad IsEnabled de un control llamado cboTablas:

IsTextSelectionEnabled="{Binding Source=cobTablas, Path=IsEnabled}"

 

3.- El control TextBlock no tiene la propiedad Background en las aplicaciones para la Tienda de Windows. En este caso, lo que yo he hecho es poner dicho control dentro de un objeto Border, ya que lo que yo hacía con el color ese de fondo era darle otro aspecto (tipo información):

<Border Background="LightGoldenrodYellow" 
        Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3" 
        Margin="5" VerticalAlignment="Top">
    <TextBlock x:Name="lblInfo" Text=""  
               TextWrapping="Wrap" FontSize="10" 
               IsTextSelectionEnabled="{Binding Source=cobTablas, Path=IsEnabled}"/>
</Border>

 

 

Y ya no me he encontrado con más problemas en esta aplicación (simple) que estoy migrando… el problema seguro que lo tendré con el código… #enfin…

 

Nos vemos.

Guillermo

Utilizar el mismo control en dos sitios (Windows Store)

 

Pues eso… que quería usar el mismo control en dos sitios diferentes (según se muestre o no el AppBar) y como no quería crear otro control para que haga lo mismo que el que ya tenía, pues… a hacer lo que "supuestamente" había que hacer… es decir, cambiarlo de contenedor.

Lo que pasa con las aplicaciones de la Tienda de Windows (Windows Store) o lo mismo es cosa de XAML, ¡vaya usted a saber!, es que el mismo control (incluso un clon referenciado, vamos que no es una copia) no puede estar en dos sitios a la vez.

Esto es lógico, pero creo que con los controles de VB6 o de Windows Forms cuando lo "emparentabas" en otro control ya perdía toda la familiaridad con el que antes hacía de padre… pero hace unas semanas que comprobé que en XAML/Windows Store no es así.

¿No te estás enterando verdad? No te preocupes, es que cuando no duermo, pues… pasa lo que pasa… la cuestión es que te voy a explicar cómo usar el mismo control para que "parezca" que es una copia ya que unas veces se mostrará en la pantalla de la aplicación y si cuando se muestre el AppBar ese mismo control estará en la barra de la aplicación.

El código te lo voy a mostrar en Visual Basic y también en C# (para que no te quejes).

Pero primero voy a mostrarte el código XAML.

 

<Page.BottomAppBar>
    <AppBar x:Name="mainBottomAppBar" Style="{StaticResource ClockAppBarBackground}" Padding="10,0,10,0"
            Closed="mainBottomAppBar_Closed" Opened="mainBottomAppBar_Opened">
        <Grid>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                <Button x:Name="buttonAlarmas"  
                        Style="{StaticResource AlarmasAppBarButtonStyle}" Click="buttonAlarmas_Click" />
            </StackPanel>            
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                <Button x:Name="buttonClock"
                        Style="{StaticResource AboutClockAppBarButtonStyle}" Click="button_Clock" />
                <StackPanel x:Name="spAppBarSettings" Margin="0,0,-10,0">
                    <Button x:Name="buttonSettings"  
                            Style="{StaticResource SettingsAppBarButtonStyle}" Click="button_Settings" />
                </StackPanel>
            </StackPanel>
        </Grid>
    </AppBar>
</Page.BottomAppBar>

 

Nota:

Los estilos usados en el código XAML son modificaciones de los que hay en el fichero StandardStyles.xaml, salvo el estilo de la barra de la aplicación que lo que contiene es la definición del color de fondo y del borde.

De todas formas, pondré el código de muestra de la página XAML en pastebin.com y así no tendrás problema con los estilos y esas cosas.

 

utilizar mismo control

 

Este código es la definición de la AppBar en la que hay tres botones, uno de ellos muestra el panel de configuración (settings charm) y los otros dos hacen lo mismo que si eligiéramos esas dos opciones en el propio panel de configuración, aquí no te muestro el código, pero te lo explico para que sepas porqué el control AppBar tiene esos dos eventos.

En esos dos eventos es donde hago el cambio de contenedor del control buttonSettings (el de configuración) ya que es el que me interesa que esté "aparentemente" en dos sitios a la vez.

Este es el código de Visual Basic y el de C# está convertido, así que… aunque supongo que funcionará, comprueba que lo hace… (es que tendría que crear un proyecto para probarlo yo, y… pues eso… )

VB:

 

' Añadir el botón de configuración al panel inferior    (04/Feb/13)
' para que siempre esté visible
' Pero sólo cuando se cierre la AppBar
Private Sub mainBottomAppBar_Opened(sender As Object, e As Object)
    spRow2.Children.Remove(buttonSettings)
    spAppBarSettings.Children.Add(buttonSettings)
End Sub

Private Sub mainBottomAppBar_Closed(sender As Object, e As Object)
    spAppBarSettings.Children.Remove(buttonSettings)
    spRow2.Children.Add(buttonSettings)

 

C#:

 

// Añadir el botón de configuración al panel inferior    (04/Feb/13)
// para que siempre esté visible
// Pero sólo cuando se cierre la AppBar
private void mainBottomAppBar_Opened(object sender, object e)
{
    spRow2.Children.Remove(buttonSettings);
    spAppBarSettings.Children.Add(buttonSettings);
}

private void mainBottomAppBar_Closed(object sender, object e)
{
    spAppBarSettings.Children.Remove(buttonSettings);
    spRow2.Children.Add(buttonSettings);
}

 

Es importante que el control que queremos mover esté primero en el contenedor spRow2, ya que al abrir la AppBar se intentará quitar de la colección de este panel y si o está… ¡ganaremos un hermoso error!

Esto lo podemos hacer por código (por ejemplo asignándolo en algunos de los eventos iniciales de la página o mejor aún, a la hora del diseño. Es decir, en vez de ponerlo donde yo lo he puesto (en la AppBar, más que nada para que vieras el código completo de la barra) debería estar en el control spRow2.

 

Otro problema con el que me he encontrado es que resulta que cuando el usuario pulsa en buttonSettings la barra de la aplicación se oculta, pero cuando se pulsa en cualquiera de los otros dos… pues… no se oculta, así que… lo que he hecho es cerrar por medio de código la barra, pero no he usado la propiedad "Visibility" para ocultarla, ya que eso haría que no se volviera a ver. Lo que he hecho es asignarle un valor falso a la propiedad IsOpen, de forma que se cierra pero sin más problemas. Esa asignación la hago en los dos botones que me daban problema, es decir, cuando el usuario pulsa en uno de esos botones, le asigno el valor falso a esa propiedad (IsOpen) y después muestro el panel de configuración y todo lo que haya que hacer.

En este caso, el código es muy simple (adivina cuál es el de VB y cuál el de C#):

 

mainBottomAppBar.IsOpen = False

mainBottomAppBar.IsOpen = false
 ;

 

Bueno, después de este inciso, sigamos con lo de "mover" el control de sitio.

Como ya te comenté hace un rato, el control para mostrar el panel de configuración (settings charm) también debe estar en el panel al que he llamado spRow2 y que estará situado al pie de la página (concretamente el control ese parecerá que está en el mismo sitio).

Y como ese StackPanel sólo tendrá el botón en cuestión hay un problema añadido, y es que cuando lo quitamos de ahí para ponerlo en el AppBar, el resto de la pantalla se baja… así que… ¡hay que arreglarlo! y eso lo he arreglado indicando la altura mínima del panel que lo contiene, de forma que ese tamaño sea el mismo que el que tiene el botón.

Mir el código y cuando lo pruebes, quita la asignación de MinHeight y verás lo que ocurre.

 

<!-- el MinHeight es para que al quitar el botón (por código)
     no se mueva lo que haya en medio 
     que se iba para abajo al abrir la appBar -->
<StackPanel x:Name="spRow2" Grid.Column="2" Orientation="Horizontal" MinHeight="90">
    <!-- poner aquí el botón de configuración, pero por código -->
</StackPanel>


 

Y creo que con esto ya está bien…

Te resumo las cosillas que creo que tiene de interesante este rollo que te he soltado, perdona si no me he sabido explicar bien o lo ves algo poco claro y enredado, pero es que llevo más de 24 horas sin pegar ojo y… eso se nota…  No es una excusa, pero… si no entiendes todo lo que te he dicho, para que sepas que no es culpa tuya, al menos el 100% jejeje (¡qué malo soy!).

 

En este ejemplo hemos visto:

– Cómo añadir una AppBar en la parte inferior de nuestra página (se mostrará al pulsar con el botón secundario del ratón).

– Hacer que se cierre la AppBar en caso de que se quedara abierta al pulsar en uno de los botones.

– Cuando veas el código XAML completo del ejemplo, sabrás cómo agregar estilos para usarlos sólo en la página en la que se definen.

– Utilizar los eventos de apertura y cierre de la AppBar para cambiar un botón de un contenedor a otro, concretamente de la colección Children de un control StackPanel.

– Cómo mantener las cosas en su sitio cuando el tamaño de un panel o contenedor cambia de tamaño (normalmente de la altura).

– Y si te fijas en el código verás que está el botón de ir hacia atrás, pero como esta página no lo utiliza (y tampoco la página en la que utilizo el código este que estoy mostrándote), lo he ocultado, pero con idea de que el título no se desplace a la izquierda, he puesto que la columna en la que está el botón de ir hacia atrás en lugar de tener un ancho automático (<RowDefinition Height="Auto"/> ) lo he puesto para que tenga un tamaño fijo, que es el que he calculado teniendo en cuenta el ancho del botón más los márgenes, en mi caso (y generalmente si usas ese diseño) es de 110 (<RowDefinition Height="110"/> ).

– Y seguramente más cosas, pero lo principal es lo que te he relacionado.

 

Recuerda que en mi cuenta de Pastebin tienes el código completo (o casi) del ejemplo (que al final he hecho), tanto para Visual Basic como para C#.

 

 

Espero que te haya sido de utilidad.

Nos vemos.

Guillermo

Usar el corrector ortográfico del control TextBox (Windows Store)

 

Pues eso… que el control TextBox que viene en el Visual Studio 2012 para las aplicaciones de la Tienda de Windows (Windows Store / Windows 8 / estilo Metro) tiene una propiedad llamada IsSpellCheckEnabled que si le asignamos un valor verdadero utilizará el corrector ortográfico y nos marcará las palabras que no reconozca, que no quiere decir que no estén bien…

La asignación la podemos hacer en modo de diseño (y supongo que por medio de código en el constructor de la clase / página en la que esté definido esa caja de textos que queremos que use el corrector ortográfico).

Este es el código que he usado en la aplicación en el que lo voy a utilizar (es la primera vez que uso un control con corrección ortográfica, al menos de forma consciente).

 

<TextBox x:Name="txtTexto" Grid.Column="1" Grid.Row="1" Text="" 
     Width="Auto" MinWidth="200" Margin="0,6" IsSpellCheckEnabled="True" 
         TextWrapping="Wrap" Height="80"/>

 

Y en esta captura lo puedes ver en acción:

 

IsSpellCheckEnabled

Figura 1.

 

Algunas palabras las corrige automáticamente y otras, simplemente te las marca como no válidas.

Y por supuesto te da la opción de corregirla (figura 2)… siempre que el corrector "sepa" cómo hacerlo.

 

IsSpellCheckEnabled 02

Figura 2. Opciones del corrector ortográfico

 

 

Nos vemos.

Guillermo

Crear un SplashScreen personalizado para la Windows Store

 

Pues eso… que en las aplicaciones para la Tienda de Windows te "exigen" que tengas una imagen de al menos 620×300 para usar como pantalla de presentación (splash screen) mientras la aplicación se carga.

Si quieres poner algún texto en esa imagen, dicho texto debe ser estático, es decir, cada vez que lo cambies en realidad estarías cambiando la imagen, y lo que te voy a explicar aquí es cómo añadir textos (o cualquier otra cosa) a esa imagen de inicio, pero de forma que sólo se añada (o sea visible) mientras está cargando el programa.

 

Nota:
Este ejemplo está basado, y creo que mejorado, pero sobre todo simplificado, en los ejemplos de la MSDN / SDK:

Cómo extender la pantalla de presentación y Directrices y lista de comprobación para pantallas de presentación, en concreto el ejemplo de C# para Evitar un parpadeo durante la transición a la pantalla de presentación extendida.

 

En este ejemplo he procurado que, en modo de diseño, la imagen esté centrada y dentro de un Grid con idea de que podamos situar los textos donde queramos que estén.

Hay que tener en cuenta que aquí estoy usando la imagen predeterminada de 620×300 pero si utilizas alguna de las otras dos, debes tenerlo en cuenta a la hora de posicionar los textos.

He dejado la imagen predeterminada, es decir, la que utiliza el Visual Studio al crear un nuevo proyecto, por tanto, los valores de las columnas y filas del Grid puede que no sean los adecuados si tienes otra imagen.

Lo que si es conveniente saber es que el ancho y alto de esas columnas y filas deben coincidir con el tamaño de la imagen, ya que así tendremos una visión exacta de dónde estarán posicionados las cosas que le añadamos a esa imagen de inicio.
En este ejemplo he usado el logo pequeño de la aplicación (SmallLogo.png) y un par de cajas de textos, una de ellas la modifico (si es necesario) en tiempo de ejecución.

En la siguiente captura puedes ver cómo quedaría con el código de ejemplo usado en este artículo.

 

ExtendedSplash

 

 

Nota:
El color de fondo de la pantalla de inicio y de la aplicación deberían coincidir con el indicado en el manifiesto de la aplicación (Package.appxmanifest), ya que si no… pues… lo mismo no queda bien.
Fíjate que en el manifiesto de la aplicación hay dos definiciones de colores, uno para las imágenes "normales" (los logos) y otro para la pantalla de inicio (Splash screen). Yo los he puesto todos (también el de MainPage) con el mismo color verde: #185F18.

 

Vamos a ver el código XAML de la página/control que hará de pantalla de inicio en este ejemplo.

 

Código Xaml El código Xaml
<Grid
    x:Class="Splash_Screen.ExtendedSplash"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="using:Splash_Screen"
    mc:Ignorable="d"
    Background="#185F18">

    <!-- la imagen tiene 620 x 300 -->
    <Canvas MaxHeight="300" MaxWidth="620">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="35" />
                <RowDefinition Height="225" />
                <RowDefinition Height="40" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="60"/>
                <ColumnDefinition Width="140"/>
                <ColumnDefinition Width="420"/>
            </Grid.ColumnDefinitions>

            <Image x:Name="extendedSplashImage" Grid.Row="0" Grid.Column="0"
                   Grid.RowSpan="4" Grid.ColumnSpan="4" 
                   Margin="0" Source="///Assets/SplashScreen.png" 
                   ImageOpened="extendedSplashImage_ImageOpened"/>
            <TextBlock x:Name="txtTitulo" x:Uid="txtTitle" 
                       Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
                       Foreground="White" FontSize="18" FontWeight="Bold" 
                       HorizontalAlignment="Center"
                       Text="Prueba de Splash Screen personalizada" />
            <TextBlock x:Name="txtCopyr" Grid.Row="2" Grid.Column="2" 
                       Foreground="White" FontSize="15" FontWeight="Bold" 
                       Margin="0,10,0,0"
                       Text="©Guillermo Som (elGuille), 2013" />
            <Image Grid.Row="2" Grid.Column="0" Grid.RowSpan="2"
                   Height="30" Width="30" Stretch="Uniform" 
                   Source="///Assets/SmallLogo.png" Margin="0,0,0,10"  Opacity="100" />
        </Grid>
    </Canvas>
</Grid> 

 

Nota sobre las tres /// que hay delante de la carpeta en la que están las imágenes:

Esas tres barras indican que es desde el directorio base de la aplicación, de esa forma, si mueves este fichero de la Splash screen a otra carpeta no tendrás que hacer cambios para indicar la ruta de las imágenes.

 

Ahora veamos el código de esa página, como verás hay dos constructores, pero nosotros solo usaremos el que recibe dos parámetros.

 

Código para Visual Basic.NET (VB.NET) El código para Visual Basic .NET
'------------------------------------------------------------------------------
' ExtendedSplash                                                    (19/Ene/13)
' Usar un SplashScreen personalizado
'
' Basado en un ejemplo de la MSDN (y creo que mejorado y simplificado)
' Sólo hay que modificar App.OnLaunched
'
' Cómo extender la pantalla de presentación
' http://msdn.microsoft.com/es-es/library/windows/apps/xaml/hh868191.aspx
' y Directrices y lista de comprobación para pantallas de presentación para
' Evitar un parpadeo durante la transición a la pantalla de presentación extendida
' http://msdn.microsoft.com/es-es/library/windows/apps/hh465338.aspx#ts_flicker_cs
'
'
' ©Guillermo 'guille' Som, 2013
'------------------------------------------------------------------------------

Imports System
Imports Windows.ApplicationModel.Activation
Imports Windows.Foundation
Imports Windows.UI.Core
Imports Windows.UI.Xaml
Imports Windows.UI.Xaml.Controls

Partial Class ExtendedSplash
    ' Rect to store splash screen image coordinates. 
    Friend splashImageRect As Rect
    ' Variable to track splash screen dismissal status. 
    Friend dismissed As Boolean = False
    ' Variable to hold the splash screen object. 
    Private splash As SplashScreen

    Friend rootFrame As Frame


    Private showWindowTimer As DispatcherTimer
    Private showWindowTimerNum As Integer = 0

    Private Sub OnShowWindowTimer(sender As Object, e As Object)
        showWindowTimerNum += 1

        If showWindowTimerNum = 1 Then
            ' Activate/show the window, now that the splash image has rendered
            Window.Current.Activate()

            ' aquí hacemos un pequeño descanso antes de mostrar
            ' la página principal
        ElseIf showWindowTimerNum >= 50 Then
            showWindowTimer.Stop()
            cargarMainPage()
        End If

    End Sub

    Private Sub extendedSplashImage_ImageOpened(sender As Object, e As RoutedEventArgs)
        ' ImageOpened means the file has been read, but the image hasn't been painted yet.
        ' Start a short timer to give the image a chance to render, before showing the window
        ' and starting the animation.
        showWindowTimer = New DispatcherTimer()
        showWindowTimer.Interval = TimeSpan.FromMilliseconds(50)
        AddHandler showWindowTimer.Tick, AddressOf OnShowWindowTimer
        showWindowTimer.Start()

    End Sub

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

    End Sub

    ''' <summary> 
    ''' Constructor with splash screen information 
    ''' </summary> 
    Public Sub New(splashscreen As SplashScreen, loadState As Boolean)
        InitializeComponent()

        If DateTime.Now.Year > 2013 Then
            txtCopyr.Text = "©Guillermo Som (elGuille), 2013-" & DateTime.Now.Year.ToString
        End If

        ' Listen for window resize events to reposition the extended splash screen image accordingly. 
        ' This is important to ensure that the extended splash screen is formatted properly in response to
        ' snapping, unsnapping, rotation, etc... 
        AddHandler Window.Current.SizeChanged, AddressOf ExtendedSplash_OnResize

        splash = splashscreen

        If splash IsNot Nothing Then
            ' Register an event handler to be executed when the splash screen has been dismissed. 
            AddHandler splash.Dismissed, AddressOf DismissedEventHandler

            ' Retrieve the window coordinates of the splash screen image. 
            splashImageRect = splash.ImageLocation
            PositionImage()
        End If

        ' Create a Frame to act as the navigation context  
        rootFrame = New Frame()

        '' Restore the saved session state if necessary 
        'RestoreStateAsync(loadState)


    End Sub

    'Public Async Sub RestoreStateAsync(loadState As Boolean)
    '    If loadState Then
    '        Await SuspensionManager.RestoreAsync()
    '    End If
    '    ' Normally you should start the time consuming task asynchronously here and  
    '    ' dismiss the extended splash screen in the completed handler of that task 
    '    ' This sample dismisses extended splash screen in the handler for "Learn More" button for demonstration 
    'End Sub

    ' Position the extended splash screen image in the same location as the system splash screen image. 
    Private Sub PositionImage()
        extendedSplashImage.SetValue(Canvas.LeftProperty, splashImageRect.X)
        extendedSplashImage.SetValue(Canvas.TopProperty, splashImageRect.Y)

        extendedSplashImage.Height = splashImageRect.Height
        extendedSplashImage.Width = splashImageRect.Width
    End Sub

    Private Sub ExtendedSplash_OnResize(sender As Object, e As WindowSizeChangedEventArgs)
        ' Safely update the extended splash screen image coordinates.
        ' This function will be fired in response to snapping, unsnapping, rotation, etc... 
        If splash IsNot Nothing Then
            ' Update the coordinates of the splash screen image. 
            splashImageRect = splash.ImageLocation
            PositionImage()
        End If
    End Sub

    Private Sub cargarMainPage()
        ' Navigate to MainPage 
        rootFrame.Navigate(GetType(MainPage))

        '' Set extended splash info on Main Page 
        'DirectCast(rootFrame.Content, MainPage).SetExtendedSplashInfo(splashImageRect, dismissed)

        ' Place the frame in the currrent window 
        Window.Current.Content = rootFrame

    End Sub

    ' Include code to be executed when the system has transitioned from the splash screen to the extended splash screen (application's first view). 
    Private Sub DismissedEventHandler(sender As SplashScreen, e As Object)
        dismissed = True

        ' Navigate away from the app's extended splash screen after completing setup operations here... 
        ' This sample navigates away from the extended splash screen when the "Learn More" button is clicked. 
    End Sub
End Class

 

 

Código para C Sharp (C#) El código para C#
//-----------------------------------------------------------------------------
// ExtendedSplash                                                   (19/Ene/13)
// Usar un SplashScreen personalizado
//
// Basado en un ejemplo de la MSDN (y creo que mejorado y simplificado)
// Sólo hay que modificar App.OnLaunched
//
// Cómo extender la pantalla de presentación
// http://msdn.microsoft.com/es-es/library/windows/apps/xaml/hh868191.aspx
// y Directrices y lista de comprobación para pantallas de presentación para
// Evitar un parpadeo durante la transición a la pantalla de presentación extendida
// http://msdn.microsoft.com/es-es/library/windows/apps/hh465338.aspx#ts_flicker_cs
//
//
// ©Guillermo 'guille' Som, 2013
//------------------------------------------------------------------------------

using System;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Splash_Screen
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class ExtendedSplash : Grid
    {
        public ExtendedSplash()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Constructor with splash screen information
        /// </summary>
        public ExtendedSplash(SplashScreen splashscreen, bool loadState)
        {
            InitializeComponent();

            //
            // Aquí pondremos lo que haya que actualizar en los textos
            //
            if (DateTime.Now.Year > 2013)
            {
                txtCopyr.Text = "©Guillermo Som (elGuille), 2013-" + DateTime.Now.Year.ToString();
            }

            // Listen for window resize events to reposition the extended splash screen image accordingly.
            // This is important to ensure that the extended splash screen is formatted properly in response to
            // snapping, unsnapping, rotation, etc...
            Window.Current.SizeChanged += ExtendedSplash_OnResize;

            splash = splashscreen;

            if (splash != null)
            {
                // Register an event handler to be executed when the splash screen has been dismissed.
                splash.Dismissed += DismissedEventHandler;

                // Retrieve the window coordinates of the splash screen image.
                splashImageRect = splash.ImageLocation;
                PositionImage();
            }

            // Create a Frame to act as the navigation context
            rootFrame = new Frame();

            // Restore the saved session state if necessary
            //RestoreStateAsync(loadState);


        }
        
        //async void RestoreStateAsync(bool loadState)
        //{
        //    if (loadState)
        //        await SuspensionManager.RestoreAsync();

        //    // Normally you should start the time consuming task asynchronously here and  
        //    // dismiss the extended splash screen in the completed handler of that task 
        //    // This sample dismisses extended splash screen  in the handler for "Learn More" button for demonstration 
        //} 


        // Rect to store splash screen image coordinates.
        internal Rect splashImageRect;

        // Variable to track splash screen dismissal status.
        internal bool dismissed = false;

        // Variable to hold the splash screen object.
        private SplashScreen splash;

        internal Frame rootFrame;


        private DispatcherTimer showWindowTimer;
        private int showWindowTimerNum = 0;

        private void OnShowWindowTimer(object sender, object e)
        {
            showWindowTimerNum += 1;

            if (showWindowTimerNum == 1)
            {
                // Activate/show the window, now that the splash image has rendered
                Window.Current.Activate();
            }
            // aquí hacemos un pequeño descanso antes de mostrar
            // la página principal
            else if (showWindowTimerNum >= 50)
            {
                showWindowTimer.Stop();
                cargarMainPage();
            }
        }

        private void extendedSplashImage_ImageOpened(object sender, RoutedEventArgs e)
        {
            // ImageOpened means the file has been read, but the image hasn't been painted yet.
            // Start a short timer to give the image a chance to render, before showing the window
            // and starting the animation.
            showWindowTimer = new DispatcherTimer();
            showWindowTimer.Interval = TimeSpan.FromMilliseconds(50);
            showWindowTimer.Tick += OnShowWindowTimer;
            showWindowTimer.Start();
        }

        // Position the extended splash screen image in the same location as the system splash screen image.
        private void PositionImage()
        {
            extendedSplashImage.SetValue(Canvas.LeftProperty, splashImageRect.X);
            extendedSplashImage.SetValue(Canvas.TopProperty, splashImageRect.Y);

            extendedSplashImage.Height = splashImageRect.Height;
            extendedSplashImage.Width = splashImageRect.Width;
        }

        private void ExtendedSplash_OnResize(object sender, WindowSizeChangedEventArgs e)
        {
            // Safely update the extended splash screen image coordinates.
            // This function will be fired in response to snapping, unsnapping, rotation, etc...
            if (splash != null)
            {
                // Update the coordinates of the splash screen image.
                splashImageRect = splash.ImageLocation;
                PositionImage();
            }
        }

        private void cargarMainPage()
        {
            // Navigate to MainPage
            rootFrame.Navigate(typeof(MainPage));

            // Set extended splash info on Main Page
            //(rootFrame.Content as MainPage).SetExtendedSplashInfo(splashImageRect, dismissed);

            // Place the frame in the currrent window
            Window.Current.Content = rootFrame;
        }

        // Include code to be executed when the system has transitioned 
        // from the splash screen to the extended splash screen (application's first view).
        private void DismissedEventHandler(SplashScreen sender, object e)
        {
            dismissed = true;

            // Navigate away from the app's extended splash screen after completing setup operations here...
            // This sample navigates away from the extended splash screen when the "Learn More" button is clicked.
        }  
    }
}

 

Ahora sólo falta modificar el código de App.xaml, concretamente el método OnLaunched para que muestre nuestra página de inicio en lugar de MainPage (desde el código de ExtendedSplash nos encargamos de mostrar esa página cuando se ha terminado de mostrar.

 

Aquí tienes el código de VB y el de C# del método OnLaunched de la clase App:

Visual Basic:

Protected Overrides Sub OnLaunched(args As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)

    ' Para usar el SplashScreen personalizado                   (07/Ene/13)
    If args.PreviousExecutionState <> ApplicationExecutionState.Running Then
        Dim loadState As Boolean = (args.PreviousExecutionState = ApplicationExecutionState.Terminated)
        Dim extendedSplash As ExtendedSplash = New ExtendedSplash(args.SplashScreen, loadState)
        Window.Current.Content = extendedSplash

        ' ExtendedSplash will activate the window when its initial content has been painted.

        ' Salir
        Exit Sub
    End If


    Dim rootFrame As Frame = TryCast(Window.Current.Content, Frame)

    ' Do not repeat app initialization when the Window already has content,
    ' just ensure that the window is active

    If rootFrame Is Nothing Then
        ' Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = New Frame()
        If args.PreviousExecutionState = ApplicationExecutionState.Terminated Then
            ' TODO: Load state from previously suspended application
        End If
        ' Place the frame in the current Window
        Window.Current.Content = rootFrame
    End If
    If rootFrame.Content Is Nothing Then
        ' When the navigation stack isn't restored navigate to the first page,
        ' configuring the new page by passing required information as a navigation
        ' parameter
        If Not rootFrame.Navigate(GetType(MainPage), args.Arguments) Then
            Throw New Exception("Failed to create initial page")
        End If
    End If

    ' Ensure the current window is active
    Window.Current.Activate()
End Sub

 

 

C#:

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    // Para usar el SplashScreen personalizado              (07/Ene/13)
    if (args.PreviousExecutionState != ApplicationExecutionState.Running)
    {
        bool loadState = (args.PreviousExecutionState == ApplicationExecutionState.Terminated);
        ExtendedSplash extendedSplash = new ExtendedSplash(args.SplashScreen, loadState);
        Window.Current.Content = extendedSplash;

        // ExtendedSplash will activate the window when its initial content has been painted.

        // Salir
        return;
    }
    

    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
        if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
        {
            throw new Exception("Failed to create initial page");
        }
    }
    // Ensure the current window is active
    Window.Current.Activate();
}

 

Ya solo quedaría hacer algo en MainPage, pero ahí haz lo que quieras, ya que no hace falta añadir ningún código para esto de mostrar la SplashScreen. Es decir, en esa página añade las cosas que tu aplicación tendrá que hacer.

Y si en lugar de usar MainPage utilizas cualquiera de las otras páginas usadas en los ejemplos de Visual Studio, acuérdate de cambiar el nombre de esa página por la que corresponda en el método que te acabo de mostrar de la clase App y también en el método cargarMainPage de ExtendedSplash.

 

Espero que te sirva para personalizar mejor tus aplicaciones para la Tienda de Windows 😉

 

Nos vemos.

Guillermo

Ejemplo sencillo de notificaciones (toast) para Windows Store

 

Pues eso… que aunque aún no son horas de toastar nada, pero te voy a poner un par de ejemplos (o uno solo) de cómo mostrar las notificaciones del sistema (toast notifications), que no son otra cosa que los mensajes (o notificaciones) que aparecen en los laterales de la parte superior de la pantalla de Windows (ver la figura 1).

 

toast01
Figura 1.

 

En principio este tipo de notificaciones están pensadas para eso, notificar al usuario de algo en un momento concreto (ahora te explico esto), pero sobre todo, que el usuario se entere de que eso está ocurriendo (lo están avisando), así que… este tipo de notificaciones se mostrarán siempre encima de lo que haya en ese momento en la pantalla… y cuando digo pantalla me refiero a la pantalla tanto de inicio de Windows 8 como a la "pantalla del escritorio".

 

Lo del par de ejemplos que te comentaba antes es porque hay dos formas de notificar (o mostrar las notificaciones). Una es de forma inmediata, es decir: ¡ya!. La otra es para que se muestre en el momento que indiquemos, ya sean unos segundos después o más tiempo… no sé exactamente cuanto tiempo, pero incluso días y meses después. La tercera (sí, ya se que te dije que había dos, pero…) es una especie de variación de la segunda, ya que también se puede indicar la periodicidad de dicha notificación (si se repite, etc.)

Es decir, que hay muchas posibilidades de usar las notificaciones del sistema (o toast notifications), así que, si además de lo que te explique aquí quieres saber más: Introducción a las notificaciones del sistema (Toast notification overview)

 

Lo primero que debes saber es que esto es para usarlo en las aplicaciones de Windows Store y que en el manifiesto de la aplicación tienes que indicar que quieres usar este tipo de notificaciones.

Abre el fichero Package.appxmanifest y en la primera ficha (Application UI) en la lista de las imágenes busca Badge Logo y selecciónala, en la parte de la derecha tendrás las opciones de notificaciones (notifications) en la lista Toast capable selecciona Yes tal como puedes ver en la figura 2.
De no hacerlo, las notificaciones simplemente ¡¡¡ NO APARECERÁN !!!

 

toast03
Figura 2

 

Una vez que tenemos esto, vamos a ver el código (en principio de Visual Basic, ya también el de C# o lo pongo más tarde o te pongo un enlace a Pastebin para cuando esté listo), de todas formas, los que usáis C# no os podéis quejar mucho, ya que la mayoría de los ejemplos de la documentación y otros sitios web están en C#, algo que no es tan habitual para los que prefieren usar Visual Basic, en fin…

 

Los pasos a seguir:

Crea un proyecto para la tienda de Windows (yo he creado el m´s básico).

Abre el fichero del manifiesto de la aplicación (Package.appxmanifest) y selecciona Yes en Toast capable (ver figura 2).

En MainPage.xaml vamos a crear una barra de aplicación (AppBar) con tres botones.
El diseño de esos botones los tomaremos del fichero StandardStyles.xaml (en la carpeta Common), pero como quiero cambiarles el texto para que me lo muestre en castellano, los he copiado y pegado en los recursos de la página (cambiándoles el nombre para que no haya conflictos, aunque en esta caso no los habría ya que los botones del fichero StandarStyles suelen estar comentados, al menos en la plantilla de las aplicaciones básicas.

Esos tres botones usarán distintos tipos de notificaciones, con idea de que sepas manejarte con varias de las posibilidades que tiene esto de los "toast notifications".

El primer botón envía una notificación inmediata usando un periodo largo, es decir, la notificación tardará más de lo habitual en quitarse, también está desactivado el audio (es una notificación silenciosa y de larga duración, jeje).

Al pulsar en el segundo botón mostrará una notificación a los 3 segundos y esta si que será con sonido y de duración normal.

El tercer botón se notificará de forma inmediata y otra más a los 10 segundos, ambas serán de larga duración y con sonido.

 

Aquí tienes una captura en pleno funcionamiento:

 

toast05
Figura 3. La aplicación en funcionamiento

 

Este es el código XAML completo, con la definición de los estilos de los tres botones.

 

<Page
    x:Class="Toast_notifications.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Toast_notifications"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <Style x:Key="PlayEsAppBarButtonStyle" TargetType="ButtonBase" 
               BasedOn="{StaticResource AppBarButtonStyle}">
            <Setter Property="AutomationProperties.AutomationId" Value="PlayAppBarButton"/>
            <Setter Property="AutomationProperties.Name" Value="Inmediato"/>
            <Setter Property="Content" Value="&#xE102;"/>
        </Style>

        <Style x:Key="HelpEsAppBarButtonStyle" TargetType="ButtonBase" 
               BasedOn="{StaticResource AppBarButtonStyle}">
            <Setter Property="AutomationProperties.AutomationId" Value="HelpAppBarButton"/>
            <Setter Property="AutomationProperties.Name" Value="Ayuda"/>
            <Setter Property="Content" Value="&#xE11B;"/>
        </Style>
        <Style x:Key="ClockEsAppBarButtonStyle" TargetType="ButtonBase" 
               BasedOn="{StaticResource AppBarButtonStyle}">
            <Setter Property="AutomationProperties.AutomationId" Value="ClockAppBarButton"/>
            <Setter Property="AutomationProperties.Name" Value="Programado"/>
            <Setter Property="Content" Value="&#xE121;"/>
        </Style>
    </Page.Resources>
    
    <Page.BottomAppBar>
        <AppBar x:Name="bottomAppBar1" Padding="10,0,10,0">
            <Grid>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                    <Button Style="{StaticResource PlayEsAppBarButtonStyle}" 
                            Click="ButtonPlay_Click" />
                    <Button Style="{StaticResource ClockEsAppBarButtonStyle}" 
                            Click="ButtonClock_Click" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <Button Style="{StaticResource HelpEsAppBarButtonStyle}" 
                            Click="ButtonHelp_Click" />
                </StackPanel>
                
            </Grid>
        </AppBar>
    </Page.BottomAppBar>
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="80" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <!-- El título de la página -->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120"/>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="pageTitle" Grid.Column="1" Text="Prueba de toast notifications" 
                       IsHitTestVisible="false" 
                       Style="{StaticResource PageHeaderTextStyle}" />
            <Image x:Name="imgLogo" Grid.Column="2" Margin="10" 
                   Source="///Assets/SmallLogo.png" Stretch="Uniform" />
        </Grid>

    </Grid>
</Page>

 

Y este es el código de los tres botones Visual Basic:

Primero te pongo las dos importaciones que hay que añadir:

 

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

 

Y este es el código de los tres botones, las explicaciones están en los comentarios.

 

Private Sub ButtonPlay_Click(sender As Object, e As RoutedEventArgs)

    ' La plantilla a usar, esta Text03 es:
    ' un texto de cabecera que puede ocupar dos líneas y una línea de texto normal
    Dim toastTemplate As ToastTemplateType = ToastTemplateType.ToastText03
    ' Asignamos el template a un documento Xml
    Dim toastXml As XmlDocument = ToastNotificationManager.GetTemplateContent(toastTemplate)

    ' El texto para el primero elemento de la pantilla
    Dim toastTextElements As XmlNodeList = toastXml.GetElementsByTagName("text")
    toastTextElements(0).AppendChild(toastXml.CreateTextNode("Cargando ..."))

    ' Si queremos que la duración sea larga
    ' Puede ser corta o larga, corta es la predeterminada
    Dim toastNode As IXmlNode = toastXml.SelectSingleNode("/toast")
    TryCast(toastNode, XmlElement).SetAttribute("duration", "long")

    ' Si queremos quitar el sonido
    ' (o indicar alguno en particular)
    ' tenemos que usar el elemento "audio"
    'Dim toastNode As IXmlNode = toastXml.SelectSingleNode("/toast")
    Dim audio As XmlElement = toastXml.CreateElement("audio")
    audio.SetAttribute("silent", "true")
    toastNode.AppendChild(audio)


    Dim toast As New ToastNotification(toastXml)
    ToastNotificationManager.CreateToastNotifier().Show(toast)
End Sub

Private Sub ButtonClock_Click(sender As Object, e As RoutedEventArgs)

    ' La plantilla a usar, esta Text03 es:
    ' un texto de cabecera que puede ocupar dos líneas y una línea de texto normal
    Dim toastTemplate As ToastTemplateType = ToastTemplateType.ToastText03
    ' Asignamos el template a un documento Xml
    Dim toastXml As XmlDocument = ToastNotificationManager.GetTemplateContent(toastTemplate)

    ' El texto para el primero elemento de la pantilla
    Dim toastTextElements As XmlNodeList = toastXml.GetElementsByTagName("text")
    toastTextElements(0).AppendChild(
        toastXml.CreateTextNode("A los 3 segundos después de haber pulsado en el botón."))
    toastTextElements(1).AppendChild(
        toastXml.CreateTextNode("Segundo texto."))

    ' Esto es para indicar que esta notificación se hará en el momento indicado
    Dim dueTime As DateTime = DateTime.Now.AddSeconds(3)
    Dim scheduledToast As New ScheduledToastNotification(toastXml, dueTime)

    ToastNotificationManager.CreateToastNotifier().AddToSchedule(scheduledToast)

End Sub

Private Sub ButtonHelp_Click(sender As Object, e As RoutedEventArgs)
    ' La plantilla a usar, esta Text02 es:
    ' un texto de cabecera y un texto normal que puede ocupar dos líneas
    Dim toastTemplate As ToastTemplateType = ToastTemplateType.ToastText02
    ' Asignamos el template a un documento Xml
    Dim toastXml As XmlDocument = ToastNotificationManager.GetTemplateContent(toastTemplate)

    ' El texto para el primero elemento de la pantilla
    Dim toastTextElements As XmlNodeList = toastXml.GetElementsByTagName("text")
    toastTextElements(0).AppendChild(
        toastXml.CreateTextNode("Esto se mostrará durante más tiempo ..."))
    toastTextElements(1).AppendChild(
        toastXml.CreateTextNode("Siempre puedes cerrar las notificaciones en la X superior."))

    ' Si queremos que la duración sea larga
    ' Puede ser corta o larga, corta es la predeterminada
    Dim toastNode As IXmlNode = toastXml.SelectSingleNode("/toast")
    TryCast(toastNode, XmlElement).SetAttribute("duration", "long")

    ' Esto es para indicar que esta notificación se hará en el momento indicado
    Dim dueTime As DateTime = DateTime.Now.AddSeconds(10)
    Dim scheduledToast As New ScheduledToastNotification(toastXml, dueTime)

    ToastNotificationManager.CreateToastNotifier().AddToSchedule(scheduledToast)

    Dim toast As New ToastNotification(toastXml)
    ToastNotificationManager.CreateToastNotifier().Show(toast)

 

Ese es el código para C# (no he tardado tanto, ¿verdad? son las 06.35 y lo publiqué a eso de las 6.05)

 

Añade estas dos importaciones:

 

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

 

private void ButtonPlay_Click(object sender, RoutedEventArgs e)
{
    // La plantilla a usar, esta Text03 es:
    // un texto de cabecera que puede ocupar dos líneas y una línea de texto normal
    ToastTemplateType toastTemplate = ToastTemplateType.ToastText03;

    // Asignamos el template a un documento Xml
    XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);

    // El texto para el primer elemento de la pantilla
    XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
    toastTextElements[0].AppendChild(toastXml.CreateTextNode("Cargando ..."));

    // Si queremos que la duración sea larga
    // Puede ser corta o larga, corta es la predeterminada
    IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
    ((XmlElement)toastNode).SetAttribute("duration", "long");

    // Si queremos quitar el sonido
    // (o indicar alguno en particular)
    // tenemos que usar el elemento "audio"
    // IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
    XmlElement audio = toastXml.CreateElement("audio");
    audio.SetAttribute("silent", "true");
    toastNode.AppendChild(audio);

    ToastNotification toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}

private void ButtonClock_Click(object sender, RoutedEventArgs e)
{
    // La plantilla a usar, esta Text03 es:
    // un texto de cabecera que puede ocupar dos líneas y una línea de texto normal
    ToastTemplateType toastTemplate = ToastTemplateType.ToastText03;

    // Asignamos el template a un documento Xml
    XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);

    // El texto para el primer elemento de la pantilla
    XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
    toastTextElements[0].AppendChild(
        toastXml.CreateTextNode("A los 3 segundos después de haber pulsado en el botón."));
    toastTextElements[1].AppendChild(
        toastXml.CreateTextNode("Segundo texto."));

    // Esto es para indicar que esta notificación se hará en el momento indicado
    DateTime dueTime = DateTime.Now.AddSeconds(3);
    ScheduledToastNotification scheduledToast = new ScheduledToastNotification(toastXml, dueTime);

    ToastNotificationManager.CreateToastNotifier().AddToSchedule(scheduledToast);
}

private void ButtonHelp_Click(object sender, RoutedEventArgs e)
{
    // La plantilla a usar, esta Text02 es:
    // un texto de cabecera y un texto normal que puede ocupar dos líneas
    ToastTemplateType toastTemplate = ToastTemplateType.ToastText02;
    // Asignamos el template a un documento Xml
    XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);

    // El texto para el primero elemento de la pantilla
    XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
    toastTextElements[0].AppendChild(
        toastXml.CreateTextNode("Esto se mostrará durante más tiempo ..."));
    toastTextElements[1].AppendChild(
        toastXml.CreateTextNode("Siempre puedes cerrar las notificaciones en la X superior."));

    // Si queremos que la duración sea larga
    // Puede ser corta o larga, corta es la predeterminada
    IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
    (toastNode as XmlElement).SetAttribute("duration", "long");

    // Esto es para indicar que esta notificación se hará en el momento indicado
    DateTime dueTime = DateTime.Now.AddSeconds(10);
    ScheduledToastNotification scheduledToast = new ScheduledToastNotification(toastXml, dueTime);

    ToastNotificationManager.CreateToastNotifier().AddToSchedule(scheduledToast);

    ToastNotification toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}

 

 

Como comentario adicional, decirte que podemos "detener" una notificación si tenemos a nuestra disposición el objeto con la que se creó, ya que para detenerla tenemos que usar el método Hide de la función compartida CreateToastNotifier. A ese método Hide le tenemos que pasar una referencia al objeto que queremos detener. Yo esto lo he usado en la aplicación que he mandado a la tienda de Windows, en la que muestro la notificación mientras se carga una página web en el control WebView y la oculto cuando dicha página se ha terminado de cargar o lo que es lo mismo, intercepto el evento LoadComplete del control WebView, y ahí es donde quito la notificación.

Como es natural, el objeto "toast" usado para esa notificación no puede estar definido dentro de un sub, ya que así no sería accesible desde otra parte del código. Por tanto, en ese caso, el objeto toast usado para poder ocultarlo cuando queramos, lo tendríamos que definir fuera de cualquier método.

 

Espero que todo esto te sea de utilidad y para los que prefieren el C#, dadme unos minutos a que cree el proyecto en ese lenguaje y convierta el código, lo tacho porque ya está publicado.

 

Nos vemos.

Guillermo