Posts Tagged ‘Visual Basic’

en el día del libro (23 de abril), te regalo dos

April 23rd, 2013

 

Pues eso… ya que estamos con el día del libro, te recuerdo que yo te regalo dos: uno de Visual Basic y otro de C#.

En realidad ya te los estuve regalando (o al menos poniéndolo gratuitamente a tu disposición) desde finales de agosto del año pasado, pero… por si no has entrado en mi blog y/o en mi sitio desde hace tiempo… o simplemente has entrado, pero ni te has enterado (que también suele pasar). Así que… como no es extraño que se te pasen las cosas… aquí estoy yo para recordártelo, jejeje

 

Nota:
Las descargas se hacen desde la web de SolidQ y debes registrarte para poder descargarlos.

 

Aquí tienes los enlaces para que te los descargue desde el sitio de SolidQ:
(recuerda que tienes que estar registrado)

Visual Basic:

guille_novedadesVB9

Novedades de Visual Basic 9.0 (Visual Basic 2008, LINQ, tipos y métodos anónimos, etc.)

Visual C#:

guille_aprendeCSharp3

Aprenda C# 3.0 desde 0.0 – Parte 3: Lo nuevo (Visual Studio 2008, LINQ, tipos y métodos anónimos, etc.)

 

Espero que te guste el regalo y sobro todo que te sigan siendo de utilidad.

Nos vemos.
Guillermo

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

March 28th, 2013

 

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

Utilizar el mismo control en dos sitios (Windows Store)

February 4th, 2013

 

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

y yo pensaba que convertir fechas y horas ya no sería más un calentamiento de cabeza

January 31st, 2013

 

Pues eso… que sigue siendo… al menos si utilizas "tu formato preferido", aunque sea con otro "locale"… en fin… que no recuerdo yo desde cuando no tenía problemas para convertir una cadena con el formato de una fecha (supuestamente de forma correcta) a un objeto de tipo DateTime. A la memoria me vienen los tiempos del VB6 (o cualquier otro anterior a VB.NET) y sobre todo me acuerdo de cuando estaba escribiendo el código de mis foros con Visual Studio 2005 (ASP.NET 2.0).

Y hoy va y me da este regalito el Visual Studio 2012:

 

error al convertir fechas
Figura 1. Error al convertir 31/01/2013 20:40:00 a una fecha…

Así que… he tenido que optar por "salir del paso" convirtiendo "a mano" esas fechas erróneas y a usar el formato "yankee" para no tener problemas posteriores… espero que así sea…

 

Te explico que todo ese "follón" es porque quiero guardar el contenido de una clase definida por mí mismo en los "settings" de Windows 8, con idea de poder usarla la próxima vez que ejecute la aplicación.

En mi clase he sobrescrito el método ToString para que devuelva una cadena que posteriormente la pueda pasar desde lo que es, un tipo string, al tipo que he definido. Y ese ToString tiene el equivalente FromString para que devuelva un objeto usando el contenido de una cadena (con el formato que previamente he definido… o casi).

El "problema" era que yo dejaba que el propio .NET se encargara de convertir la fecha en cadena, es decir, no usaba ningún formato específico, por tanto el formato devuelto es: #d/M/yyyy h:m:sAM/PM# (o más o menos) y claro, la cadena usada era diferente… total un follón.

Así que… además de la comprobación manual, ahora lo que he hecho es que el método ToString devuelve la fecha con este formato: Fecha.ToString("#M/d/yyyy HH:mm:00#") y que salga el sol por Antequera

El "00" para los segundo es porque en este caso quiero que los segundos siempre sean 00, ya que es para una alarma en la que ese espacio de tiempo no se tiene en cuenta…

 

