Etiqueta: Visual Studio

Posts relacionados con el entorno de desarrollo (IDE) de Visual Studio

  • Mostrar un menú contextual (aplicación para Windows Store)

    Pues eso… voy a seguir mostrándote algunas de las cosas que voy aprendiendo mientras voy creando mi primera aplicación para la tienda de Windows. En esta ocasión lo que te voy a explicar es cómo mostrar un menú contextual (por llamarlo de alguna forma) al hacer Click en un botón.

    Para mostrar el menú contextual vamos a usar las clases PopupMenu y UICommand que están definidas en el espacio de nombres Windows.UI.Popups, que es el mismo en el que está definida la clase MessageDialog que ya vimos que sirve para mostrar mensaje "al estilo Windows 8".

    Seguiremos con el código que vimos por última vez el 7 de diciembre en: Saber cuando la aplicación está en modo Snapped y actuar en consecuencia.

    También usaremos el código para actualizar periódicamente el icono de la aplicación que ya vimos en Actualizar cada minuto el icono (tile) de una aplicación de Windows Store hasta la hora indicada, es decir el método asignarTiles, por tanto ese método no te lo mostraré en este artículo, más que nada para no repetir el código.

    Lo primero que haremos es añadir un botón a la barra de la aplicación (AppBar) y le asignaremos el siguiente estilo: (el cual guardaremos en el fichero StandardStyles.xaml junto con el de los otros botones)

    <Style x:Key="LangAppBarButtonStyle" TargetType="ButtonBase" 
           BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="LangAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Idioma"/>
        <Setter Property="Content" Value="&#xE128;"/>
    </Style>
    

    Ahora añadimos el código XAML del botón, éste lo pondremos en el segundo StackPanel, es decir, junto con el botón About:

    <Button x:Name="buttonLang" Click="buttonLang_Click"
            Style="{StaticResource LangAppBarButtonStyle}" />
    
    

    Una vez hecho esto, nuestra barra de botones tendrá la apariencia mostrada en la figura 1:

    menucontextual_01

    Figura 1. La barra de botones de la aplicación reloj

    Después de ver la captura sabrás que el nombre del botón es buttonLang y el método del evento Click será: buttonLang_Click.

    Veamos el código de ese método de evento, además de otro método que usaremos para saber cuál es el menú elegido (ese código, así como el código para mostrar el menú contextual por medio de la clase PopupMenu está sacado de un ejemplo del SDK de Windows: Context menu sample).

    También usaremos una serie de variables para los textos a mostrar en las opciones del menú y otra más para asignarle el valor que corresponda al idioma seleccionado en ese menú contextual.

    Lo que no veremos (por ahora) son un par de métodos en los que se asignan las cadenas a los controles con el texto del idioma que hemos elegido, ya que para hacer eso antes tenemos que añadir una serie de recursos con los textos en español e inglés que serán los dos idiomas soportados por esta aplicación.

    Lo que me interesa es que sepas cómo mostrar ese menú contextual, cómo interceptar el menú seleccionado y actuar en consecuencia.

    En este ejemplo concreto los métodos usados para hacer la asignación que corresponde según la opción elegida, los crearemos de forma anónima, es decir, será una expresión lambda en lugar de un método de evento real, ahora lo veremos en el código.

    Empecemos por las declaraciones de las variables y el método getElementRect que es el que se encarga de indicar qué opción del menú es la que se ha elegido (si es que se ha elegido alguna).

    VB:

    ''' <summary>El idioma a usar</summary>
    Private culLanguage As String = "es"
    
    ''' <summary>Contiene el texto para Español</summary>
    Private culEs As String = "Español"
    ''' <summary>Contiene el texto para Inglés</summary>
    Private culEn As String = "Inglés"
    
    
    ''' <summary>
    ''' Para saber el menú elegido
    ''' </summary>
    ''' <remarks>
    ''' Del ejemplo de la SDK: Context menu sample
    ''' </remarks>
    Private Function getElementRect(element As FrameworkElement) As Rect
        Dim buttonTransform As GeneralTransform = element.TransformToVisual(Nothing)
        Dim point As Point = buttonTransform.TransformPoint(New Point)
        Return New Rect(point, New Size(element.ActualWidth, element.ActualHeight))
    End Function
    

     

    C#:

    /// <summary>El idioma a usar</summary>
    private string culLanguage = "es";
    
    /// <summary>Contiene el texto para Español</summary>
    private string culEs = "Español";
    /// <summary>Contiene el texto para Inglés</summary>
    private string culEn = "Inglés";
    
    
    private static Rect getElementRect(FrameworkElement element)
    {
        GeneralTransform buttonTransform = element.TransformToVisual(null);
        Point point = buttonTransform.TransformPoint(new Point());
        return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
    }
    

    Ahora le toca el turno al método del evento clic del botón de idioma.

    Empezaremos creando una variable del tipo PopupMenu a la que le añadimos dos opciones (menús o comandos). La forma de añadirlo es mediante el método Add de la propiedad/colección Commands, Lo que espera ese método es un objeto del tipo UICommand. Así que, lo creamos directamente en la asignación, y en la sobrecarga que estamos utilizando los parámetros que el constructor de los comandos espera recibir son: el texto a mostrar y la dirección (puntero) de un método que cumpla con el delegado UICommandInvokedHandler.

    Como ya te comenté antes, el método para ese segundo parámetro lo vamos a definir directamente, es decir, como un método anónimo o función lambda. Y en ese "método" lo que haremos es simplemente asignar un valor a una variable para saber qué idioma usar. Que el usuario elige el primer menú, asignamos "es", que elige el segundo, asignamos "en".

    Veamos el código del método buttonLang_Click tanto para Visual Basic como para C#:

    VB:

    ''' <summary>
    ''' Al pulsar en el botón, mostrar el menú contextual
    ''' En otros casos se puede usar el evento RightTapped
    ''' </summary>
    Private Async Sub buttonLang_Click(sender As Object, e As RoutedEventArgs)
        Dim menu = New PopupMenu
        Dim sLang As String = "es"
    
        menu.Commands.Add(New UICommand(culEs, Sub(command) sLang = "es"))
        menu.Commands.Add(New UICommand(culEn, Sub(command) sLang = "en"))
    
        Dim chosenCommand = Await menu.ShowForSelectionAsync(
                                    Me.getElementRect(TryCast(sender, FrameworkElement)))
    
        ' The command is null if no command was invoked.
        If chosenCommand IsNot Nothing Then
    
            culLanguage = sLang
    
            'guardarConfig()
    
            'cambiarIdioma()
    
            '' Eliminar las notificaciones anteriores y usar nuevas
            '' para que se utilice el nuevo idioma indicado
            'borrarTileNotifications()
    
            asignarTiles()
    
        End If
    End Sub
    
    

     

    C#:

    /// <summary>
    /// Al pulsar en el botón, mostrar el menú contextual
    /// En otros casos se puede usar el evento RightTapped
    /// </summary>
    private async void buttonLang_Click(object sender, RoutedEventArgs e)
    {
        var menu = new PopupMenu();
        string sLang = "es";
    
        menu.Commands.Add(new UICommand(culEs, (command) => sLang = "es"));
        menu.Commands.Add(new UICommand(culEn, (command) => sLang = "en"));
    
        var chosenCommand = await menu.ShowForSelectionAsync(
                                        getElementRect((FrameworkElement)sender));
    
    
        // The command is null if no command was invoked.
        if (chosenCommand != null)
        {
            culLanguage = sLang;
    
            //guardarConfig();
    
            //cambiarIdioma();
    
            //' Eliminar las notificaciones anteriores y usar nuevas
            //' para que se utilice el nuevo idioma indicado
            //borrarTileNotifications();
    
            asignarTiles();
        }
    }
    

     

    Y una vez que tenemos todo esto, si iniciamos la aplicación y pulsamos en la pantalla con el botón derecho (o secundario) del ratón (o mouse) veremos las dos opciones mostradas por encima del botón, tal como podemos apreciar en la figura 2:

    menucontextual_02

    Figura 2: La aplicación en funcionamiento dentro del emulador / simulador de un Windows 8 en un tablet.

     

    En otra ocasión veremos el código de guardar (y leer) la configuración y un par de detalles a tener en cuenta ya que (como estarás comprobando) casi todos los métodos que utilizamos en las aplicaciones para la tienda de Windows son asíncronos y en ocasiones hay que "esperar" a que uno de esos métodos termine antes de seguir con el siguiente.

    También veremos cómo cambiar los textos según el idioma elegido y por supuesto el método ese para borrar las notificaciones que hubiese pendientes de mostrar, ya que al cambiar el idioma el texto de los días y meses se muestran distintos en español que en inglés.

    Ah, y el código de asignarTiles recuerda que el de VB ya estaba publicado en Actualizar cada minuto el icono (tile) de una aplicación de Windows Store hasta la hora indicada y ahora ya está también publicado el de C#.

     

    Y ya que tenemos ese método para que "automatice" las notificaciones en el icono (tile) de la aplicación, hay que modificar el código que ya tenemos, con idea de que se utilice ese nuevo código.

    Esos cambios te los mostraré en la próxima ocasión, cuando te muestre el código de borrarTileNotifications.

    ¡Hasta entonces!

     

    Más abajo tienes los enlaces al resto de artículos de esta serie.

    ¡Espero que te sean de utilidad!

     

    Nos vemos.

    Guillermo

     

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

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

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

    Pues eso, un titulo más largo que el del artículo que publiqué hace unos días:
    Actualizar el icono (tile) de una aplicación de Windows Store,
    en el que te contaré cómo indicarle a Windows que actualice lo que se muestra en el icono de la aplicación (tile), pero incluso cuando dicha aplicación ya está cerrada, además esa actualización se hará cada "x" tiempo y hasta cuando tu digas (la cantidad de minutos u horas que quieras que duren esas actualizaciones).

    El código te lo mostraré primero para Visual Basic y cuando lo tenga listo en C# lo agregaré a esta misma "página" (con fecha del domingo 23 de diciembre de 2012 ya está el código de C#, pero lo he puesto al final del todo y sin usar el fondo negro).

    Dicho esto, crea una nueva aplicación para Windows Store (la más básica es suficiente).

    En el código del evento OnNavigatedTo de MainPage añade esta llamada al método que hará todo el trabajo: asignarTiles.

     

    Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
        asignarTiles()
    End Sub
    
    

     

    Y ahora viene cómo hacer esto de modificar (si quieres, llámalo notificar) el contenido del icono de la aplicación (conocido como tile por los que ponen nombres a estas cosas nuevas que van saliendo). Pero para hacer esa modificación no tendremos que usar ningún temporizador (timer) simplemente usaremos una serie de asignaciones a un objeto del tipo TileUpdater creado a través del método CreateTileUpdaterForApplication de la clase TileUpdateManager a través del método AddToSchedule de forma que le digamos lo que se mostrará en el icono (ya sea el grande -ahora hablamos de ese icono- o el pequeño) y, lo más importante, cuándo se mostrará.

    Nota:

    Este código está adaptado del ejemplo Clock Tile de Dave Smits.

    Te dejo el código y espero que lo comprendas… si tienes dudas pregunta en los comentarios de este mismo post… eso si antes no te lo he explicado con más detalle.

    Recuerda que el cuadro del código puede tener scroll y no lo veas al completo…

     

    Lo que sí (o también, si ya te he explicado lo que te iba a explicar) te voy a decir que al crear un nuevo proyecto para Windows Store (al menos del más elemental) el icono ancho no estará y por tanto el icono (o tile) de la aplicación se mostrará en el formato cuadrado (pequeño). Esa imagen es la que se llama Logo.png en la carpeta Assets y su tamaño es de 150×150. Para tener el logo de tamaño ancho, necesitarás una imagen de 310×150 a la que tendrás que darle el nombre WideLogo.png. Si no haces esto, este ejemplo no compilará.

     

    ''' <summary>
    ''' Crear la lista de notificaciones al icono (tile)
    ''' Se crearán los necesarios para las próximas 4 horas
    ''' notificando cada minuto
    ''' </summary>
    ''' <remarks>
    ''' Basado en el código de Clock Tile de Dave Smits
    ''' </remarks>
    Private Sub asignarTiles()
        Dim tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication()
    
        Dim now As DateTime = DateTime.Now
        ' aquí está el truco... crear tiles para un buen rato...
        Dim planTill As DateTime = now.AddMinutes(30) ' .AddHours(4) ' .AddMinutes(15)
    
        Dim updateTime = New DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0).AddMinutes(1)
    
        Dim plannedUpdated = tileUpdater.GetScheduledTileNotifications()
        If plannedUpdated.Count > 0 Then
            ' Asignar a updatetime la última fecha de la colección (o la que sea mayor)
            updateTime = plannedUpdated.Select(Function(x) x.DeliveryTime.DateTime).Union({updateTime}).Max()
        End If
        Const sXml = "<tile><visual>" &
                            "<binding template=""TileSquareText02""><text id=""1"">{0}</text><text id=""2"">{2}</text></binding>" &
                            "<binding template=""TileWideText01""><text id=""1"">{0}</text><text id=""2"">{1}</text>" &
                                "<text id=""3""></text><text id=""4""></text><text id=""5""></text></binding>" &
                    "</visual></tile>"
    
        Dim tileXmlNow = String.Format(sXml, now.ToString("HH:mm"),
                                             now.ToString("dddd, dd MMMM yyyy"),
                                             now.ToString("dd/MMM/yyyy"))
        Dim documentNow As New XmlDocument()
        documentNow.LoadXml(tileXmlNow)
    
        tileUpdater.Update(New TileNotification(documentNow) With {.ExpirationTime = now.AddMinutes(1)})
    
        Dim startPlanning = updateTime
        While startPlanning < planTill
    
            Try
                Dim tileXml = String.Format(sXml, startPlanning.ToString("HH:mm"),
                                                  startPlanning.ToString("dddd, dd MMMM yyyy"),
                                                  startPlanning.ToString("dd/MMM/yyyy"))
                Dim document As New XmlDocument()
                document.LoadXml(tileXml)
    
                Dim deliveryTime = New DateTimeOffset(startPlanning)
                Dim scheduledNotification = New ScheduledTileNotification(document, deliveryTime)
                scheduledNotification.ExpirationTime = startPlanning.AddMinutes(1)
    
                tileUpdater.AddToSchedule(scheduledNotification)
    
            Catch e As Exception
                Debug.WriteLine("exception: " & e.Message)
    
            End Try
    
            startPlanning = startPlanning.AddMinutes(1)
        End While
    
    End Sub
    

     

    Bueno, y esto es todo… si cierras la aplicación (sólo te mostrará una pantalla negra) verás que el icono se sigue actualizando y cuando pase el tiempo indicado (en este ejemplo es de 30 minutos) se asignará el texto original.

    El tiempo total en el que se estarán mostrando las notificaciones es el indicado en la definición de la variable PlanTill:

    Dim planTill As DateTime = now.AddMinutes(30)

     

    Por cierto, creo que el número máximo de "actualizaciones / notificaciones" al tile de la aplicación es de 4096, así que… lo mismo habría que tener en cuenta que no se sobrepase esa cantidad… (tendré que probar si da error o no al pasar de esa cantidad).

     

    Espero que te sea de utilidad.

    Nos vemos.

    Guillermo

    P.S.

    El "estilo" del código es el que Visual Studio 2012 utiliza cuando está en modo "Dark"… a mi no me acaba de convencer…

     

    Nota del 23/Dic/12: el código de C#

    /// <summary>
    /// Crear la lista de notificaciones al icono (tile)
    /// Se crearán los necesarios para las próximas 4 horas
    /// notificando cada minuto
    /// </summary>
    /// <remarks>
    /// Basado en el código de Clock Tile de Dave Smits
    /// </remarks>
    private void asignarTiles()
    {
        var tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();
    
        DateTime now = DateTime.Now;
        // aquí está el truco... crear tiles para un buen rato...
        DateTime planTill = now.AddMinutes(30); // .AddHours(4)
    
        var updateTime = new DateTime(now.Year, now.Month, now.Day, 
                                      now.Hour, now.Minute, 0).AddMinutes(1);
    
        var plannedUpdated = tileUpdater.GetScheduledTileNotifications();
        if (plannedUpdated.Count > 0)
        {
            // Asignar a updatetime la última fecha de la colección (o la que sea mayor)
            updateTime = plannedUpdated.Select(
                x => x.DeliveryTime.DateTime).Union(new[] { updateTime }).Max();
        }
    
        const string sXml = 
            @"<tile><visual>
                <binding template=""TileSquareText02"">
                    <text id=""1"">{0}</text><text id=""2"">{2}</text>
                </binding>
                <binding template=""TileWideText01"">
                    <text id=""1"">{0}</text><text id=""2"">{1}</text>
                    <text id=""3""></text><text id=""4""></text><text id=""5""></text>
                </binding>
            </visual></tile>";
    
        var tileXmlNow = String.Format(sXml, now.ToString("HH:mm"),
                                             now.ToString("dddd, dd MMMM yyyy"),
                                             now.ToString("dd/MMM/yyyy"));
        XmlDocument documentNow = new XmlDocument();
        documentNow.LoadXml(tileXmlNow);
    
        tileUpdater.Update(new TileNotification(documentNow) { ExpirationTime = now.AddMinutes(1) });
    
        // Este bucle while se puede cambiar por este for:
        // for (var startPlanning = updateTime; startPlanning < planTill; startPlanning = startPlanning.AddMinutes(1))
        var startPlanning = updateTime;
        while (startPlanning < planTill)
        {
            try
            {
                var tileXml = String.Format(sXml, startPlanning.ToString("HH:mm"),
                                                  startPlanning.ToString("dddd, dd MMMM yyyy"),
                                                  startPlanning.ToString("dd/MMM/yyyy"));
                XmlDocument document = new XmlDocument();
                document.LoadXml(tileXml);
    
                var deliveryTime = new DateTimeOffset(startPlanning);
                var scheduledNotification = new ScheduledTileNotification(document, deliveryTime);
                scheduledNotification.ExpirationTime = startPlanning.AddMinutes(1);
    
                tileUpdater.AddToSchedule(scheduledNotification);
            }
            catch (Exception e)
            {
                Debug.WriteLine("exception: " + e.Message);
            }
            startPlanning = startPlanning.AddMinutes(1);
        }
    }
    

     

  • Error al usar inadecuadamente el XAML

    Pues eso… que no es un error que sepas por dónde "meterle mano", o al menos eso me pasó a mí cuando me ocurrió. Me estoy refiriendo a un "Break" que se toma el Debugger de Visual Studio 2012 concretamente cuando llega a la línea que contiene la siguiente cadena: DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION.

    El error ocurre porque hay código XAML que no es correcto.
    Y cuando digo que "no es correcto" no me refiero a que haya un fallo de que falte un cierre de elemento o algo así, me refiero a que, por ejemplo, estás usando código Xaml en el sitio inadecuado.

    Te explico uno de los casos en los que me ocurrió a mí.

    En la aplicación del Reloj para Windows 8 que estoy haciendo, y la que te he ido mostrando paso a paso, he creado unas cadenas para usar en el código XAML, concretamente para el ToolTip, etcétera.
    Esas cadenas las utilizo (o esa era la intención inicial) para no tener que andar repitiendo el mismo texto en varios sitios y así poder modificarlas una sola vez y se usará esa modificación en las distintas partes de la aplicación en las que la utilizo.
    Pues bien, las cadenas las tengo definidas con este código:

    <x:String x:Key="AboutClock">Un reloj…</x:String>

    Y para usarla lo hago de esta forma (en el código XAML, en este ejemplo de la página principal):

    ToolTipService.ToolTip="{StaticResource AboutClock}"

    El problema que tuve es que al usar esto último en la definición del estilo de un botón en el que quiero que se muestre un ToolTip me daba ese error.

    La definición de la cadena está en App.xaml y el estilo en StandardStyles.xaml y lo usaba de esta forma:

    <Setter Property="ToolTipService.ToolTip" Value="{StaticResource AboutClock}" />

     

    Pues bien, eso no funciona (a mí no me funciona) y lo tengo que definir directamente en el estilo (de esa forma al aplicar el estilo al botón también se asigna el ToolTip).

     

    Bueno, pues esto es todo… espero que te sea de utilidad.

     

    Nos vemos.
    Guillermo