Introducción:
Pues... hasta ayer, todo lo que he hecho con las aplicaciones para WPF
(Windows Presentation Foundation), es decir, las aplicaciones basadas en .NET
Framework 3.0 (antes conocido como WinFX), y que como seguramente sabrás están
basadas en la nueva tecnología que Microsoft introdujo en Windows Vista, aunque
actualmente esta versión (3.0) de .NET Framework también se puede usar en
Windows XP con SP2 o en Windows 2003 con SP1 (o superior), pues... (es que
siempre me aparto de lo que tengo que decir para dar explicaciones, en fin...),
pues como te decía, hasta ayer todo lo que he hecho con WinFX es para "jugar" un
poco, es decir, saber cómo funciona, cómo se pueden hacer algunas cosas, etc.
Algunas de esa pruebas ya las he publicado en esta sección de WinFX (no me
decido a dejar de llamarlo WinFX, lo siento), y algunas otras que iré
publicando... no solo para que a ti te sirva lo que publico, que también, pero
es que como tengo la cabeza que tengo y tengo tantas pruebas en mi disco duro,
pues algunas veces me cuesta trabajo encontrar la forma de hacer "algunas"
cosas, así que... esto que publico en esta sección (y en muchas otras de mi
sitio), me sirven de "chuletario", es decir, me sirven para encontrar
cómo hacer aquello que quiero hacer... es decir, ¡el Guille también usa el sitio
del Guille! (hay que practicar con el ejemplo, que dicen... je, je).
Te cuento la historia... si no quieres leer este "rollo";
puedes seguir leyendo más abajo.
Yo tengo una utilidad que uso para reproducir ficheros MP3, en realidad
(teóricamente) reproduce cualquier tipo de fichero musical que el Windows sea
capaz de reproducir, aunque yo lo uso para reproducir listas de ficheros MP3,
normalmente con la extensión .m3u, aunque acepta hasta ficheros .txt siempre y
cuando sean "ficheros reproducibles" (musicalmente hablando).
Esa utilidad la tengo desde hace muuuchos años, de hecho, en mi sitio
publiqué una de esas "reencarnaciones", y está en la sección de VB6:
gsPlayWMP
Una utilidad para reproducir listas de ficheros de audio (WAV y MP3), usando el
Windows Media Player del IE5.
La que uso actualmente es una versión mejorada con respecto a esa que
publiqué. Y mi versión ahora está hecha con Visual Basic 2005, pero antes lo
estuvo con Visual Basic .NET (2002) y con Visual Basic .NET 2003, es decir, la
voy actualizando con cada nueva versión de Visual Basic.
Hace unos meses (según la fecha de los ficheros, fue el 7 de Febrero de este
año), pensé de convertirla para usar todo lo que WPF ofrece, sobre todo por el
tema de los "colorines" y el uso de los Expander, con idea de
contraer o mostrar algunas partes de la aplicación, más que nada para tener solo
visible aquella parte que en realidad voy a usar en cada momento.
La verdad es que empecé a hacer el diseño del "formulario" o ventana
principal con la Beta 2 de Expression Blend, ya que las betas
que tenía por esas fechas de Visual Studio Orcas no permitían hacer muchas cosas
con el tema del diseño de las "ventanas de WPF", ahora con la Beta 2 de Visual
Studio 2008 tampoco, pero... ese es otro tema. Aunque es un tema importante...
ya que ayer se me colgó como 10 veces el Visual Studio 2008 Beta 2 al trabajar
con la aplicación esta que te comento... en fin... es una beta, que se puede
esperar...
Pero ayer (y anteayer) me puse "en serio" a hacer la aplicación, en realidad
a hacer la conversión de mi aplicación de Visual Basic 2005 a Visual Basic 2008
con WPF, que en realidad no hace nada que no se pueda hacer ahora, ya que en el
fondo lo que uso son las clases de WinFX, que se pueden usar actualmente sin
necesidad de usar ninguna beta, pero... como el Visual Studio 2008 Beta 2
soporta trabajar con ese tipo de aplicaciones, pues es más fácil que hacerlo
todo "a pelo".
Aunque todo hay que decirlo, seguramente será porque esto de crear
aplicaciones para WPF está pensado para que san los "diseñadores" los que se
encarguen de preparar el "formulario" o la interfaz de presentación, pero es un
"peñazo" trabajar con ese tipo de aplicaciones... sobre todo para los que
estamos acostumbrados a trabajar con las "bondades" de un buen diseñador de
formularios... y es que esto de crear la interfaz del usuario de una aplicación
para WPF con Visual Studio 2008 Beta 2 e incluso con Expression Blend es...
bueno... me reservo el adjetivo para no herir susceptibilidades, je, je...
Al final he acabado haciendo a mano casi todo... pero al menos el Visual
Studio 2008 tiene un buen soporte para manejar las propiedades y demás cosas que
hay que usar en el código XAML de la ventana (antes formulario) en la que se
mostrará la interfaz de usuario.
En esto le gana al Expression Blend por goleada, ya que el diseñador "ese",
que se maneja muy bien con las cosas que ofrece el WPF está muy bien... aunque
no me gusta el "aspecto de la interfaz" del programa, pero... ese también es
otro tema... Ya que el IntelliSense de Visual Studio 2008 es "casi perfecto" (no
soporta todo, pero se le puede perdonar), ya que el Expression Blend... bueno,
creo que no conoce ni de lejos que es eso de IntelliSense, al menos cuando vamos
a trabajar con el código XAML.
Pues bien... y ya termino con la batallita, no solo han sido "problemas" con
el diseño del interfaz gráfico, que al final casi he conseguido lo que me
proponía, con mucho trabajo y mucho escribir código a mano directamente en el
código XAML, pero... al menos casi he logrado lo que me proponía... ya te iré
contando también algunas de esas cosas que he "aprendido" por el camino... pero
esas cosas te las contaré en otro momento, ya que lo que te voy a contar hoy es
otro de los problemas con los que me he topado al convertir el código...
algunos, (la mayoría) los he resuelto, aunque aún me quedan muchos otros que no
tengo ni repajolera idea de cómo solucionarlos.... pero no cejaré en el
intento... je, je... A lo que iba, además de los problemas del interfaz, me he
topado con problemas de código... es decir, las cosas que "yo sé hacer" con una
aplicación para .NET Framework 2.0 no me sirven... bueno, si me sirven, pero no
son iguales que para .NET Framework 3.0, al menos si las clases que estoy usando
son las propias de esa versión.
Y a partir de la siguiente sección te contaré algunas de esas
"incompatibilidades" y lo que puedes hacer para remediarlas.
Como te comentaba antes en "la batallita del
agüelo", cuando quieres usar en una aplicación de WinFX algunas
cosas a las que estás acostumbrado en .NET Framework 2.0, puedes encontrarte con
algunas sorpresas.
Seguramente las podrás solucionar fácilmente, pero... para que te sea más
cómodo, aquí te explico algunas.
En otras actualizaciones de este artículo (o de uno nuevo que continúe este) te
explicaré más cosas.
Debido a como funcionan los controles de WPF, la propiedad Text
a la que estamos acostumbrados ya no está, ahora se puede usar la propiedad
Content, que aunque es mucho más "potente" que el mero hecho de
mostrar texto, al menos será la que usemos para asignar los textos a mostrar en
algunos controles.
LabelStatus.Content = "¡Hola, Mundo!"
Algo que me gustaba de la versión de .NET es que se consiguió eliminar la "multipliciidad"
de propiedades que servían para mostrar un texto, ahora con WPF volvemos "pa
trás" y... en fin... Bueno, decirte que la ventana principal (siempre que sea
una ventana basada en la clase Window), el texto a mostrar en
la barra del título, se asigna por medio de la propiedad Title:
Me.Title = "El título de la ventana"
Ahora, la propiedad Checked de los controles que aceptan esa
propiedad se llama IsChecked.
Por otra parte, Checked es un evento que se produce cuando el control en
cuestión se "chekea".
If chkMinimizar.IsChecked Then
Ese evento es el que normalmente solemos usar para comprobar si se ha
cambiado el estado de chequeado o no de un control que soporta ese estado de si
"IsChecked" o no. Bueno, podemos usar tanto
CheckedChanged como
CheckStateChanged, cualquiera de los dos nos avisa de que ha
cambiado el estado "chequeado" del control.
Pero en los controles de WinFX ahora hay dos eventos para averiguar ese
cambio, por un lado tenemos el evento Checked para indicar que
el control está "marcado" y por otro tenemos el evento UnChecked
para indicarnos que no está marcado. Pero debido a que tanto uno como el otro
usan los mismos parámetros en el método del evento, podemos unificarlos los dos
y seguir usando el mismo código que teníamos, por ejemplo, en las versiones
anteriores a .NET Framework 3.0 (WinFX) el código sería algo así:
Private Sub chkMezclar_CheckedChanged( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles chkMezclar.CheckedChanged
If Me.inicializando Then Exit Sub
'
Me.mezclarCanciones = Me.chkMezclar.Checked
If Me.chkMezclar.Checked Then
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "1")
Else
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "0")
End If
End Sub
En una aplicación para WPF sería de esta otra forma: (usando los dos eventos)
Private Sub chkMezclar_Checked(ByVal sender As Object, _
ByVal e As RoutedEventArgs) _
Handles chkMezclar.Checked
If Me.inicializando Then Exit Sub
Me.mezclarCanciones = True
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "1")
End Sub
Private Sub chkMezclar_Unchecked(ByVal sender As Object, _
ByVal e As RoutedEventArgs) _
Handles chkMezclar.Unchecked
If Me.inicializando Then Exit Sub
Me.mezclarCanciones = False
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "0")
End Sub
Que podemos "unificar" en un solo método y usarlo tal como hacemos hasta
ahora:
Private Sub chkMezclar_CheckedChanged(ByVal sender As Object, _
ByVal e As RoutedEventArgs) _
Handles chkMezclar.Checked, chkMezclar.Checked
If Me.inicializando Then Exit Sub
Me.mezclarCanciones = Me.chkMezclar.IsChecked.Value
If Me.chkMezclar.IsChecked Then
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "1")
Else
mINI.IniWrite(sFicIni, "General", "Mezclar canciones", "0")
End If
End Sub
Pues eso... que muchos de eventos habituales, clases, enumeraciones, etc.,
que usamos en nuestros proyectos par aplicaciones de escritorio de .NET 2.0 y
que suelen estar definidos en el espacio de nombres System.Windows.Forms,
ahora están definidos en System.Windows.
Debes tener en cuenta que aunque el espacio "raíz" sea el mismo (System.Windows),
en realidad son librerías distintas.
Si quieres usar las cosas "viejas" de System.Windows.Forms, debes agregar una
referencia a la librería System.Windows.Forms.dll.
Pero NO TE RECOMIENDO que agregues una importación al espacio de
nombres System.Windows.Forms, ya que si lo haces, pues tendrás
"inconsistencias" entre esas clases o tipos que se llaman igual en los dos
espacios de nombres.
Un caso concreto de las clases que están definidas en los dos sitios es la
"habitual" MessageBox. La clase en sí no da problemas, pero si
los parámetros.
Por ejemplo, este código que funciona bien en una aplicación de .NET 2.0 (o
anterior), fallará en una aplicación para WPF:
MessageBox.Show("ERROR: " & ex.Message, _
"Error al tocar", _
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation)
Tendrás que usar el siguiente código:
MessageBox.Show("ERROR: " & ex.Message, _
"Error al tocar", _
MessageBoxButton.OK, _
MessageBoxImage.Exclamation)
A lo mejor piensas que son iguales. Pero no te fíes... esto no es uno de esos
juegos de buscar las diferencias.
Esto es algo "común" en las clases de WPF, el que se inventó los nombres
seguro que estaba "fumao" o recibió una inspiración divina... en serio,
seguramente pensó que es más "guay" (cool, molón, chido, etc.) usar nuevos
nombres "más adecuados", ya que si te fijas bien, los tipos de botones a mostrar
en las aplicaciones "actuales" están definidos en la enumeración
MessageBoxButtons, mientras que en WPF están en
MessageBoxButton, es decir, lo mismo, pero sin la "recomendada" ese del
plural al final.
Lo mismo ocurre con la enumeración de los iconos a mostrar, en las aplicaciones
de Windows.Forms se usa MessageBoxIcon y en
WPF es
MessageBoxImage.
Y como esto... muchos casos parecidos, por ejemplo... (sigue leyendo).
En este caso, hay varios cambios... de nombres, no de forma de usarlo.
Por ejemplo, hay que tener en cuenta que cuando definimos el método para
interceptar los eventos de arrastrar y soltar (Drag & Drop), en nuestras
aplicaciones de .NET "normal" el código será más o menos como este:
Private Sub fgsPlayMCIAPI_DragDrop(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DragEventArgs) _
Handles MyBase.DragDrop
addDrop2List(e.Data)
End Sub
Private Sub fgsPlayMCIAPI_DragOver(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DragEventArgs) _
Handles MyBase.DragOver
e.Effect = DragDropEffects.Copy
End Sub
Y en una aplicación de WPF, eso mismo se haría así:
Private Sub fgsPlayMCIAPI_DragDrop(ByVal sender As Object, _
ByVal e As DragEventArgs) _
Handles MyBase.Drop
addDrop2List(e.Data)
End Sub
Private Sub fgsPlayMCIAPI_DragOver(ByVal sender As Object, _
ByVal e As DragEventArgs) _
Handles MyBase.DragOver
e.Effects = DragDropEffects.Copy
End Sub
Busca las diferencias (que son las que te he marcado en el primer código).
En el caso del parámetro, volvemos a lo que te he comentado en
el punto 5.
Se llaman igual, pero están definidos en espacios de nombres distintos.
El segundo cambio es el nombre de la propiedad a la que hay que asignar el
"efecto", y es que ahora está en plural, mientras que antes estaba en
singular... sin más comentarios de los que ya hice antes.
El tercero, y más importante, es el cambio del nombre del evento que "acepta"
la operación de soltar, hasta ahora se llamaba DragDrop, pero
en WinFX se llama Drop. ¡Que lo sepas!
Otro follón con los nombres de las propiedades y demás... en este caso, si
desde el evento KeyDown o KeyUp quieres
averiguar que la tecla pulsada es una en concreto, pues... debes usar otros
nombres.
Por ejemplo, este sería el código para .NET "normal":
Private Sub ListView1_KeyDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles lvCanciones.KeyDown
' Si se pulsa la tecla DEL, borrar el seleccionado
If e.KeyCode = System.Windows.Forms.Keys.Delete Then
If lvCanciones.SelectedItems.Count = 0 Then Return
' Borrar
lvCanciones.Items.RemoveAt(lvCanciones.SelectedItems(0).Index)
guardarListaActual()
End If
End Sub
No te resalto las diferencias para que mejores tu capacidad visual, je, je,
(esto como lo de "Brain trainer ese").
Este sería el código equivalente para WPF:
Private Sub ListView1_KeyDown(ByVal sender As Object, _
ByVal e As KeyEventArgs) _
Handles lvCanciones.KeyDown
' Si se pulsa la tecla DEL, borrar el seleccionado
If e.Key = Key.Delete Then
If lvCanciones.SelectedItems.Count = 0 Then Return
' Borrar
lvCanciones.Items.RemoveAt(lvCanciones.SelectedItems(0).Index)
guardarListaActual()
End If
End Sub
Aparte de que en lugar de la propiedad KeyCode hay que usar
Key y de que en lugar de comprobar si es un valor de la
enumeración Keys ahora esa enumeración se llama Key,
debemos recordar que los espacios de nombres usados en WPF son distintos a los
de las aplicaciones de Windows.Forms de .NET 2.0, sí, ya sé que te lo he dicho
varias veces, pero...
Además, me he dado cuenta de que ese código solo funcionará si Option Strict
está desconectado. Ya que... mira el siguiente punto.
En este caso, voy a hablar de la colección SelectedItems del
control
ListView. Pero estos cambios también afectan a los controles
ComboBox y ListBox, aunque menos.
En .NET 2.0, la colección SelectedItems del ListView
devuelve una colección de tipo ListView.SelectedListViewItemCollection
(definida en System.Windows.Forms). Esa colección devuelve valores del tipo
ListViewItem, por tanto, podemos acceder a ciertos valores o
propiedades del elemento seleccionado que estamos "observando".
Pero en WPF no es lo mismo, ya que esa colección es de tipo IList,
por tanto, devuelve valores de tipo Object.
¿Por qué? Por lo que ya te comenté antes, porque ahora en WPF el contenido
de los controles pueden ser... ¡cualquier cosa! Es decir, un control
ListView puede tener más cosas que "simples" objetos del tipo
ListViewItem, así que... hay que tener en cuenta esto.
En el código que te mostré antes, para saber el índice del primer elemento
que estaba seleccionado usaba este código:
lvCanciones.SelectedItems(0).Index
Para ese caso concreto en el que quiero averiguar el índice del primer
elemento seleccionado, lo puedes hacer así:
lvCanciones.SelectedIndex
Y si lo que quieres es recuperar el texto del elemento seleccionado... mira
el punto 10:
Siguiendo con el tema del punto anterior, decirte que el valor devuelto por
la propiedad SelectedItem de la clase ListView
ahora en WPF es de tipo
Object
(por aquello de que puede contener cualquier cosa, bla, bla, bla).
Pero si sabes que lo que contiene es simplemente texto, y el código que
usabas hasta ahora es algo como esto:
toolTip1.SetToolTip(lvCanciones, _
"Seleccionado: " & lvCanciones.SelectedItems(0).Text)
Aquí tienes que "ver" dos cosas, una es que el texto se accede con
SelectedItems(0).Text, es decir, el texto del primer elemento
que está seleccionado se asigna al "tool tip" relacionado con el ListView.
En WPF yo lo he hecho así:
lvCanciones.ToolTip = "Seleccionado: " & lvCanciones.SelectedItem.ToString
Es decir, ahora SelectedItem nos indica el elemento que está
seleccionado en el control ListView, y como resulta que ese
"objeto" devuelto es de tipo Object, y "yo sé" que en realidad
es una cadena de texto, pues simplemente lo convierto en texto usando el método
ToString.
La segunda cosa es que en WPF todos los controles (creo que todos) tienen la
propiedad Tooltip, por tanto, a esa propiedad es a la que
asigno el "tool tip" que quiero mostrar.
Al menos en el control ListView (en los otros controles no lo he comprobado,
pero al menos en el control ComboBox también se llama así).
La cosa es que el "efecto" conseguido es el mismo... saber si se ha
seleccionado otro elemento distinto al que antes estaba seleccionado, pero...
para que sepas que ahora se llama de otra forma.
Seguramente de estos dos eventos, en todas tus aplicaciones usas el que
"inicia" el formulario principal, que en las aplicaciones "normales" es el
evento producido por el evento Load.
En WPF ese evento ya no existe.
No te asustes, je, je, que no existe con ese nombre, ya que ahora se llama
Loaded, que seguramente le habrán cambiado el nombre para que
quede claro que el formulario (perdón, la ventana) ya está cargado.
El evento de cierre de los formularios de nuestras aplicaciones clásicas (de
.NET 2.0) utilizan el evento FormClosing para avisar de que el
formulario se está cerrando. En ese evento se avisa de varias cosas que podemos
usar para saber más sobre cómo se cierra.
En WPF se llama como "antes", es decir, es Closing y el tipo
de parámetro que recibe es del mismo tipo:
System.ComponentModel.CancelEventArgs. Osea... casi un paso atrás, je,
je.
Bueno, pues ya está bien por hoy... aunque hay más cosas que debes saber...
A ver si me apunto las cosas que vaya encontrando (que tengo más cosas que
han cambiado, pero como no las he ido apuntando, pues pasa lo que pasa, que solo
he "encontrado" las que más o menos me he ido encontrando en el repaso que he
hecho, pero hay más "incompatibilidades", sobre todo en cosas "comunes" o al
menos comunes para lo que yo suelo hacer, ya que ahora, pues... en fin... que ya
no se puede hacer como antes... y en algunos casos, aún no he averiguado cómo
hacerlo... seguramente porque no me he puesto "en serio", je, je... o lo mismo
nunca averiguo cómo hacerlo, que... ya sabes que el Guille no lo sabe todo, je,
je...
Por si te sientes "animado" a ayudarme, te digo las cosas que me gustaría
saber cómo hacer:
- Cómo agregar "subitems" a un item de un ListView
- Cómo saber en qué columna se ha pulsado (en el control
ListView)
- Cómo clasificar los elementos del ListView
- Cómo asignar los valores de FullRowSelect, GridLines,
MultiSelect, etc. del modo "View" de un ListView
- Cómo crear menús del tipo ContextMenu de WPF "al vuelo",
ya que he tenido que usar los de Windows.Forms, que en VB no sé cómo asignar
los eventos de los submenús (pero todo por código) o en modo de diseño... ya
que tampoco he visto cómo hacerlo
- Bueno... y más cosas, pero por ahora está bien... de todas formas, dudo
que me lo aclare alguien, je, je.
(las cosas tachadas es porque ya sé cómo hacerlas...
por desgracia nadie me ha contado cómo hacerlas y he tenido que "comerme el
coco" para averiguarlo, con el problema de casi no saber por dónde buscar),
un día de estos te contaré cómo)
Si te animas a "ayudarme" a solucionar mis dudas o
simplemente quieres hacer un comentario sobre este artículo, puedes usar este
hilo de mis foros (recuerda que para postear debes estar registrado):
http://foros.elguille.info/Mensajes.aspx?ID=32500
Un vistazo rápido a las equivalencias mostradas en este artículo
Estos son los links a las distintas equivalencias que te he mostrado aquí.
En otros artículos posteriores te mostraré más... conforme los vaya
descubriendo...
- No existe la propiedad Text de los controles
- La ventana principal tampoco tiene Text
- No existe la propiedad Checked (ahora es un evento)
- No existe el evento CheckedChanged
- Muchas de los tipos habituales de Windows.Forms
están definidos en Windows
- La clase MessageBox y las enumeraciones de esa
clase
- Cambios en Drag & Drop
- Cambios en las teclas pulsadas en los eventos
KeyDown y KeyUp
- La colección SelectedItems de un ListView es
de tipo Object (IList)
- El elemento seleccionado de un ListView es de tipo
Object
- El evento SelectedIndexChanged ahora es
SelectionChanged
- Los eventos de inicio y cierre de la ventana
principal
A ver si le pregunto a la gente del "TEAM de VB" para ver si existe algún
tipo de documento "oficial" sobre estas equivalencias entre los programas hechos
con .NET 2.0 (Windows.Forms) y los creados con .NET 3.0 (WinFX).
Si me responden afirmativamente, te lo diré.
Espero que todo esto te sea de utilidad como lo ha sido (y será) para mí, je,
je.
Nos vemos.
Guillermo
Nota del 21/Ago/07:
He publicado la segunda parte de
las equivalencias entre WPF y .NET normal.