Te muestro parte del código que ahora estoy usando y lo que he tenido que hacer para salir del paso… (sabiendo que el error es porque el formato es dd/MM/yyyy HH:mm:ss, por eso no hay más comprobaciones en el caso de que falle la conversión del DateTime.TryParse.

 

Private Const elSeparador As String = ","

''' <summary>
''' Devuelve una cadena con los datos básicos:
''' Fecha y Mensaje.
''' Si se devuelven más datos, 
''' devolver el mensaje siempre al final.
''' </summary>
Public Overrides Function ToString() As String
    Return String.Format("{0}{2} {1}", Fecha.ToString("#M/d/yyyy HH:mm:00#"), Mensaje, elSeparador)
End Function

''' <summary>
''' Devuelve un objeto Alarma a partir del contenido
''' de una cadena, en el formato: "Fecha, Mensaje"
''' (el devuelto por <see cref="ToString"/>)
''' En lugar de la coma se usará el caracter indicado en <see cref="elSeparador"/>
''' </summary>
Public Shared Function FromString(s As String) As Alarma
    Dim it As New Alarma

    Dim i = s.IndexOf(elSeparador)
    If i = -1 Then
        Return Nothing
    End If

    If DateTime.TryParse(s.Substring(0, i), it.Fecha) = False Then
        Dim s1 = s.Substring(0, i)
        ' si da el error es porque está en formato dd/MM/yyyy HH:mm:ss
        Dim dia = CInt(s1.Substring(0, 2))
        Dim mes = CInt(s1.Substring(3, 2))
        Dim año = CInt(s1.Substring(6, 4))
        Dim hora = CInt(s1.Substring(11, 2))
        Dim minuto = CInt(s1.Substring(14, 2))
        it.Fecha = New DateTime(año, mes, dia, hora, minuto, 0)
    End If
    'it.Fecha = CDate(s.Substring(0, i))
    it.Mensaje = s.Substring(i + 1).TrimStart()

    Return it
End Function

 

Sólo lo muestro en VB porque en C# no lo estoy usando para este programa, pero como comprobarás no es un código demasiado complicado… creo… ;-)

 

Pues ya está… ahí queda eso… para que no te confíes con las fechas desde cadena… ;-)

 

Nos vemos.

Guillermo

Crear un SplashScreen personalizado para la Windows Store

January 19th, 2013

 

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

January 18th, 2013

 

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

Crear un objeto a partir de una cadena con Activator.CreateInstance

January 5th, 2013

 

Pues eso… como el título indica, te voy a explicar cómo crear (instanciar sería más correcto) un objeto a partir del "nombre" de una clase, pero estando ese nombre dentro de una cadena. Y como de costumbre, con el código de ejemplo tanto para (mi querido) Visual Basic como para (mi obligado) C# ;-)

Lo de "obligado" también lo puedes tomar por "obrigado" que en portugués significa "gracias", de agradecimiento, no te vayas a pensar que el C# me hace gracia… bueno, algunas veces me hago a la idea que me está guiñando… (por lo del punto y coma) ;-)

 

Vamos al tema.

Si tienes una cadena con el nombre de una clase, puedes crear una nueva instancia de esa clase utilizando la clase Activator, más concretamente el método compartido CreateInstance.

Para que funcione la conversión de cadena a objeto del tipo correcto debes tener en cuenta que:

  1. Indiques el espacio de nombres del tipo en la cadena
  2. La clase puede ser una clase derivada (o implementada)
  3. El tipo tenga un constructor público y sin parámetros
  4. Las clases abstractas (abstract / MustInherit) no sirven ya que no se pueden instanciar
  5. Que la clase a crear esté accesible, vamos que si tú no puedes crear el objeto ni indicando el tipo de forma explícita el Activator no creo que haga milagros…

 

Una vez que sabemos las condiciones que debemos seguir para instanciar clases a partir del contenido de un string vamos a ponerlo a prueba con unos ejemplos en los que comprobaremos lo dicho en la lista anterior y algo más.

El código para hacer la conversión lo podemos simplificar al máximo, pero en ese caso sólo deberíamos usarlo para crear clases que cumplan las 2 primeras condiciones indicadas en la lista anterior.
Para casos indicados en los puntos 3 y 4 habría que usar un bloque try/catch para detectar la excepción que ocurrirá, ya que el tipo es correcto pero no se puede instanciar.
Para los casos que cumplan la última condición (el tipo no existe o no está accesible) podemos usar una comprobación que en realidad es para saber si el tipo es del mismo tipo a convertir o se deriva de él, esa comprobación la haremos por medio del método IsAssignableFrom (definido en la clase Type).

Veamos primero el código que hace la conversión sin más comprobaciones 
(el código completo lo he puesto en un método para que sea más fácil hacer varias de las pruebas).

