Categoría: cosas técnicas

Posts relacionados con la programación o manejo o configuración de los oredenadores (computadores)

  • Ejemplo de ListView y equivalencia a subitems

     

    Pues eso… si has intentado usar el control ListView en una aplicación de tipo WPF (Windows Presentation Foundation), seguramente te habrás dado cuenta que no tiene nada que ver con el control de las aplicaciones de Windows Forms (WinForms), sobre todo a la hora de agregar y recuperar los elementos, de hecho la propiedad SubItems no existe.
    Sí, es más complicado.

     

    WPF = .NET + XAML (Windows Presentation Foundation) WPF = .NET + XAML

     

    Hace unos años (más de 5) ya te comentaba algunos de esos cambios en los puntos 9, 10 y 11 de Equivalencias entre las clases de WPF y las de .NET (1).

    Aquí te voy a explicar lo que yo creo que se debe hacer para acceder a los elementos de un ListView, que en realidad no ha cambiado mucho, pero lo que sí ha cambiado es la forma de acceder a los "sub elementos", es decir ya no existe una propiedad / colección subitems que es la forma que tenemos en WinForms para acceder a las diferentes columnas de cada elemento.

    Cómo se hace en WinForms

    Por ejemplo, si tenemos un ListView con más de una columna, la forma de acceder a la segunda columna de la primera fila es: LisView1.Items(0).SubItems(1).Text

    Pero eso no lo podemos hacer en WPF ya que SubItems no existe.

    Cómo se hace en WPF

    Los controles ListView de WPF para definir columnas deben tener un control del tipo GridView como contenido de la propiedad ListView.View.

    Ese GridView define tantas columnas como sean necesarias, esas columnas son del tipo GridViewColumn y en la definición de las propiedades de ese objeto-columna, concretamente en DisplayMemberBinding es donde está "la magia" de cómo acceder a cada uno de los "sub-elementos" de cada fila de datos.

    Por ejemplo, si tenemos un formulario tal como el mostrado en la figura 1, ese ListView lo podemos definir tal como vemos en el listado 1.

    El formulario WPF
    Figura 1. El formulario de WPF

     

    Este es el código XAML para crear ese ListView (más abajo te mostraré el código completo):

    <ListView Name="lvDatos" Margin="0" MinHeight="240"
          VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
    <ListView.View>
        <GridView AllowsColumnReorder="True">
            <!-- Utilizo valores en Binding distintos a los nombres de las cabeceras
                 para que se vea que no hay relación entre el nombre del Header
                 y el nombre de Binding -->
                <GridViewColumn Header="Nombre y apellidos" 
                                DisplayMemberBinding="{Binding Nombre}" Width="150"/>
                <GridViewColumn Header="Correo" 
                                DisplayMemberBinding="{Binding email}" Width="190"/>
                <GridViewColumn Header="How old" 
                                DisplayMemberBinding="{Binding Edad}" Width="70"/>
        </GridView>
    </ListView.View>
    </ListView>
    

    Listado 1. El código XAML del control ListView

    Como puedes comprobar en cada columna hay tres asignaciones a otras tantas propiedades:

    Width que es para indicar el ancho, Header que es para indicr el texto a mostrar en la cabecera de la columna y DisplayMemberBinding que es la propiedad que enlaza con el dato a mostrarse en esa columna.

    Como puedes comprobar el valor que le estoy asignando a esa propiedad "enlazada" es: {Binding Nombre}.

    ¿Qué significa eso?

    Eso significa que lo que se muestre en esa columna será un "campo" (o propiedad) del dato que se agregue a una fila.

    Y tu dirás… ¡pos vale! pero no me entero…

    Eso quiere decir que cada dato que añadas a cada fila del ListView debe ser un objeto que defina una propiedad con el mismo nombre que indicamos después de Binding. En ese ejemplo, será una propiedad llamada Nombre.

    Es decir, en WPF el ListView no funciona "tan fácil" como lo hace en WinForms, en WPF los elementos agregados deben ser (o deberían ser) de un tipo concreto y las columnas (o cabeceras) deben hacer referencia a las propiedades que queremos mostrar.

    En el ejemplo que he puesto del ListView para WPF podemos usar una clase que defina al menos tres propiedades (no campos públicos) con los nombres usados después de Binding, en este ejemplo esas propiedades deben ser: Nombre, email y Edad.

    Nota:

    Los nombres usados en Binding deben coincidir "exactamente" con los nombres de las propiedades, es decir, no deben diferenciarse en las mayúsculas/minúsculas… ¡ni siquiera en Visual Basic!

    Además deben estar definidos como propiedades, no como métodos ni campos públicos.

     

    Código de cómo agregar y recuperar los elementos

    Veamos cómo agregar elementos al ListView y después veremos cómo recuperar esos elementos.

    Te voy a mostrar dos formas de hacerlo:

    1.- usando tipos anónimos (no es recomendable, pero si sabes lo que haces, es fácil de utilizar, aunque a la hora de recuperar los datos en Visual Basic deberías hacerlo usando Option Strict Off al menos en el fichero de código en el que estés usando el tipo anónimo. En C# tendrás que acceder a ese dato usando dynamic.

    2.- usando un tipo de datos previamente definido con esas tres propiedades.

     

    Agregar datos usando un tipo anónimo

    Para agregar los datos en este ejemplo, voy a usar un bucle que agregue 10 objetos al ListView. Como son objetos de tipo anónimo usamos un tipo anónimo que defina las propiedades que necesitamos.

    VB:

    ' Rellenar el listview con datos
    lvDatos.Items.Clear()
    For i = 1 To 10
    
        ' Asignamos un tipo anónimo
        ' Si las columnas (GridViewColumn) tienen un Binding a las propiedades
        ' los valores se asignarán a cada columna, si no están enlazadas
        ' se agregará el valor de ToString a cada columna (estará el texto repetido)
        lvDatos.Items.Add(New With 
                          {.Nombre = "Nombre anónimo" & i.ToString,
                           .email = "correo" & i.ToString & "@outlook.com",
                           .Edad = 17 + i
                          })
    
    Next
    

    C#:

    // Rellenar el listview con datos
    lvDatos.Items.Clear();
    for (var i = 1; i <= 10; i++)
    {
    
        // Asignamos un tipo anónimo
        // Si las columnas (GridViewColumn) tienen un Binding a las propiedades
        // los valores se asignarán a cada columna, si no están enlazadas
        // se agregará el valor de ToString a cada columna (estará el texto repetido)
        lvDatos.Items.Add(new
        {
            Nombre = "Nombre anónimo" + i.ToString(),
            email = "correo" + i.ToString() + "@outlook.com",
            Edad = 17 + i
        });
    
    }
    

     

    Recuperar los datos usando un tipo anónimo

    La forma de recuperar esos datos es usando una variable de tipo Object en Visual Basic y en el caso de C# esa variable debe ser de tipo dynamic, ya que en ambos lenguajes vamos a usar late-binding o enlace tardío, es decir, hasta que no se ejecute el código no se sabrá si esas propiedades están definidas o no en el objeto recuperado del ListView.

    Es importante que leas y pruebes el comentario que te hago al final del código sobre qué ocurriría si quisiéramos acceder a una propiedad que no está definida en el "tipo anónimo".

    VB:

    ' Si tenemos Option Strict Off podemos hacer esto:
    Dim v = lvDatos.SelectedItem
    
    txtMostar.Text = v.Nombre & " (" & v.email & ")"
    
    ' El problema es que queramos acceder a una propiedad que no exista
    ' (esto dará error)
    'txtMostar.Text = v.Nombre & " (" & v.cagonto & ")"
    

    C#:

    // En C# debemos definir la variable como dynamic:
    dynamic v = lvDatos.SelectedItem;
    
    txtMostar.Text = v.Nombre + " (" + v.email + ")";
    
    // El problema es que queramos acceder a una propiedad que no exista
    // (esto dará error)
    //txtMostar.Text = v.Nombre + " (" + v.cagonto + ")";
    

     

    Agregar datos usando un tipo definido (Colega)

    Como podrás comprobar, el código de agregar los datos es muy parecido al anterior, la diferencia está en que después de "NEW" usamos el tipo Colega en vez de no indicar qué tipo de datos estamos usando.

    VB:

    ' Rellenar el listview con datos
    lvDatos.Items.Clear()
    For i = 1 To 10
    
        ' Asignamos un valor de un tipo definido (Colega)
        ' que tiene como mínimo las propiedades que vamos a usar
        ' en el listiView
    
         lvDatos.Items.Add(New Colega With
                          {.Nombre = "Nombre " & i.ToString,
                           .email = "email" & i.ToString & "@outlook.com",
                           .Edad = 17 + i
                          })
    
    Next
    

    C#:

    // Rellenar el listview con datos
    lvDatos.Items.Clear();
    for (var i = 1; i <= 10; i++)
    {
    
        // Asignamos un valor de un tipo definido (Colega)
        // que tiene como mínimo las propiedades que vamos a usar
        // en el listiView
    
        lvDatos.Items.Add(new Colega
        {
            Nombre = "Nombre " + i.ToString(), 
            email = "email" + i.ToString() + "@outlook.com", 
            Edad = 17 + i
        });
    
    }
    

     

    Recuperar los datos usando un tipo definido (Colega)

    En este caso, como estamos usando un tipo definido previamente, lo único que tenemos que hacer a la hora de recuperar el elemento seleccionado es hacer un "cast", es decir, una conversión al tipo Colega, ya que el valor devuelto por SelectedItem es del tipo object, ya que los elementos de un ListView pueden ser de cualquier tipo, incluso tipos mezclados… aunque esto último no lo he probado, pero hacer se puede hacer… otra cosa es hacerlo bien, jeje.

    VB:

    Dim v = TryCast(lvDatos.SelectedItem, Colega)
    
    txtMostar.Text = v.Nombre & " (" & v.email & ")"
    
    ' como ahora usamos un tipo previamente definido
    ' si usamos esto:
    '   txtMostar.Text = v.Nombre & " (" & v.cagonto & ")"
    ' nos dirá que "cagonto" no es un miembro de Colega
    

    C#:

    var v = ((Colega)lvDatos.SelectedItem);
    
    txtMostar.Text = v.Nombre + " (" + v.email + ")";
    
    // como ahora usamos un tipo previamente definido
    // si usamos esto:
    //   txtMostar.Text = v.Nombre + " (" + v.cagonto + ")";
    // nos dirá que "cagonto" no es un miembro de Colega
    

     

    Y esto es todo… espero que te haya aclarado algunos conceptos y dudas… y si quieres saber más sobre los ListView, los GridView y cómo hacer maravillas con el contenido de los ListView, te recomiendo que le eches un vistazo a los enlaces que te he puesto en cada una de esos tipos de datos.

     

    Un poco más abajo tienes el código compelto tanto del XAML como el de Visual Basic y C#.

    Ese código lo he hecho con Visual Studio 2012, pero supongo que será válido en otras versiones anteriores: en el caso de VB desde el VS2008, y en el caso de C# desde Visual Studio 2010.

     

    Nota:

    En ese código hay comentadas un par de pruebas que puedes hacer.

    Entre ellas está la de no asignar ningún valor a la propiedad Binding de los GridViewColumn y asignar cadenas normales a los elementos del ListView, después al ejecutar el programa y al recuperar el contenido del elemento seleccionado verás que… no te lo cuento… así lo tendrás que probar 😉

     

     

    Espero que te sea de utilidad.

    Nos vemos.

    Guillermo

     


    Código Xaml El código Xaml

    En VB usa esta definición de la ventana:

    <Window x:Class="MainWindow"

    En C# usa esta definición de la ventana (o pon el nombre del espacio de nombres del proyecto delante de MainWindows):

    <Window x:Class="WpfListView_cs.MainWindow"

     

    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" 
        Height="380" Width="525">
        <Grid>
            <StackPanel Orientation="Vertical" Margin="10,10">
                <ListView Name="lvDatos" Margin="0" MinHeight="240"
                      VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                      SelectionChanged="lvDatos_SelectionChanged" >
                <ListView.View>
                    <GridView AllowsColumnReorder="True">
                        <!-- Utilizo valores en Binding distintos a los nombres de las cabeceras
                             para que se vea que no hay relación entre el nombre del Header
                             y el nombre de Binding -->
                            <GridViewColumn Header="Nombre y apellidos" 
                                            DisplayMemberBinding="{Binding Nombre}" Width="150"/>
                            <GridViewColumn Header="Correo" 
                                            DisplayMemberBinding="{Binding email}" Width="190"/>
                            <GridViewColumn Header="How old" 
                                            DisplayMemberBinding="{Binding Edad}" Width="70"/>
                    </GridView>
                </ListView.View>
                </ListView>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
                    <Button Content="Rellenar" Click="Button_Click_1" />
                    <Label />
                    <Button Name="btnMostrar" Content="Mostrar seleccionado" 
                            IsEnabled="False" Click="btnMostrar_Click" />
                    <Label />
                    <CheckBox Name="chkAnonimo" IsChecked="True" 
                              Content="Usar tipo anónimo (si no, tipo definido)" />
                </StackPanel>
                <TextBlock Name="txtMostar" Text="Una fila"/>
                <TextBlock Name="txtMostar2" Text="Una fila"/>
            </StackPanel>
        </Grid>
    </Window>
    

     

    Código para Visual Basic.NET (VB.NET) El código para Visual Basic .NET
    '------------------------------------------------------------------------------
    ' Ejemplo de ListView usando WPF para Desktop                       (27/Dic/12)
    ' y cómo acceder a las columnas (sin usar SubItems)
    '
    ' ©Guillermo 'guille' Som, 2012
    '------------------------------------------------------------------------------
    
    Option Strict Off
    Option Infer On
    
    Class MainWindow
    
        Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
            ' Rellenar el listview con datos
            lvDatos.Items.Clear()
            For i = 1 To 10
    
                '' Esto añadirá el mismo texto a todas las columnas
                '' siempre y cuando no hayamos usado ningún Binding
                '' si no, esto no añade nada de nada...
                '.Items.Add("Item número " & i.ToString("00"))
    
    
                If chkAnonimo.IsChecked Then
                    ' Asignamos un tipo anónimo
                    ' Si las columnas (GridViewColumn) tienen un Binding a las propiedades
                    ' los valores se asignarán a cada columna, si no están enlazadas
                    ' se agregará el valor de ToString a cada columna (estará el texto repetido)
                    lvDatos.Items.Add(New With
                                      {.Nombre = "Nombre anónimo" & i.ToString,
                                       .email = "correo" & i.ToString & "@outlook.com",
                                       .Edad = 17 + i
                                      })
    
                Else
                    ' Asignamos un valor de un tipo definido (Colega)
                    ' que tiene como mínimo las propiedades que vamos a usar
                    ' en el listiView
    
                    lvDatos.Items.Add(New Colega With
                                      {.Nombre = "Nombre " & i.ToString,
                                       .email = "email" & i.ToString & "@outlook.com",
                                       .Edad = 17 + i
                                      })
    
                End If
    
            Next
    
        End Sub
    
        Private Sub btnMostrar_Click(sender As Object, e As RoutedEventArgs)
            ' Mostar en el TextBlock el elemento seleccionado
    
            ' Dependiendo de que sea el tipo anónimo o el definido
            ' el valor mostrado será diferente.
            ' En el caso de la clase Colega, si no tenemos sobrecargado el método ToString
            ' mostrará el nombre de la clase: WpfListView_vb.Colega
            txtMostar2.Text = lvDatos.SelectedItem.ToString
    
            If chkAnonimo.IsChecked Then
                ' Si tenemos Option Strict Off podemos hacer esto:
                Dim v = lvDatos.SelectedItem
    
                txtMostar.Text = v.Nombre & " (" & v.email & ")"
    
                ' El problema es que queramos acceder a una propiedad que no exista
                ' (esto dará error)
                'txtMostar.Text = v.Nombre & " (" & v.cagonto & ")"
    
            Else
    
                Dim v = TryCast(lvDatos.SelectedItem, Colega)
    
                txtMostar.Text = v.Nombre & " (" & v.email & ")"
    
                ' como ahora usamos un tipo previamente definido
                ' si usamos esto:
                '   txtMostar.Text = v.Nombre & " (" & v.cagonto & ")"
                ' nos dirá que "cagonto" no es un miembro de Colega
    
            End If
    
        End Sub
    
        Private Sub lvDatos_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
            ' habilitar según haya o no elementos seleccionados
            btnMostrar.IsEnabled = (lvDatos.SelectedItems.Count > 0)
        End Sub
    
    End Class
    
    ''' <summary>
    ''' Tipo Colega, con las popiedades (o campos)
    ''' que tendrán las columnas del ListView.
    ''' Aunque puede tener más propiedades
    ''' aunque no estén enlazadas con las columnas
    ''' </summary>
    Class Colega
        Public Property Nombre As String
        Public Property email As String
        Public Property Edad As Integer
    
        ' Podemos tener más propiedadas
        Public Property Apellidos As String
    End Class
    
    

     

    Código para C Sharp (C#) El código para C#
    //-----------------------------------------------------------------------------
    // Ejemplo de ListView usando WPF para Desktop                      (27/Dic/12)
    // y cómo acceder a las columnas (sin usar SubItems)
    //
    // ©Guillermo 'guille' Som, 2012
    //-----------------------------------------------------------------------------
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace WpfListView_cs
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Button_Click_1(object sender, RoutedEventArgs e)
            {
                // Rellenar el listview con datos
                lvDatos.Items.Clear();
                for (var i = 1; i <= 10; i++)
                {
    
                    //' Esto añadirá el mismo texto a todas las columnas
                    //' siempre y cuando no hayamos usado ningún Binding
                    //' si no, esto no añade nada de nada...
                    //.Items.Add("Item número " + i.ToString("00"));
    
    
                    if ((bool)chkAnonimo.IsChecked)
                    {
                        // Asignamos un tipo anónimo
                        // Si las columnas (GridViewColumn) tienen un Binding a las propiedades
                        // los valores se asignarán a cada columna, si no están enlazadas
                        // se agregará el valor de ToString a cada columna (estará el texto repetido)
                        lvDatos.Items.Add(new
                        {
                            Nombre = "Nombre anónimo" + i.ToString(),
                            email = "correo" + i.ToString() + "@outlook.com",
                            Edad = 17 + i
                        });
    
                    }
                    else
                    {
                        // Asignamos un valor de un tipo definido (Colega)
                        // que tiene como mínimo las propiedades que vamos a usar
                        // en el listiView
    
                        lvDatos.Items.Add(new Colega
                        {
                            Nombre = "Nombre " + i.ToString(), 
                            email = "email" + i.ToString() + "@outlook.com", 
                            Edad = 17 + i
                        });
                    }
                }
    
            }
    
            private void btnMostrar_Click(object sender, RoutedEventArgs e)
            {
                // Mostar en el TextBlock el elemento seleccionado
    
                // Dependiendo de que sea el tipo anónimo o el definido
                // el valor mostrado será diferente.
                // En el caso de la clase Colega, si no tenemos sobrecargado el método ToString
                // mostrará el nombre de la clase: WpfListView_vb.Colega
                txtMostar2.Text = lvDatos.SelectedItem.ToString();
    
                if ((bool)chkAnonimo.IsChecked)
                {
                    // En C# debemos definir la variable como dynamic:
                    dynamic v = lvDatos.SelectedItem;
    
                    txtMostar.Text = v.Nombre + " (" + v.email + ")";
    
                    // El problema es que queramos acceder a una propiedad que no exista
                    // (esto dará error)
                    //txtMostar.Text = v.Nombre + " (" + v.cagonto + ")";
    
                }
                else
                {
                    
                    var v = ((Colega)lvDatos.SelectedItem);
    
                    txtMostar.Text = v.Nombre + " (" + v.email + ")";
    
                    // como ahora usamos un tipo previamente definido
                    // si usamos esto:
                    //   txtMostar.Text = v.Nombre + " (" + v.cagonto + ")";
                    // nos dirá que "cagonto" no es un miembro de Colega
    
                }
            }
    
            private void lvDatos_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                // habilitar según haya o no elementos seleccionados
                btnMostrar.IsEnabled = (lvDatos.SelectedItems.Count > 0);
            }        
    
        }
    
        /// <summary>
        /// Tipo Colega, con las popiedades (o campos)
        /// que tendrán las columnas del ListView.
        /// Aunque puede tener más propiedades
        /// aunque no estén enlazadas con las columnas
        /// </summary>
        class Colega
        {
            public string Nombre { get; set; }
            public string email { get; set; }
            public int Edad { get; set; }
    
            // Podemos tener más propiedadas
            public string Apellidos { get; set; }
        }
    }
    
    

    Espacios de nombres usados en el código de este artículo:

    System.Windows

    System.Windows.Controls

     

     

    El artículo original está en mi sitio, este de mi blog sólo es por si quieres hacer comentarios sobre lo aquí explicado.

  • 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

  • Saber la versión de una aplicación de Windows Store

     

    Pues eso… quería mostrar la versión de mi aplicación en la página de "acerca de" y al buscar en Google (aparte de ver que tiene un doodle para el 13º Baktún de los mayas) me he encontrado con un artículo/truco de Caio Proiete en el que lo muestra para C# y JavaScript, y como yo suelo usar Visual Basic como lenguaje de programación, pues… lo he convertido, que no es que sea muy complicado hacerlo (lo de convertir de C# a VB) así que… aquí lo tienes tal y como yo lo estoy usando.

     

    Dim version = Windows.ApplicationModel.Package.Current.Id.Version
    Dim sVersion = String.Format("{0}.{1}.{2}.{3}",
                                 version.Major, version.Minor,
                                 version.Build, version.Revision)
    

     

    Espero que te sea de utilidad.

     

    Nos vemos.
    Guillermo