Etiqueta: ComoNET

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

     

    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

    …

  • Como usar GetFileFromApplicationUriAsync (para que no se te cuelgue la aplicación)

     

    Pues eso… que este post iba a ser una especie de declaración de «mi» incapacidad a la hora de trabajar de forma asíncrona con los ficheros de una aplicación de Windows Store, y al final será una especie de truco o consejo de cómo hacer las cosas con todo esto de el acceso a ficheros de forma asíncrona (la única que conozco para Windows Store).

    Y la solución no es porque «yo» haya dado con la respuesta, bueno, un poco sí, ya que si no hago un par de búsquedas en Google lo mismo no hubiese dado con la respuesta.

    Y digo «un par» de búsquedas por no decir tres, ya que es complicado algunas veces encontrar respuestas, sobre todo porque la mayoría de las preguntas con respuestas están en inglés y después porque no sabes con certeza cómo «plantear» la búsqueda…

    Y lo curioso es que al tercer intento con esto: «GetFileFromApplicationUriAsync don’t» es cuando ha salido la respuesta, y precisamente en el primer lugar:

     

    getfilefromapplicationuriasync don t  Buscar con Google

     

    Y eso que en la búsqueda anterior lo puse un poco más concreto, en fin… estos buscadores y/o los usuarios de los mismos… ¡habrá que apañarlos! 😉

     

    Te explico de qué va la cosa:

    Estoy haciendo una aplicación para Windows 8 (Windows Store) en la que quiero mostrar el contenido de unos ficheros de textos. Para ello estoy usando una versión adaptada del tipo de proyecto Split App en el que todo el contenido está basado en «bindings», pero ese no es el problema, el problema es que en la clase SampleDataSource estoy haciendo una serie de cambios para que se adapte a lo que yo quiero, y como el texto a mostrar es bastante grande como para «pegarlo» en la propia clase (tal como hacen en el proyecto de ejemplo) me puse a crear un código que leyera el contenido del fichero y lo asignara adecuadamente a una propiedad de la clase usada como «binding» que es la que muestra el texto final.

    Así que… se me ocurrió usar este código (y algún otro con distintas pruebas):

     

    Dim ficUri = New Uri("ms-appx:///contenido/" & fic)
    
    ' aquí se queda colgado (algunas veces)
    ' las veces que pasa de aquí es en modo debug y haciendo un break
    ' (pero no siempre)
    Dim file = Await StorageFile.GetFileFromApplicationUriAsync(ficUri)
    
    ' Aquí también se para, y pasa si hay un breakpoint
    Dim sf = Await file.OpenStreamForReadAsync
    
    Using sr As New StreamReader(sf, Encoding.UTF8, True)
        sBody = sr.ReadToEnd()
    End Using
    
    Return sBody
    
    
    

     

    Y tal como comento en los comentarios (valga la redundancia) cuando estaba en funcionamiento «natural» no pasaba de ahí y se quedaba colgada la aplicación.

    Ya estaba por desistir cuando me ha dado el punto de buscar en Google ya que suponía que no sería cosa mía y que seguramente a alguien más le habrá ocurrido… ¡y así es!

    Nota: antes de buscar en Google ya busqué información en la documentación de Visual Studio, pero no encontré nada, aparte de los ejemplos triviales que suelen poner que que dan por hecho de que prácticamente te lo sabes todo, todo, todo… en fin…

    No me voy a enrollar más de la cuenta y te pongo el código correcto para hacer esa tarea de leer un fichero de forma asíncrona y que no se quede colgada la aplicación de Windows Store.

     

    También te pondré el enlace a esa pregunta y un artículo del autor de la respuesta donde explica porqué pasa eso, no, mejor dicho: «porqué nos pasa eso a los principiantes» del acceso asíncrono, o eso he entendido yo de este párrafo:

    «I think it’s the most-asked question by async newcomers once they’ve learned the basics.»

    Y tiene razón ya que reconozco que soy un newcomer en esto de el acceso asíncrono, al menos para los ficheros en el directorio de la aplicación, ya que antes he estado accediendo de forma asíncrona a otros ficheros (del Local storage) y ha funcionado… en fin…

     

    Dim ficUri = New Uri("ms-appx:///contenido/" & fic)
    
    Dim file = Await StorageFile.GetFileFromApplicationUriAsync(ficUri).AsTask().ConfigureAwait(False)
    
    Dim sf = Await file.OpenStreamForReadAsync().ConfigureAwait(False)
    
    Using sr As New StreamReader(sf, Encoding.UTF8, True)
        sBody = sr.ReadToEnd()
    End Using
    
    
    Return sBody
    

     

    Como puedes ver, la solución es agregar .AsTask().ConfigureAwait(False) al final del método GetFileFromApplicationUriAsync que es el que se encarga de obtener el fichero desde el directorio de la aplicación, y de ConfigureAwait(False) al método OpenStreamForReadAsync que es el que se encarga de convertir dicho fichero en un Stream de lectura.

     

    La explicación del porqué del uso de esos métodos está en el comentario que hizo Nito al preguntarle el «buscador de respuestas» porqué era necesario hacer eso:

    (te lo pego en inglés que la traducción automática como que no me convence)

    Explanation is quite simple: when you use task.Result ortask.Wait() on GUI thread in conjunction with await keyword you’re causing deadlock. This happens because after awaiting code is resumed on the same context it was invoked (in your case – GUI thread). And because GUI thread currently waiting for task to complete (via Result or Wait()) deadlock arises and code after await keyword will never be invoked. ConfigureAwait(false)specifies that current context can be ignored and thus allows your code to complete successfully. More details on this here: http://nitoprograms.blogspot.com/2012/07/dont-block-on-async-code.html

     

    Este es el enlace a la pregunta/respuesta esa que te comentaba y este otro es al artículo de Nito Programming.

     

    Espero que te sea de utilidad.

     

    Nos vemos.

    Guillermo

    …

  • 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.

    …