VB:

Dim laClase = "ActivatorVB.Colega"

Dim elTipo As Type = Type.GetType(laClase)

Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)

If obj IsNot Nothing Then
    obj.Nombre = "Pepe"
    obj.Email = "pepe@outlook.com"
End If

 

C#:

var laClase = "ActivatorCS.Colega";

Type elTipo = Type.GetType(laClase);

Colega obj = Activator.CreateInstance(elTipo) as Colega;

if (obj != null)
{
    obj.Nombre = "Pepe";
    obj.Email = "pepe@outlook.com";
}

 

Un par de aclaraciones (tanto para VB como para C#, las aclaraciones) sobre este código:

En VB utilizo TryCast y en C# utilizo as para hacer la conversión ya que de esa forma, si el objeto no se puede crear se devuelve un valor nulo.

Si en VB utilizara CType o DirectCast o en C# hiciera la conversión con (Colega) y la conversión falla, produciría un error.

Aunque en realidad esto sólo nos salvará de los casos indicados en el punto 5 de las "condiciones" para realizar la instanciación de la clase a partir de una cadena. Pero bueno, ya sabes que hay varias formas de hacer conversiones entre tipos ;-)

 

Para hacer la comprobación de si el tipo es adecuado por medio del método IsAssignableFrom lo haríamos de la siguiente forma:

 

'  VB
' Esto controlará las clases que no se basan en Colega
If Not GetType(Colega).IsAssignableFrom(elTipo) Then Console.WriteLine("El tipo: {0} no es del tipo adecuado." & vbLf & "", laClase) Return End If // C# // Esto controlará las clases que no se basan en Colega if (!typeof(Colega).IsAssignableFrom(elTipo)) { Console.WriteLine("El tipo: {0} no es del tipo adecuado.\n", laClase); return; }

 

Ya solo me queda ponerte todo el código de prueba en el que he hecho las pruebas esas que conforman la lista para estar seguro de que es así…

Te recomiendo que hagas tú las tuyas propias, incluso usando las partes comentadas en dicho código.

En el método Main2 de dicho código he puesto un ejemplo de una clase que implementa una interfaz.

 

Nota:

El código completo lo pongo aparte para que no llene tanto esto.

Además te pongo separado el de Visual Basic de el de C# y tu eliges el que quieras.

También está publicado en Pastebin.

 

Espero que te sea de utilidad.

Nos vemos.

Guillermo

P.S.

Los enlaces:

El de VB en Pastebin

El de C# en Pastebin

El de VB en mi blog

El de C# en mi blog

elGuille_luna300x200

Código de cómo instanciar una clase a partir del contenido de una cadena (Visual Basic)

January 5th, 2013

 

Lo dicho el código completo para Visual Basic del ejemplo comentado en:

Crear un objeto a partir de una cadena con Activator.CreateInstance

 

(el enlace al de C# está en ese mismo artículo)

 

Namespace ActivatorVB

    Public NotInheritable Class CrearClase

        Public Shared Sub Main()

            'CrearClase2.Prueba()

            'Main2()

            Prueba()

            Console.ReadKey()

        End Sub

        Private Shared Sub Prueba()
            ' La clase a crear
            Dim elNombre = "ActivatorVB.Colega"

            crearColega(elNombre)

            ' Esta clase se llama igual,
            ' pero no es del mismo tipo
            ' y no pasaría la prueba de IsAssignableFrom,
            ' aunque no daría error al hacer la conversión
            ' pero no crearía una instancia,
            ' por tanto Activator devolvería nulo,
            ' en realidad el valor nulo lo devuelve
            ' la operación de conversión de tipos
            ' por eso en VB utilizo TryCast en lugar
            ' de DirectCast o CType
            elNombre = "OtroEspacio.Colega"
            crearColega(elNombre)

            ' esta clase se basa en la que queremos crear
            ' aunque esté en otro espacio de nombres
            elNombre = "OtroEspacio.Coleguilla"
            crearColega(elNombre)

            '' Esta no tiene constructor
            'elNombre = "OtroEspacio.Coleguilla2"
            'crearColega(elNombre)


            ' este tipo no existe,
            ' pero debemos usar IsAssignableFrom
            ' para no provocar una excepción
            elNombre = "Activator.Colega"
            crearColega(elNombre)

            ' esta se basa en la nuestra
            ' pero es abstracta (no tiene constructor)
            ' ya dará un error al crearla
            ' aunque pasaría la prueba de IsAssignableFrom
            Try
                elNombre = "ActivatorVB.ColegaAbstracto"
                crearColega(elNombre)

            Catch ex As Exception
                Console.WriteLine("Mrw! (o Ups!) esta clase {0} da error: {1}",
                                  elNombre, ex.Message)

            End Try

        End Sub

        Private Shared Sub crearColega(laClase As String)
            Dim elTipo As Type = Type.GetType(laClase)

            If Not GetType(Colega).IsAssignableFrom(elTipo) Then
                Console.WriteLine("El tipo: {0} no es del tipo adecuado." & vbLf & "",
                                  laClase)
                Return

            End If

            'Dim obj1 As Object = Activator.CreateInstance(elTipo)
            'Dim obj2 As Colega = CType(Activator.CreateInstance(elTipo), Colega)
            'Dim obj3 As Colega = DirectCast(Activator.CreateInstance(elTipo), Colega)

            Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)

            If obj IsNot Nothing Then
                obj.Nombre = "Pepe"
                obj.Email = "pepe@outlook.com"

                Console.WriteLine("Objeto creado correctamente: {0}{1}{2}, {3}",
                                  obj.GetType().Name, vbCrLf,
                                  obj.Nombre, obj.Email)
            Else
                Console.WriteLine("Parece que el tipo: {0} no es del tipo adecuado.",
                                  laClase)
            End If

            Console.WriteLine()
        End Sub

        Sub pruebaDirecta()
            Dim laClase = "ActivatorVB.Colega"

            Dim elTipo As Type = Type.GetType(laClase)

            Dim obj As Colega = TryCast(Activator.CreateInstance(elTipo), Colega)

            If obj IsNot Nothing Then
                obj.Nombre = "Pepe"
                obj.Email = "pepe@outlook.com"
            End If

        End Sub

        Shared Sub Main2()
            Dim laClase = "ActivatorVB.unColega"

            Dim elTipo As Type = Type.GetType(laClase)

            Dim obj As IColega = TryCast(Activator.CreateInstance(elTipo), IColega)

            If obj IsNot Nothing Then
                obj.Nombre = "Pepe"
            End If

        End Sub

    End Class

    Public Interface IColega
        Property Nombre As String
    End Interface

    Public Class unColega
        Implements IColega

        Public Property Nombre As String Implements IColega.Nombre
    End Class

    Public Class Colega
        Public Property Nombre As String
        Public Property Email As String
    End Class

    ' Clase no instanciable
    Public MustInherit Class ColegaAbstracto
        Inherits Colega

        Public Property Edad As Integer
    End Class

End Namespace

Namespace OtroEspacio

    Public Class Colega
        Public Property Nombre As String
        Public Property Edad As Integer
    End Class

    Public Class Coleguilla
        Inherits ActivatorVB.Colega

        Public Property Edad As Integer

    End Class

    ' las clase selladas (sealed / NotInheritable)
    ' se pueden usar, pero siempre deben tener 
    ' un constructor público sin parámetros
    Public NotInheritable Class Coleguilla2
        Inherits ActivatorVB.Colega

        'Protected Sub New()
        'End Sub

        'Private Sub New()
        'End Sub

        Public Property Edad As Integer
    End Class

End Namespace


 

Nos vemos.

Guillermo

P.S. (06/Ene/13)

He actualizado el código que esta entrada la edité desde el editor de WordPress y se hizo un follón, quitando los espacios y el formato… GRRRR!

hay que ver… como es el Format con las llaves…

January 3rd, 2013

 

Pues eso… que si usas Format, en cualquiera de sus encarnaciones: String.Format, StringBuilder.AppendFormat, Console.WriteLine, (incluso la función Format de Visual Basic) etc. y estas usando llaves para indicar parámetros (marcadores de posición que dicen en la documentación), mucho cuidado con que la cadena que estás formateando no tenga también llaves, porque entonces la hemos "pifiao".

 

El problema

 

Por ejemplo, si tienes esta cadena (o este código XAML):

 

Dim xaml = <StackPanel Style="{StaticResource spImagen}">
               <Image Source="ms-appx:///contenido/img/{0}"
                   Style="{StaticResource Imagen}"/>
           </StackPanel>

 

Y pretendes usarlo con algo como esto:

VB:

 

sb.AppendFormat(xaml.ToString & "{1}",
                sText.Replace(".tif", ".jpg"), vbCrLf)

 

C#:

Nota:

En C# no se puede usar el contenido XML directamente, pero no es complicado adaptarlo, eso sí, es algo más laborioso que en Visual Basic, todo hay que decirlo. Pero para este caso que es poco código lo pasaremos por alto ;-)

 

string xaml = @"<StackPanel Style=""{StaticResource spImagen}"">
                    <Image Source=""ms-appx:///contenido/img/{0}""
                        Style=""{StaticResource Imagen}""/>
                </StackPanel>";

sb.AppendFormat(xaml + "\n",
                sText.Replace(".tif", ".jpg"));

 

Si usamos este código (ya sea con VB o con C#) el compilador producirá una excepción de formato no válido (FormatException) y es porque el "formateador" se encuentra con llaves (de inicio y/o de cierre) que no tienen un valor numérico asignado.

 

FormatException

Figura 1. El error en la aplicación de C# (es el mismo que en VB)

 

 

La solución

 

Pues… quitar las cadenas con las llaves que no sean "marcadores" y agregarlas en el propio AppendFormat:

 

VB:

 

Dim xaml = <Paragraph TextAlignment="Center">
               <InlineUIContainer>
                   <StackPanel Style="{0}">
                       <Image Source="ms-appx:///contenido/img/{1}"
                           Style="{2}"/>
                   </StackPanel>
               </InlineUIContainer>
           </Paragraph>

sb.AppendFormat(xaml.ToString & "{3}",
                "{StaticResource spImagen}",
                sText.Replace(".tif", ".jpg"),
                "{StaticResource Imagen}", vbCrLf)

 

C#:

 

string xaml = @"<StackPanel Style=""{0}"">
                    <Image Source=""ms-appx:///contenido/img/{1}""
                        Style=""{2}""/>
                </StackPanel>";

sb.AppendFormat(xaml + "\n","{StaticResource spImagen}",
                sText.Replace(".tif", ".jpg"), "{StaticResource Imagen}");

 

Es decir, ponemos para cada una de las "llaves fatídicas" una cadena con un marcador (de posición) y ponemos esas cadena (con sus llaves) en los parámetros de la función Format y asunto solucionado.

 

Nota del Guille:

Mira que esto mismo ya me pasó ayer (o hace un puñao de horas) y después me volvió a pasar, pero he estado un buen rato dándole vueltas al tema (se me olvidó que volví a cambiar el código XAML) y no daba con el dichoso error… eso me pasa por dos razones:

1.- no tener buena memoria, no dormir lo suficiente, ser más cabezón que el Visual Studio

2.- no usar suficientes Try/catch para "acorralar" el error…

En fin… lo importante es que he dado con el error y que te lo cuento por si te pasa a ti.

 

Si todo va bien, ya mismo te explicaré para qué estoy usando ese código (el de Visual Basic, ya que el de C# sólo lo he escrito para aquellos que les gustan los puntos y comas)…

 

Lo dicho, espero que te pueda ser de ayuda… esa siempre es la intención ;-)

 

Nos vemos.

Guillermo

Presuntamente algunas funciones de Microsoft.VisualBasic no son compatibles con las aplicaciones para Windows Store

January 1st, 2013

 

Pues eso… que en un proyecto DLL (biblioteca de clases) para Windows Store no me ha reconocido ciertas funciones de manipulación de cadenas que están definidas en el módulo Strings del espacio de nombres Microsoft.VisualBasic (que es como si dijéramos el "runtime" de Visual Basic para .NET).

Lo de presuntamente es por si he hecho algo mal y por eso no me reconocía algunas funciones… al menos en un proyecto del tipo biblioteca de clases (class library), en la figura 1 tienes las cosas que se pueden usar de ese módulo con las funciones de cadena.

 

Microsoft.VisualBasic.Strings en app store class library
Figura 1. Las funciones que muestra Strings en un proyecto del tipo Class library para Windows store

 

Nota:
En una aplicación para Windows Store (no una librería de clases) también muestra los mismos métodos. En una aplicación/librería para el escritorio (incluso de Windows 8) muestra las que estamos habituados a usar.

 

Así que… me las he tenido que fabricar, al menos las que me han hecho falta para este caso concreto, que no es otro que una actualización de mi librería (perdón, biblioteca) para colorear código: gsColorearCodigo.

Aquí te dejo un módulo de Visual Basic para hacer las conversiones… lo mismo ya tenía algo publicado sobre algo parecido, pero…

 

'------------------------------------------------------------------------------
' VBStrings                                                         (01/Ene/13)
'
' Para simular las funciones de cadena de Visual Basic
' ya que parece que el runtime para Windows Store no incluye algunas funciones
'
' ©Guillermo 'guille' Som, 2013
'------------------------------------------------------------------------------
Option Strict On
Option Infer On


Public Module VBStrings
    ''' <summary>
    ''' Devuelve la posición (en base 1) de cadena1 en cadena2
    ''' </summary>
    ''' <remarks></remarks>
    Public Function InStr(cadena1 As String, cadena2 As String) As Integer
        Try
            Dim i = cadena1.IndexOf(cadena2)
            Return i + 1
        Catch ex As Exception
            Return 0
        End Try
    End Function


    ''' <summary>
    ''' Devuelve la posición en base 1 de cadena1 en cadena2
    ''' se comprueba a partir de la posición desde
    ''' </summary>
    ''' <remarks></remarks>
    Public Function InStr(desde As Integer, cadena1 As String, cadena2 As String) As Integer
        Try
            Dim i = cadena1.IndexOf(cadena2, desde - 1)
            Return i + 1
        Catch ex As Exception
            Return 0
        End Try
    End Function

    ''' <summary>
    ''' Devuelve una cadena con los cuantos primeros caracteres de cadena1
    ''' El primer caracter es el 1 y se devuelve en base 1
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Left(cadena1 As String, cuantos As Integer) As String
        If String.IsNullOrWhiteSpace(cadena1) OrElse cuantos < 1 Then
            Return ""
        End If

        Dim s = cadena1.Substring(0, cuantos - 1)
        Return s
    End Function

    ''' <summary>
    ''' Devuelve la cantidad de caracteres que tiene cadena1
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Len(cadena1 As String) As Integer
        If String.IsNullOrWhiteSpace(cadena1) Then
            Return 0
        End If

        Return cadena1.Length
    End Function

    ''' <summary>
    ''' Devuelve una cadena nueva después de quitarle
    ''' los espacios del principio y del final
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Trim(cadena1 As String) As String
        If String.IsNullOrWhiteSpace(cadena1) Then
            Return ""
        End If

        Return cadena1.Trim()
    End Function

    ''' <summary>
    ''' Devuelve una cadena a partir de la posición desde
    ''' La pirmera posición es la 1
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Mid(cadena1 As String, desde As Integer) As String
        If String.IsNullOrWhiteSpace(cadena1) OrElse desde < 1 Then
            Return ""
        End If

        Return cadena1.Substring(desde - 1)
    End Function

    ''' <summary>
    ''' Devuelve una cadena con cuantos caracteres de cadena1 
    '''  a partir de desde
    ''' La primera posición es la 1
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Mid(cadena1 As String, desde As Integer, 
                                            cuantos As Integer) As String
        If String.IsNullOrWhiteSpace(cadena1) OrElse desde < 1 Then
            Return ""
        End If

        Return cadena1.Substring(desde - 1, cuantos)
    End Function

    ''' <summary>
    ''' Trocea una cadena usando los caracteres indicados
    ''' </summary>
    ''' <remarks></remarks>
    Public Function Split(cadena1 As String, caracteres As String) As String()
        Return cadena1.Split(caracteres.ToCharArray, 
                              StringSplitOptions.RemoveEmptyEntries)
    End Function
End Module


 

Espero que te sea de utilidad.

 

Nos vemos.

Guillermo

%d bloggers like this: