SlideShare una empresa de Scribd logo
1 de 29
Descargar para leer sin conexión
Desarrollo de aplicaciones con .NET y WPF
Andrés Marzal
Departamento de Lenguajes y Sistemas Informáticos
Universitat Jaume I
Decharlas, 24 de mayo de 2010
¿Qué es .NET? ¿Qué es C#? ¿Qué es WPF? ¿Qué es Visual
Studio? ¿Qué es Expression Blend?
odemos simplificar mucho y decir que .NET es la respuesta de Microsoft a Java. .NET ofrece un entorno
de ejecución con máquina virtual para un lenguaje de máquina propio: IL, por Intermediate Language.
Diferentes lenguajes se traducen a ese lenguaje de máquina y un compilador de última hora genera
código nativo, que es lo que realmente se ejecuta.
.NET sigue un estándar ECMA: “Standard ECMA-335, Common Language Infrastructure (CLI)”. La
implementación de Microsoft del CLI se conoce por CLR (Common Language Runtime). Hay una
implementación libre de CLI desarrollada por Novell: Mono. Acompaña al entorno un conjunto de
librerías gigantesco, aspecto en el que .NET va significativamente por delante de Mono.
El lenguaje de preferencia para .NET es C# (se lee “C Sharp”), un lenguaje que se diseñó para superar
algunos problemas de Java. En particular, la diferencia sustancial entre valores y objetos y la carencia
de delegados que facilitaran la implementación del patrón observador/observable. C# ha evolucionado
mucho desde su aparición, pero mantiene una coherencia en el diseño que lo hace fácil de aprender.
Aunque es un lenguaje con herencia simple, implementación de interfaces y memoria con
recolección automática, como Java, se diferencia de éste en numerosos aspectos importantes. C# ha
integrado eficazmente varios conceptos de la programación funcional, como las funciones anónimas
y las clausuras. Cuenta además con un mini-lenguaje para efectuar consultas a fuentes de datos, LINQ,
que facilita mucho la gestión de información proveniente de bases de datos, de colecciones en
memoria, de ficheros XML, etcétera. Lo cierto es que LINQ facilita el trabajo con cualquier objeto que
proporcione una enumeración de elementos. Las enumeraciones son muy corrientes en .NET, pues C#
facilita su diseño e implementación mediante estructuras de control como “yield return”. C# evita,
además, la verbosidad del patrón de consulta y asignación de valor a campos (“getters & setters”) propia
de Java mediante las denominada propiedades. Finalmente cabe advertir que la implementación de
tipos genéricos en C# es mucho más sólida que la de Java, pues conserva información de tipos y
distingue entre valores y objetos en el parámetro de tipo, a diferencia de lo que ocurre en Java, que
basa su implementación de genéricos en el borrado de tipos. C# está estandarizado y su definición se
encuentra en “Standard ECMA-334 – C# Language Specification”. Va por la versión 4.0 tanto en .NET
como en Mono.
WPF son las siglas de Windows Presentation Foundation. Es un conjunto de librerías para
implementar aplicaciones interactivas. Arrancó con el nombre en clave “Avalon”. Presenta muchos
P
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
¿Quées.NET?¿QuéesC#?¿QuéesWPF?¿QuéesVisualStudio?¿QuéesExpressionBlend?
2
aspectos interesantes: separación de apariencia y lógica,
soporte del patrón “orden” (command), fácil conexión a
fuentes de datos vía ligaduras (bindings), simplificación
de trabajo con objetos observables mediante
propiedades de dependencia, herencia de valores para
propiedades por relación jerárquica entre componentes,
acceso directo a hardware gráfico, animaciones,
personalización completa de componentes mediante
plantillas, etcétera.
La P de WPF viene de “Presentation” y es importante.
WPF soporta el patrón arquitectónico Modelo-Vista-
Presentador (frente al clásico Modelo-Vista-Controlador).
La versión WPF de este patrón es la que se conoce por
Modelo-Vista-Modelo de la Vista, o MVVM por
Model-View-ViewModel.
Hay una versión ligera de WPF diseñada para correr
incrustada en navegadores (aunque también puede
ejecutarse fuera del navegador): Silverlight. El proyecto
arrancó con el nombre en clave WPF/E, por WPF
Everywhere, y muchas veces se habla de él en términos
de competencia directa con Flex y Flash. Mono ofrece
una implementación libre de Silverlight: Moonlight
(aunque suele ir retrasada con respecto a la de Microsoft:
La versión actual de Silverlight es la 3.0, con la 4.0 a
punto de salir, y Moonlight implementa la funcionalidad
de la 2.0 y buena parte de 3.0).
WPF propone separar apariencia de lógica y lo lleva al
extremo de ofrecer una herramienta para diseñadores
gráficos que se integra en el proceso de desarrollo.
Cuando el programador crea una interfaz gráfica se
concentra en los elementos desde el punto de vista lógico y en cómo se comunican entre sí y con los
datos de la aplicación. Los ficheros generados son directamente accesibles con Microsoft Expression
Blend. Allí, el diseñador encuentra una aplicación con la que es sencillo cambiar el aspecto visual de los
elementos, aplicar efectos y diseñar animaciones. Blend es parte de la suite Microsoft Expression, que
incluye más herramientas orientadas a diseñadores gráficos (como Microsoft Expression Design, una
herramienta en la línea de Adobe Freehand).
Visual Studio es la plataforma de desarrollo por excelencia para .NET. Su última versión es Visual Studio
2010 (VS 2010) y ofrece soporte nativo para lenguajes .NET como C#, Visual Basic, F# (un lenguaje
funcional de la familia de Ocaml) y para proyectos “clásicos” con C o C++. VS 2010 es extensible y
cuenta con soporte (de terceras partes) para herramientas como Subversion o Mercurial. El proyecto
Mono cuenta con su propia plataforma de desarrollo: MonoDevelop. Y hay otra plataforma abierta,
SharpDevelop, aunque de uso marginal.
.NET y Software Libre
El principal problema de .NET para la
comunidad no es de carácter técnico, sino el
estigma de ser obra de Microsoft. Muchas de
las herramientas libres de uso común en Java
están disponibles para .NET: NHibernate,
NAnt, NUnit, Spring.NET, etcétera. Microsoft
abrió en 2009 CodePlex, un espacio para dar
soporte a proyectos de software libre y
algunos de sus proyectos recientes se
distribuyen con licencias que permite acceder
al código fuente (MEF e IronPython, por
ejemplo). Microsoft apoya oficiosamente la
iniciativa Moonlight, de Mono, con la que se
está desarrollando una versión abierta de
Silverlight.
Aunque CLI o C# se han publicado como
estándares ECMA y la actitud de Microsoft
ante la comunidad de software libre ha
evolucionado mucho, la comunidad mira con
recelo cualquier innovación que provenga de
Microsoft. Por ejemplo, Miguel de Icaza, líder
del proyecto Mono, es frecuentemente
insultado o menospreciado por personas o
asociaciones respetadas en la comunidad del
software libre. (Se puede obtener información
sobre uno de los últimos rifi-rafes en
http://www.fsf.org/blogs/rms/microsoft-
codeplex-foundation,
http://www.linuxtoday.com/news_story.php3
?ltsn=2009-09-21-028-35-OP-CY-EV).
Parece que ahora le toca a Microsoft sufrir
una campaña de FUD como las que montaba
hace tiempo.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
PrimerospasosconWPF
3
Primeros pasos con WPF
onstruyamos una aplicación WPF extremadamente sencilla para empezar a entender algunos de los
elementos básicos de WPF y C#. Nuestra aplicación mostrará un formulario en el que el usuario puede
poner nombre y primer apellido. Tras pulsar un botón, se mostrará un cuadro de diálogo modal con un
saludo personalizado.
Empezamos iniciando VS 2010 (Figura 1a). Con la opción FileNewProject… creamos un nuevo
proyecto de tipo “WPF Application” al que denominamos HolaMundo (Figura 1b).
Esto crea un directorio HolaMundo y una estructura de
ficheros y directorios que facilita el desarrollo de la
aplicación. El explorador de soluciones muestra esta
estructura (Figura 2). Encontramos una carpeta para
propiedades, otra para referencias a DLLs y “ficheros
lógicos” de aplicación que pueden desplegarse en uno o
más ficheros físicos.
Los ficheros App.* contienen el punto de entrada a la
aplicación y definen/construyen una instancia de la clase
Application. Los ficheros MainWindow.* definen la ventana
principal de la aplicación y App.* contiene una indicación de
que MainWindow.* es la ventana principal o URI de inicio.
Los ficheros que nos interesan tienen extensión “.xaml” o
“.cs”. Los primeros son ficheros en un formato XML
denominado XAML (eXtensible Application Markup
Language); los segundos son ficheros C#.
XAML sigue un esquema XML cuyos elementos se inscriben en el espacio de nombres
{http://schemas.microsoft.com/winfx/2006/xaml}. XAML es un lenguaje de marcado diseñado para
facilitar la instanciación de objetos .NET y la definición de sus propiedades (o “atributos”, en jerga XML).
Hay una versión de XAML con un esquema cuyos elementos están en el espacio de nombres
C
(a) (b)
Figura 1. (a) Pantalla inicial de Visual Studio 2010. (b) Cuadro de diálogo para crear un nuevo proyecto.
Figura 2. Explorador de soluciones con el
proyecto WPF recién creado.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
PrimerospasosconWPF
4
{http://schemas.microsoft.com/winfx/2006/xaml} que permite instanciar objetos WPF y definir sus
propiedades.
XML es un formato jerárquico y, por tanto, los objetos en XAML siempre forman un árbol. Esa
estructura es natural en una interfaz de usuario. Veamos el aspecto de App.xaml:
<Application x:Class="HolaMundo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
El fichero instancia un objeto de la clase App (que es nuestra y hereda de otra, Application, propia de
WPF) en el espacio de nombres C# HolaMundo (lo indica el atributo x:Class). Define los dos espacios de
nombres XML que se usan: XAML y WPF y señala que la aplicación empieza en la URI
MainWindow.xaml. A continuación, define una sección para los recursos de la aplicación, pero no los
hay. La sintaxis es especial y merece que nos detengamos: la marca Application.Resources corresponde,
en realidad, a un atributo “Resources” de la marca “Application”. Es decir, en principio (pero sólo en
principio), podríamos haber encontrado algo de este estilo:
<Application x:Class="HolaMundo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
Resources="…">
</Application>
¿Por qué se usa esa otra sintaxis, extraña en el mundo XML (aunque siga el estándar)? Porque así es
posible que el atributo Resources contenga nuevos elementos XML y no sólo una simple cadena. Esta
sintaxis alternativa es muy corriente en los ficheros XAML y conviene acostumbrarse a ella.
El fichero App.xaml.cs no contiene gran cosa:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace HolaMundo
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Botonesyeventos
5
La clase App, en el espacio de nombres HolaMundo, es una clase parcial (adjetivo “partial”), lo que
significa que parte de su código está definido en otro fichero. Ese otro fichero contiene el código auto-
generado por VS 2010.
La clase está vacía (en nuestro fichero, pero no en el auto-generado). En nuestra parte podríamos
definir, por ejemplo, comportamientos relacionados con el ciclo de vida de la aplicación (creación,
activación, minimización, cierre, etcétera).
Veamos ahora qué contiene MainWindow.xaml:
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</Window>
Se crea una instancia de un objeto de la clase MainWindow, que por herencia es también de la clase
Window. Se definen algunos atributos: el título (Title), la altura (Height) y la anchura (Width). Las
unidades en que se indican las medidas son independientes de la pantalla. Una pulgada corresponde
siempre a 96 unidades. (Se escogió esta unidad de medida por facilitar la medida en monitores
convencionales, que suelen presentar una resolución de 96 dpi, es decir, 96 puntos por pulgada.)
Dentro de la ventana hay un Grid. Es un elemento de maquetación. Más adelante lo estudiaremos con
detenimiento.
Ejecutemos la aplicación (pulsamos la tecla F5) y aparece una ventana vacía (Figura 3). VS 2010 sigue
estando accesible, pero no permite editar el contenido del proyecto. Cerramos la aplicación pulsando
en el botón de cierre de la ventana (o pulsando Shift-F5 en VS 2010) y volvemos a VS 2010.
Botones y eventos
camos a crear un botón con el texto “Saluda” y haremos que al pulsarlo aparezca una ventana modal
con un saludo:
<Window x:Class="HolaMundo.MainWindow"
…
Title="MainWindow"
Height="350" Width="525">
<Grid>
<Button Click="Button_Click">
Saluda
</Button>
</Grid>
</Window>
El texto “Saluda” es el valor que se asigna a una
propiedad por defecto: Content. Es decir, este
código XAML es equivalente:
V
Figura 3. Ventana de la aplicación en ejecución.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Botonesyeventos
6
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Saluda" Click="Button_Click" />
</Grid>
</Window>
Algunos objetos tienen campos privilegiados en tanto que el contenido de la marca XML (el texto o
código XML que va entre las marcas de apertura y cierre) se les asigna automáticamente. En un botón,
el campo especial es Content, en una caja de texto, el campo especial es Text.
El atributo Click corresponde a un evento. Cada vez que se pulse en el elemento de tipo Button con el
botón izquierdo del ratón y se levante en el interior del elemento, se invocará automáticamente al
método Button_Click. El asistente de VS 2010 nos ha preparado el método Button_Click en
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 HolaMundo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
}
El parámetro sender de Button_Click contendrá una referencia al propio botón y el parámetro de tipo
RoutedEventArgs contendrá ciertos datos relativos al evento cuando éste ocurra. Vamos a rellenar el
método con la invocación al diálogo modal:
private void Button_Click(object sender, RoutedEventArgs e)
{
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Maquetaciónconpaneles
7
MessageBox.Show("Hola");
}
El código que acompaña al XAML se denomina “código trasero” (code behind). Y aunque ahora
recurrimos a él, veremos que MVVM permite eliminar buena parte de él (si no todo).
Ejecutemos la aplicación (F5). El botón ocupa toda el área de trabajo (Figura 5a). Al pulsar el botón
aparece la ventana modal que bloquea el acceso a la ventana MainWindow (Figura 5b).
Maquetación con paneles
eamos qué ocurre si tratamos de añadir elementos gráficos nuevos, como una etiqueta o una caja para
texto:
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label>Nombre:</Label>
<TextBox></TextBox>
<Button Click="Button_Click">Saluda</Button>
</Grid>
</Window>
En la ventana sólo vemos el último elemento. En realidad están
todos, pero uno encima del otro (véase la Figura 5). Es cosa del
elemento Grid, que dejamos para luego por ser complejo: si dos o
más elementos están en la misma celda de un Grid, se
superponen.
V
Figura 5. Los elementos se tapan
unos a otros en el Grid, por lo que
sólo se ve el botón, que está encima
del todo.
(a) (b)
Figura 4. (a) Ventana con el botón “Saluda”, que ocupa toda la superficie de la ventana. Ventana de diálogo
modal que aparece al ejecutar el método Button_Click.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Maquetaciónconpaneles
8
Empezamos por un elemento de maquetación más sencillo:
StackPanel. Un StackPanel apila vertical u horizontalmente sus
elementos.
<StackPanel>
<Label>Nombre:</Label>
<TextBox></TextBox>
<Button Click="Button_Click">
Saluda
</Button>
</StackPanel>
La Figura 6 muestra el resultado de la nueva disposición de
elementos en el StackPanel: uno sobre el otro, por orden de
aparición en el fichero XAML.
Cada elemento WPF contiene decenas de atributos. Podemos
recurrir a un formulario para asignar valores distintos de los
“por defecto”, pero lo cierto es que a la larga resulta
conveniente usar el editor de XAML.
Hay más elementos de maquetación y se pueden incorporar
otros definidos por programadores. Los paneles que vienen de
serie son:
 Grid: distribución de elementos en una tabla, con la
posibilidad de fundir filas y columnas.
 StackPanel: distribución de elementos en sucesión
vertical u horizontal.
 DockPanel: distribución de elementos con anclaje a
punto cardinal y posible expansión del último al área
sobrante.
 WrapPanel: distribución de elementos en sucesión
vertical u horizontal en “líneas” (como el texto, que
fluye de una línea a la siguiente).
 UniformGrid: distribución de elementos en una matriz
cuadrada.
 Canvas: ubicación precisa de elementos.
(Algunos paneles definidos por programadores disponen
elementos gráficos de formas novedosas u ofrecen
animaciones para el desplazamiento o selección de sus
elementos. Se pueden encontrar ejemplos en
http://www.codeproject.com/Articles/37348/Creating-
Custom-Panels-In-WPF.aspx,
http://www.wpftutorial.net/CustomLayoutPanel.html o
http://www.codeproject.com/KB/WPF/Panels.aspx.) Figura 6. Los tres elementos se muestran
uno sobre otro gracias al StackPanel.
Propiedades
Los elementos XAML tienen
numerosos atributos y al principio
cuesta un poco manejarse con
tantos. Puede venir bien invocar el
panel de edición de atributos. Con el
cursor en el elemento XAML cuyos
atributos se desea editar, aparece un
panel de propiedades al pulsar F4.
No obstante, a la larga es más
productivo usar el editor de XAML en
VS 2010, que asiste al programador
con los menús Intellisense.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Maquetaciónconpaneles
9
Las maquetas más complejas suelen formarse combinando diferentes paneles. En nuestro caso,
podemos crear un StackPanel vertical en el que apilar dos StackPanels adicionales, estos horizontales, y
el botón.
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Nombre:</Label>
<TextBox></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Apellido:</Label>
<TextBox></TextBox>
</StackPanel>
<Button Click="Button_Click">Saluda</Button>
</StackPanel>
</Window>
La ventana que hemos creado tiene espacio muerto bajo el botón “Saluda”. Esto es así porque hemos
creado la ventana con unas dimensiones fijas: 525 por 350 puntos. El problema es fácil de corregir:
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
SizeToContent="Height">
El atributo SizeToContent puede tomar los valores
Manual (que es el que toma por defecto), Height,
Width y WidthAndHeight. El efecto de seleccionar
Height para SizeToContent es una ventana con
anchura fija y altura ajustada al contenido de la
ventana (Figura 7a).
Hay un par de problemas adicionales. Por una
parte, los campos de texto son pequeños (aunque
crecen automáticamente conforme tecleamos
texto en ellos); por otra, el alineamiento de los
campos de texto no es perfecto y depende del
tamaño de las etiquetas (Figura 7b).
El elemento de maquetación Grid permite
solucionar los dos problemas. Un Grid consta de
una sección de declaración de filas y columnas. En
nuestro caso definiremos 3 filas y 2 columnas. La
primera fila contendrá la etiqueta “Nombre:” y su
campo de texto; la segunda, la etiqueta “Primer
apellido:” y su campo de texto; y la tercera, el
botón “Saluda”. Las etiquetas se dispondrán en la Figura 8. Ventana con espacio indeseado.
(a)
(b)
Figura 7. (a) La ventana con altura ajustada a su
contenido. (b) Efecto de mal alineamiento cuando las
etiquetas tienen texto de diferentes longitudes.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Maquetaciónconpaneles
10
primera columna y los campos de texto en la segunda. El botón “Saluda” ocupará una columna que será
resultado de fundir las dos. La primera columna se ajustará al contenido y la segunda ocupará el resto
del espacio. Nuestra tabla presentará, pues, esta estructura:
Etiqueta Campo de texto----------------------------------------------------------------------------------
Etiqueta Campo de texto----------------------------------------------------------------------------------
---------------------------------------------------------Botón------------------------------------------------
El código XAML se complica un poco:
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525" SizeToContent="Height">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0">Nombre:</Label>
<TextBox Grid.Column="1" Grid.Row="0"></TextBox>
<Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label>
<TextBox Grid.Column="1" Grid.Row="1"></TextBox>
<Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">
Saluda
</Button>
</Grid>
</Window>
Por fin vemos lo conveniente de fijar atributos con la sintaxis Elemento.Atributo: no es apropiado definir
los atributos RowDefinitions y ColumnDefinitions con una simple cadena de texto, pues son en realidad
listas de elementos XML, algunos con sus propios atributos.
Hay un nuevo elemento sintáctico. Hay atributos (no elementos, como antes) con la sintaxis
Elemento.Atributo. Examinemos, por ejemplo, esta línea:
<Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label>
El atributo Grid.Column permite asignar un valor a una propiedad Column definida en Grid, no en Label.
Los elementos de tipo Label no saben nada de los de tipo Grid y, aun así, pueden asociar un valor a una
propiedad de Grid. Los elementos WPF mantienen un diccionario que permite asociar valores a claves
(propiedades) de las que nada sabe. En este caso, esas propiedades permiten ubicar el elemento en una
fila/columna del Grid.
Si nosotros definiésemos un panel propio, digamos que con una clase ClockPanel, en el que hubiese
que ubicar los elementos alrededor de una circunferencia, por ejemplo, necesitaríamos que cada
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Contenidorico,diseñográficoyanimaciones
11
elemento especificase los grados en los que debe aparecer en la esfera del reloj. Podríamos, entonces,
usar una etiqueta como ésta:
<Label ClockPanel.Angle="90">Primer apellido:</Label>
Nótese que Label no sabe nada de ClockPanel (de hecho, ClockPanel es una invención nuestra y, de
momento, ni siquiera existe). Pese a ello, podemos gestionar atributos de este tipo, que reciben el
nombre de “propiedades pegadas” (attached properties).
Contenido rico, diseño gráfico y animaciones
PF sigue un modelo de contenido rico. En muchos sistemas de construcción de aplicaciones con interfaz
gráfica de usuario hay serias limitaciones al contenido de los elementos. En algunas, los botones sólo
pueden contener, por ejemplo, texto y, opcionalmente, un icono. Escapar de esta restricción, cuando es
posible, obliga a construir nuevos elementos, lo que supone un incremento de complejidad enorme.
WPF, sin embargo, permite que muchos componentes contengan a otros componentes en su interior, lo
que facilita el diseño de aplicaciones con un acabado gráfico espectacular (si se trabaja codo con codo
con diseñadores gráficos, claro está).
Podemos probar a añadir un smiley al botón “Saluda”. Lo haremos con ayuda de Miorosoft Expression
Blend. Arrancamos Blend y desde su menú FileOpen Project/Solution… abrimos el proyecto VS 2010
HolaMundo y nos encontramos con la aplicación como se muestra en la Figura 9. La interfaz de Blend es
bastante compleja y no la analizaremos en detalle. Sólo queremos llevarnos una impresión acerca de su
uso y ver que es una herramienta especializada en adaptar la apariencia de nuestra aplicación, y no en
la lógica.
Podemos editar código XAML desde Microsoft
Expression Blend del mismo modo que hacemos
con VS 2010. Basta con pulsar el icono “<>” que
hay en la parte superior derecha del panel central
(el que contiene la ventana de nuestra aplicación).
Con el editor XAML de Microsoft Expression Blend
hemos escrito este texto en el fichero de texto
MainWindow.xaml, es decir, en el mismo fichero
que hemos estado editando en Visual Studio y
que forma parte del proyecto HolaMundo:
<Window x:Class="HolaMundo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525" SizeToContent="Height">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
W
Figura 9. Microsoft Expression Blend 4 tras abrir el
proyecto HolaMundo.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Contenidorico,diseñográficoyanimaciones
12
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/>
<TextBox Grid.Column="1" Grid.Row="0"/>
<Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/>
<TextBox Grid.Column="1" Grid.Row="1"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">
<StackPanel>
<TextBlock TextAlignment="Center">Saluda</TextBlock>
<Canvas Width="64" Height="64">
</Canvas>
</StackPanel>
</Button>
</Grid>
</Window>
El botón contiene ahora un StackPanel con dos elementos: un bloque de texto y un panel de tipo
Canvas de tamaño 64x64. Ahí dibujaremos el smiley. Con ayuda de la paleta de herramientas y el panel
de propiedades creamos el gráfico que se muestra en la Figura 10.
Blend permite crear efectos gráficos y animaciones. Vamos a
hacer que cuando el ratón entre en la región del botón el
smiley dé una vuelta.
Empezamos creando una animación. Seleccionamos el
Canvas y en el icono “+” del panel Objects and Timeline
seleccionamos New… y creamos una historia (storyboard) a
la que denominamos ZoomStoryboard. Con la marca de
tiempo (línea amarilla) en el instante 0, fijamos a 0 la propiedad Angle en el panel Transform del Canvas
que contiene al smiley, y fijamos a 360 su valor en el instante 1. Con eso conseguiremos que el smiley
dé una vuelta completa en un segundo.
Seleccionamos ahora el botón Saluda y seleccionamos el activo de tipo Behaviors denominado
ControlStoryBoardAction. En su panel, seleccionamos la historia ZoomStoryboard y el evento MouseEnter.
Podemos probar a ejecutar la aplicación y comprobar que cada vez que el cursor entra en el botón, se
ejecuta la animación. Bueno, no sólo entonces: también se dispara cuando cargamos la aplicación.
Luego eliminaremos este efecto indeseado (que aunque podemos eliminar desde Blend, eliminaremos
desde VS 2010).
Todo lo que hemos hecho con Blend se podría haber hecho directamente con VS 2010, pero hubiese
supuesto un esfuerzo considerablemente mayor, como comprobaremos en breve al analizar el XAML
generado.
Figura 10. Botón con dibujo creado con el
editor de Blend.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
MássobreXAML:recursos,disparadores,transformacionesyreferencias
13
Más sobre XAML: recursos, disparadores, transformaciones y
referencias
s hora de volver a VS 2010. Cerramos Blend y volvemos a VS 2010, que detectará que hubo cambios en
el proyecto y solicita, por tanto, recargarlo.
Analicemos el XAML que se ha generado desde Blend:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
x:Class="HolaMundo.MainWindow"
Title="MainWindow" Width="525" SizeToContent="Height">
<Window.Resources>
<Storyboard x:Key="ZoomStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateT
ransform.Angle)" Storyboard.TargetName="canvas">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/>
<TextBox Grid.Column="1" Grid.Row="0"/>
<Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/>
<TextBox Grid.Column="1" Grid.Row="1"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:ControlStoryboardAction
Storyboard="{StaticResource ZoomStoryboard}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel>
<TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock>
<Canvas x:Name="canvas" Width="64" Height="64"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
E
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
MássobreXAML:recursos,disparadores,transformacionesyreferencias
14
</TransformGroup>
</Canvas.RenderTransform>
<Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8"
Stroke="Black" Canvas.Top="8" Width="48"/>
<Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20"
Stroke="Black" Canvas.Top="21.12" Width="7.5"/>
<Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36"
Stroke="Black" Canvas.Top="21.12" Width="7.5"/>
<ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90"
Fill="#FFF4F4F5" Height="12" Canvas.Left="20"
Stretch="None" Stroke="Black" StartAngle="90"
Canvas.Top="36.12" Width="23.5"/>
</Canvas>
</StackPanel>
</Button>
</Grid>
</Window>
Complejo. Pero podemos analizar su contenido y comprobar que todo lo hecho con Blend, acaba
codificándose como texto en el fichero XAML.
Por una parte tenemos una sección de recursos, que es código XAML asignado a la propiedad Resources
de Window.
<Window.Resources>
<Storyboard x:Key="ZoomStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2]
.(RotateTransform.Angle)" Storyboard.TargetName="canvas">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
La sección de recursos es un diccionario que permite asociar elementos WPF a claves. La historia es que
hemos creado tiene por clave ZoomStoryboard y por valor una instancia de la clase
DoubleAnimationUsingKeyFrames. Las animaciones en WPF se forman con elementos simples. Una
DoubleAnimation, por ejemplo, es una animación consistente en el cambio de un valor de tipo de
double a lo largo del tiempo. Si vincuamos el valor de una propiedad de un elemento de la interfaz
gráfica a esa animación (como la escala, la altura, la opacidad…), su valor afectará al aspecto visual de
ese elemento. Una DoubleAnimationUsingKeyFrames es eso mismo, pero fijando tramas clave (key
frames) en las que el double debe tomar ciertos valores. En nuestro caso, en el instante 0 debe tener
valor 0 y en el instante 0:0:1 (un segundo después), debe valer 360. La animación tiene efecto sobre una
propiedad del Canvas en el que hemos puesto el smiley: el ángulo de rotación.
Después de la sección de recursos hay otra con disparadores (triggers). Los disparadores permiten
asociar acciones a eventos (entre otras cosas).
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/>
</EventTrigger>
</Window.Triggers>
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
MássobreXAML:recursos,disparadores,transformacionesyreferencias
15
Este disparador es el responsable de que tan pronto se carga la ventana se inicie la animación
ZoomStoryboard. Los valores de atributos entre llaves son especiales. En este caso se indica que la
historia que debe ejecutarse se encuentra almacenada en el diccionario de recursos con la clave
ZoomStoryboard. Si eliminamos ese disparador (o, para el caso, su sección completa), eliminaremos la
animación indeseada.
Seguimos analizando el XAML. El botón tiene este código que fija el valor de algunas “propiedades
pegadas”:
<Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:ControlStoryboardAction
Storyboard="{StaticResource ZoomStoryboard}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Los elementos en los espacios de nombres con sufijos i y ei son propios de Microsoft Expression Blend.
No entramos en detalles. Baste saber que los comportamientos (behaviors) que podemos fijar en Blend
se han incrustado en el XAML así.
El botón contiene un StackPanel y su primer elemento es un TextBlock, un bloque de texto. Su único
componente es una instancia de Run.
<TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock>
Hay varios tipos de elemento que podemos poner en un TextBlock y entre ellos destacamos Run (texto
normal), Italic (texto en cursiva) y Bold (texto en negrita). Pero no son los únicos. Y si ponemos texto a
pelo, WPF sabe que queríamos poner una marca Run y la pone por nosotros. Mucha de la
infraestructura de WPF nos permite eliminar algo de verbosidad en el código XAML (que aún así es muy
verboso).
Y llegamos por fin al Canvas:
<Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8"
Stroke="Black" Canvas.Top="8" Width="48"/>
<Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20"
Stroke="Black" Canvas.Top="21.12" Width="7.5"/>
<Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36"
Stroke="Black" Canvas.Top="21.12" Width="7.5"/>
<ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90"
Fill="#FFF4F4F5" Height="12" Canvas.Left="20" Stretch="None"
Stroke="Black" StartAngle="90" Canvas.Top="36.12" Width="23.5"/>
</Canvas>
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Accesoapropiedadesdesdecódigotrasero
16
Hay un atributo interesante con identificador x:Canvas. Permite asociar un nombre a un componente y
así poder referenciarlo desde otros puntos. De hecho, ya hemos referenciado a éste desde uno recurso:
<Window.Resources>
<Storyboard x:Key="ZoomStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2]
.(RotateTransform.Angle)"
Storyboard.TargetName="canvas">
Así es como aplica la historia a un componente concreto: fijando como valor del objetivo el
identificador o nombre del elemento. En el Canvas hay un grupo de elementos asignados al atributo
RenderTransform: con componentes que aplican una transformación afín a un elemento en el momento
de visualizarse. La expresión
(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)
Estaba seleccionando el tercer componente (índice 2) del grupo de transformación, es decir, la rotación,
y centrando el interés en la propiedad Angle, que corresponde al ángulo. La rotación tiene lugar
centrada en el centro del Canvas gracias a este otro atributo:
<Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5">
El Canvas contiene tres elipses y un arco, elemento que forma parte de un espacio de nombres propio
de Microsoft Expression.
Cabe señalar otro aspecto interesante de los valores de ciertos atributos. En las elipses podemos ver
que hay varias formas de expresar un color:
<Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8"
Stroke="Black" Canvas.Top="8" Width="48"/>
Una forma es con #AARRGGBB (alfa, rojo, verde, azul) y otra con el propio nombre del color. WPF sabe
interpretar apropiadamente el valor que se desea usar a partir de una cadena. Para ello usa un rico
juego de conversores. Con unidades de medida, por ejemplo, sabe que “48” es media pulgada, que
también podríamos expresas con “0.5in”. Y “1cm” representa un centímeto, o lo que es lo mismo,
“10mm”.
¡Ah! Y fijémonos en los atributos Canvas.Left y Canvas.Top, que permite fijar las coordenadas X e Y de la
esquina superior izquierda del rectángulo de inclusión de la elipse en el Canvas que la contiene. Ya
vimos algo parecido con Grid.Column y Grid.Row.
Acceso a propiedades desde código trasero
amos a acceder desde código trasero al valor que el usuario teclee en las cajas de texto. Para ello
necesitaremos poder acceder a las cajas de texto y deberán tener un identificador. En el código XAML
escribimos esto:
<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/>
<TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0"/>
<Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/>
<TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1"/>
V
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Enlaces
17
Volvamos al código trasero. En particular, al método que se invoca al pulsar el botón. Hagamos que el
método quede así:
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("¡Hola, " + nombre.Text + " " + apellido.Text + "!");
}
Estamos accediendo a la propiedad Text de los elementos “nombre” y “apellido”. Decimos propiedad y
no campo porque Text no es un campo de tipo cadena, sino una “propiedad C#”. Las propiedades C#
son pares de métodos que permiten acceder o asignar un valor a, en principio, un campo. Lo cierto es
que detrás puede haber un campo o no haberlo, pero el programador que usa la propiedad tiene la
ilusión de que lo hay. En realidad se ejecutará código que podría calcular el valor que se quiere leer a
partir de uno o más campos (o de ninguno). El acceso a la propiedad Text no es una mera consulta a un
campo: probablemente consista en el acceso a un diccionario y en la aplicación de operaciones que nos
permitan ver el resultado como una cadena. Pero no hay de qué preocuparse: es todo tarea de WPF y
para nosotros se crea la ilusión de acceso a un simple campo.
Enlaces
odemos enlazar diferentes elementos gráficos. Probemos a asociar el título de la ventana con el nombre
que se introduce en el formulario. Para eso hemos de asignar un nombre a la ventana y crear un enlace
(binding) que ligue su valor al del campo de texto que deseemos (y que ha de tener un nombre):
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
…
Title="{Binding ElementName=nombre, Path=Text}"
Width="525" SizeToContent="Height">
El enlace indica que hemos vincular el valor del campo Title de la ventana al del campo Text del
elemento llamado “nombre”. La sintaxis de las llaves puede reemplazarse por esta otra:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
…
Width="525" SizeToContent="Height">
<Window.Title>
<Binding ElementName="nombre" Path="Text"/>
</Window.Title>
Al ejecutar la aplicación podemos
comprobar que título de ventana y
contenido de la caja de texto con el
nombre coinciden en todo
momento. Conforme tecleamos los
caracteres del nombre, el título de la
ventana se va actualizando.
P
Figura 11. El título de la ventana está vinculado al contenido de la
primera caja de texto.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Vistasymodelos(ocasi)
18
Vistas y modelos (o casi)
o usual en una aplicación interactiva bien diseñada es que haya una clara separación entre la
representación de un objeto (la vista) y el propio objeto (el modelo). De hecho, lo ideal es que el
modelo dependa lo menos posible de la interfaz gráfica. Vamos a hacerlo ahora en nuestra aplicación,
aunque resultará un tanto impostado por ser ésta muy sencilla.
Creamos una nueva clase Persona. En el menú contextual del proyecto HolaMundo seleccionamos
AddClass… y creamos la clase Persona, que pasa a definirse así:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace HolaMundo
{
class Persona : INotifyPropertyChanged
{
private string _nombre;
private string _apellido;
public string NombreCompleto
{
get { return Nombre + " " + Apellido; }
}
public string Nombre
{
get { return _nombre; }
set
{
if (value != _nombre)
{
_nombre = value;
NotifyChange("Nombre");
NotifyChange("NombreCompleto");
}
}
}
public string Apellido
{
get { return _apellido; }
set
{
if (value != _apellido)
{
_apellido = value;
NotifyChange("Apellido");
NotifyChange("NombreCompleto");
}
}
}
void NotifyChange(string id)
L
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Vistasymodelos(ocasi)
19
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(id));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
La idea ahora es que el campo Nombre y el campo Apellido de una instancia de Persona estén siempre
sincronizados con las cajas de texto correspondientes en el formulario. Hemos creado cierta
infraestructura al efecto. Por una parte, Persona implementa la interfaz INotifyPropertyChanged. La
interfaz obliga a que se implemente un evento que se disparará cada vez que alguien modifique el valor
de una propiedad, avisando así a los suscriptores del evento. Nótese que el cambio del nombre o el
apellido no sólo cambia Nombre y Apellido, respectivamente: también cambia NombreCompleto.
Hemos de crear una instancia de Persona e indicar que el “contexto de datos” (DataContext) de la
ventana principal es esa instancia:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 HolaMundo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public readonly Persona LaPersona;
public MainWindow()
{
InitializeComponent();
LaPersona = new Persona {
Nombre = "Tu nombre",
Apellido = "Tu apellido"
};
DataContext = LaPersona;
}
private void Button_Click(object sender, RoutedEventArgs e)
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Propiedadesdedependencia
20
{
MessageBox.Show("¡Hola, " + LaPersona.NombreCompleto + "!");
}
}
}
Y ahora, vinculemos el contenido de las cajas de texto con los de LaPersona. De paso, vincularemos el
título con el nombre completo:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
x:Class="HolaMundo.MainWindow"
Width="525" SizeToContent="Height">
<Window.Title>
<Binding Path="NombreCompleto"/>
</Window.Title>
…
<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/>
<TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0" Text="{Binding Nombre}"/>
<Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/>
<TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1" Text="{Binding Apellido}"/>
Y ya está. Nuestra ventana tiene por título el nombre completo y la instancia LaPersona siempre está
sincronizada con el formulario.
Propiedades de dependencia
PF ofrece una herramienta muy interesante para crear propiedades que notifican automáticamente de
los cambios que experimentan: las propiedades de dependencia. De hecho, las propiedades de los
elementos WPF son realmente propiedades de dependencia. Estas propiedades no sólo notifican de los
cambios que experimentan: tienen valores por defecto, se pueden heredar sus valores en la jerarquía de
objetos, se pueden ligar a otras propiedades de dependencia, pueden usarse en animaciones y, lo que
quizá es más importante: no consumen memoria si no se les asigna un valor. Las propiedades de
dependencia se almacenan en un diccionario cuando se les asigna un valor. Si no lo tienen, WPF se
encarga de acceder al valor por defecto automáticamente. Se trata de un factor muy importante si
tenemos en cuenta que un elemento WPF puede tener más de medio centenar de propiedades.
Convirtamos nuestra persona en un objeto con propiedades de dependencia y, de momento, olvidemos
la sincronización del título de la ventana con el nombre completo. Más tarde recuperaremos esa
funcionalidad.
La nueva definición de Persona es ésta:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
W
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Propiedadesdedependencia
21
namespace HolaMundo
{
public class Persona : DependencyObject
{
public string NombreCompleto
{
get { return Nombre + " " + Apellido; }
}
public string Nombre
{
get { return (string)GetValue(NombreProperty); }
set { SetValue(NombreProperty, value); }
}
public static readonly DependencyProperty NombreProperty =
DependencyProperty.Register("Nombre", typeof(string),
typeof(Persona), new UIPropertyMetadata(""));
public string Apellido
{
get { return (string)GetValue(ApellidoProperty); }
set { SetValue(ApellidoProperty, value); }
}
public static readonly DependencyProperty ApellidoProperty =
DependencyProperty.Register("Apellido", typeof(string),
typeof(Persona), new UIPropertyMetadata(""));
}
}
Complicado, ¿no? Afortunadamente VS 2010 nos ayuda con los denominados snippets, plantillas con
fragmentos de código fácilmente utilizables. Si tecleamos propdp (por “property: dependency property”),
el editor nos ofrece una plantilla como ésta:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty…
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int),
typeof(ownerclass), new UIPropertyMetadata(0));
Con la ayuda del tabulador podemos asignar valor a los cuatro campos de la plantilla (que aparecen
con fondo verde en este documento).
La clase Persona hereda de DependencyObject, clase que ofrece la infraestructura necesaria para
soportar propiedades de dependencia. Una propiedad de dependencia es un objeto estático que se
registra en un diccionario con el método DependencyProperty.Register. Las instancias de un
DependencyObject pueden acceder al valor de su propiedad de dependencia con el método GetValue,y
asignarle un valor con SetValue (ambos heredados de DependencyObject). La propiedad C# que da
acceso a estos métodos nos facilita el acceso a su lógica.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
UnaintroducciónalpatrónarquitectónicoMVVM
22
No hemos tenido que notificar los cambios porque las propiedades de dependencia ya se ocupan de
ello automáticamente. Como NombreCompleto no es una propiedad de dependencia y ya no notifica de
los cambios, hemos perdido esa funcionalidad. No costaría nada recuperarla volviendo a implementar el
notificador de cambios en propiedades. Pero es mejor que pasemos a hablar de un patrón de diseño
importante en el mundo WPF: el patrón Modelo-Vista-Modelo de la Vista, o MVVM, por Model-View-
ViewModel.
Una introducción al patrón arquitectónico MVVM
a separación entre vista y modelo que hemos hecho no es buena. Hemos acabado por tocar el modelo
para ajustarlo a la vista hasta el punto de construirlo con componentes de WPF, y eso es muy mala
práctica. Normalmente el modelo nos viene dado y tenemos poca capacidad de influencia sobre él.
Este problema es crucial en el diseño de aplicaciones
interactivas, salvo en las más triviales. Desde el inicio de la
programación de aplicaciones interactivas se plantearon la
cuestión de la separación de responsabilidades en este tipo
de sistemas. Un patrón exitoso es el conocido como
Modelo-Vista-Controlador o MVC, por Model-View-
Controller, que divide el sistema en tres componentes: el
modelo, la vista y el controlador. Podemos representar
gráficamente el concepto como se muestra en la Figura 12.
Un patrón de diseño, como MVC, no es una receta estructa
acerca de cómo implementar cierta funcionalidad, sino una
serie de criterios que deben considerarse al diseñar la
arquitectura de la aplicación e implementarla.
El usuario interactúa con dispositivos que “hablan” con un
controlador, el cual manipula un modelo (los datos) cuyo cambio fuerza la actualización de una vista,
que es lo que percibe el usuario. Hoy día el modelo presenta ciertas dificultades y se considera
superado por otros, como el denominado MVP, por Model-View-Presenter. También es un patrón con
tres componentes. En este caso se trata del modelo, la vista y el presentador. Vista y modelo parece
claro lo que son, pero ¿qué es el presentador? Es una capa entre la vista y el modelo. Cuando la vista
necesita algo del modelo, se lo solicita al presentador, que ofrece métodos que hacen cómodo para la
vista el acceso a los datos relevantes del modelo. Supongamos que el modelo tiene la fecha de
nacimiento de una persona, pero no la edad. El presentador podría ofrecer un método o propiedad
Edad que accediese a la fecha de nacimiento y al día actual para proporcionar el dato deseado. Y en
sentido inverso, cuando la vista necesita modificar el modelo, lo hace a través del presentador, que sabe
cómo “traducir” elementos de la vista en datos del modelo. También modelo y presentador interactúan:
el presentador lee y escribe sus datos y cuando el modelo cambia “espontáneamente” (es decir, por
eventos no relacionados con la interacción con el usuario), notifica al presentador de los cambios y éste
se encarga de actualizar la vista.
El patrón arquitectónico MVVM es una versión especializada de MVP para WPF. Establece mecanismos
propios de WPF para la comunicación Vista-Presentador (que aquí se denomina Modelo de la Vista). En
particular y limita las herramientas que podemos usar en cada capa.
L
Figura 12. Diagrama del patrón Modelo-
Vista-Controlador. (Imagen extraída de
Wikipedia.)
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
UnaintroducciónalpatrónarquitectónicoMVVM
23
 La vista es XAML 100% (o casi).
 El modelo de la vista expone lógica vía órdenes (de las que nos ocupamos en breve), expone el
modelo mediante ligaduras entre propiedades y propiedades de dependencia y mantiene
información de estado de la interacción (pero sólo de la interacción).
 El modelo mantiene los datos y sus operaciones, pero no lógica dependiente de cómo se usa.
(Si cambia espontáneamente, implementa un notificador de cambios en propiedades.)
Ahora vamos a seguir el patrón MVVM para que la aplicación vuelva a funcionar. Devolvamos el modelo
a una versión minimalista. El fichero Persona pasa a contener este texto:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HolaMundo
{
public class Persona
{
public string Nombre { get; set; }
public string Apellido { get; set; }
public string NombreCompleto { get { return Nombre + " " + Apellido; } }
}
}
El modelo no “sabe” nada de WPF ni de cómo se usará en la aplicación. Se limita a mantener un par de
datos. Podría tener, además, operaciones para serializar el objeto, almacenarlo en disco, etcétera.
Preparemos una clase para el modelo de la vista: MainWindowViewModel. Recordemos que su papel es
hacer de puente entre la vista y el modelo.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace HolaMundo
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public Persona Model;
public string Nombre
{
get { return Model.Nombre;}
set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); }
}
public string Apellido
{
get { return Model.Apellido; }
set { Model.Apellido = value;
NotifyChange("Apellido", "NombreCompleto"); }
}
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
UnaintroducciónalpatrónarquitectónicoMVVM
24
public string NombreCompleto
{
get { return Model.Nombre + " " + Model.Apellido; }
}
void NotifyChange(params string[] ids)
{
if (PropertyChanged != null)
foreach (var id in ids)
PropertyChanged(this, new PropertyChangedEventArgs(id));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Hemos de preocuparnos ahora de vincular Vista, Modelo y Modelo de la Vista. Lo haremos modificando
en App.xaml el arranque de la aplicación. Su contenido actual es éste:
<Application x:Class="HolaMundo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Y pasa a ser este otro:
<Application x:Class="HolaMundo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources>
</Application.Resources>
</Application>
El método Application_Startup se definirá en App.xaml.cs así:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Input;
namespace HolaMundo
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Órdenes:elpatrónCommand
25
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var model = new Persona { Nombre = "Tu nombre",
Apellido = "Tu apellido" };
var viewModel = new MainWindowViewModel {Model = model};
var view = new MainWindow {DataContext = viewModel};
view.Show();
}
}
}
Hemos creado un modelo, que se almacena en el modelo de la vista tan pronto se crea, y hemos creado
una vista cuyo contexto de datos es el modelo de la vista. La última acción consiste en mostrar la vista,
que es la ventana principal.
Para que la aplicación funciones hemos de eliminar las referencias a LaPersona o sus campos. Una de
ellas está en el método Button_Click, asociado al evento Click del Button. Vamos a deshacernos de los
eventos, pues no son recomendables en una aplicación MVVM.
Órdenes: el patrón Command
l uso de eventos no es, en general, recomendable en aplicaciones de tamaño moderado o grande. Los
eventos crean referencias entre objetos que pueden prolongar la vida de éstos más allá de lo que el
programador supone. Es la causa principal de las fugas de memoria en aplicaciones .NET, por lo que
conviene tomar medidas profilácticas.
WPF soporta el patrón de diseño “orden” (command) que permite asociar lógica a acciones interactivas.
Y no sólo eso: permite también habilitar o deshabilitar la interacción de ciertos componentes en función
del estado de los datos.
En principio hemos de definir una clase que implemente la interfaz ICommand para cada orden del
sistema. Pero resulta más cómodo usar una clase única a la que suministrar, mediante delegados o
funciones anónimas, la lógica que deseamos. Esta clase suele denominarse RelayCommand o
DelegateCommand. Esta es nuestra versión:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
E
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Órdenes:elpatrónCommand
26
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
Antes de estudiarla, veamos cómo usarla. Creemos una orden que se dispare cuando pulsamos el botón
“saluda”. El lugar natural para las órdenes es el modelo de la vista. Este es el código que le corresponde:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace HolaMundo
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public Persona Model;
private readonly ICommand _saludaCommand;
public ICommand SaludaCommand
{
get { return _saludaCommand; }
}
public MainWindowViewModel()
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Órdenes:elpatrónCommand
27
{
_saludaCommand = new RelayCommand(
o => MessageBox.Show("¡Hola, " + NombreCompleto + "!"),
o => !string.IsNullOrEmpty(NombreCompleto.Trim()));
}
public string Nombre
{
get { return Model.Nombre;}
set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); }
}
public string Apellido
{
get { return Model.Apellido; }
set { Model.Apellido = value;
NotifyChange("Apellido", "NombreCompleto"); }
}
public string NombreCompleto
{
get { return Model.Nombre + " " + Model.Apellido; }
}
void NotifyChange(params string[] ids)
{
if (PropertyChanged != null)
foreach (var id in ids)
PropertyChanged(this, new PropertyChangedEventArgs(id));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Al construir el RelayCommand proporcionamos dos funciones (anónimas en este caso): una que indica
qué hacer y la otra qué nos permite saber si la orden puede ejecutarse. En nuestro caso, el qué hacer es
lo de siempre: mostrar el diálogo modal. Y la orden podrá ejecutarse siempre que el nombre completo
contenga algún carácter no blanco.
El ICommand que se ejecutará al pulsar el botón se expone como propiedad de sólo lectura. Falta
vincular la orden a la pulsación del botón en MainWindow.xaml:
<Button Grid.Row="2" Grid.ColumnSpan="2" Command="{Binding SaludaCommand}">
Y ya está. Ejecutemos la aplicación y
comprobemos que el botón funciona.
Y comprobemos también que cuando
no hay texto en las cajas de texto no
podemos pulsar el botón, como se
muestra en la Figura 13.
Figura 13. El botón aparece deshabilitado porque no hay texto en
las cajas.
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Aúnhaymás,peronoparahoy
28
Podría pensarse que no hemos ganado mucho con las órdenes, pero el patrón orden aisla
efectivamente la lógica de la vista y la encapsula en una entidad que reúne la acción con un método
que permite saber cuándo es posible ejecutarla. Si ahora quisiésemos que otros elementos (opciones de
menú, gestos de teclados, etcétera) disparasen esa misma lógica, bastaría con asociarlos al mismo
RelayCommand. Todos esos elementos se activarían y desactivarían gracias al método CanExecute y
dispararían, cuando fuera posible, la misma acción vía Execute.
Aún hay más, pero no para hoy
s imposible presentar todos los elementos que conforman WPF en una simple charla. He pretendido
mostrar algunos elementos básicos e introducir, aunque sea someramente, el patrón arquitectónico de
preferencia: MVVM. Entre lo que nos dejamos en el tintero:
 Infinidad de controles.
 Diferentes tipos de animaciones.
 Estilos y plantillas que permiten personalizar prácticamente todo.
 Gestión de colecciones que facilita la interacción con listas o árboles de datos.
 La conexión a fuentes de datos provenientes de bases de datos o XML.
 El modelo de navegación.
 La versión empotrable en páginas web (Silverlight).
 Los componentes multimedia.
 Los efectos de bitmap.
 Diseño de controles y paneles personalizados.
 El trabajo con elementos 3D.
 El diseño de pruebas unitarias para componentes MVVM.
 Librerías de ayuda para el diseño de aplicaciones MVVM.
 Uso de inyección de dependencias para facilitar la asociación entre elementos de la tríada
MVVM.
 Extensiones para tinta, tacto, etcétera.
 Conversores.
 Eventos y órdenes enrutadas.
Espero haber despertado la curiosidad por WPF y C# para que acudáis ahora a las fuentes bibliográficas
o los blogs temáticos para aprender más.
E
Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal
Fuentesbibliográficasrecomendables
29
Fuentes bibliográficas recomendables
ólo queda recomendar algunos libros que permiten profundizar en WPF.
Windows Presentation Foundation Unleashed, de Adam Natham.
(Sacará una nueva edición en breve para cubrir WPF 4.0.)
Programming WPF, 2ª edición, de Chris Sells.
Essential Windows Presentation Foundations, de Chris Anderson.
Applications = Code + Markup: A Guide to the Microsoft Windows
Presentation Foundation, de Charles Petzold.
Hay también varios blogs recomendables. Entre ellos, os cito estos:
 http://blogs.msdn.com/llobo/
 http://sachabarber.net/
 http://joshsmithonwpf.wordpress.com/
 http://blogs.msdn.com/jgoldb/default.aspx
 http://houseofbilz.com/Default.aspx
 http://jesseliberty.com/
S

Más contenido relacionado

La actualidad más candente

La actualidad más candente (20)

Manual de Java
Manual de JavaManual de Java
Manual de Java
 
Microsoft Asp. Net [Asp.Net - Parte 2]
Microsoft Asp. Net [Asp.Net - Parte 2]Microsoft Asp. Net [Asp.Net - Parte 2]
Microsoft Asp. Net [Asp.Net - Parte 2]
 
Integración de aplicaciones Java
Integración de aplicaciones JavaIntegración de aplicaciones Java
Integración de aplicaciones Java
 
Frameworks J2EE
Frameworks J2EEFrameworks J2EE
Frameworks J2EE
 
Desarrollo de aplicasiones .net
Desarrollo de aplicasiones .netDesarrollo de aplicasiones .net
Desarrollo de aplicasiones .net
 
Bloques
BloquesBloques
Bloques
 
Introducción a Javascript I
Introducción a Javascript IIntroducción a Javascript I
Introducción a Javascript I
 
Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011
 
Asp.net
Asp.netAsp.net
Asp.net
 
Presentacion wpf
Presentacion wpfPresentacion wpf
Presentacion wpf
 
patron de diseño MVVMo.pptx
patron de diseño MVVMo.pptxpatron de diseño MVVMo.pptx
patron de diseño MVVMo.pptx
 
C Sharp Avanzado Dia 1
C Sharp Avanzado   Dia 1C Sharp Avanzado   Dia 1
C Sharp Avanzado Dia 1
 
Rails intro
Rails introRails intro
Rails intro
 
Consultas linq
Consultas linqConsultas linq
Consultas linq
 
Desarrollo de Apps Web en Ruby on Rails
Desarrollo de Apps Web en Ruby on RailsDesarrollo de Apps Web en Ruby on Rails
Desarrollo de Apps Web en Ruby on Rails
 
Javascript Básico
Javascript BásicoJavascript Básico
Javascript Básico
 
Introducción ASP .NET
Introducción ASP .NET Introducción ASP .NET
Introducción ASP .NET
 
Foro 1
Foro 1Foro 1
Foro 1
 
Fundamentos de Visual Basic
Fundamentos de Visual Basic Fundamentos de Visual Basic
Fundamentos de Visual Basic
 
Manual tutorial-sql
Manual tutorial-sqlManual tutorial-sql
Manual tutorial-sql
 

Destacado

ERP SAP Aprendizaje Modulo Finanzas parte 3
ERP SAP Aprendizaje Modulo Finanzas parte 3ERP SAP Aprendizaje Modulo Finanzas parte 3
ERP SAP Aprendizaje Modulo Finanzas parte 3magister845
 
ERP SAP Aprendizaje Modulo Finanzas parte 2
ERP SAP Aprendizaje Modulo Finanzas parte 2ERP SAP Aprendizaje Modulo Finanzas parte 2
ERP SAP Aprendizaje Modulo Finanzas parte 2magister845
 
Introduccion Sistemas Gestion Empresarial ERP
Introduccion Sistemas Gestion Empresarial ERPIntroduccion Sistemas Gestion Empresarial ERP
Introduccion Sistemas Gestion Empresarial ERPmagister845
 
ERP SAP Aprendizaje Modulo Logistica parte 2
ERP SAP Aprendizaje Modulo Logistica parte 2ERP SAP Aprendizaje Modulo Logistica parte 2
ERP SAP Aprendizaje Modulo Logistica parte 2magister845
 
ERP SAP Aprendizaje Modulo Finanzas parte 1
ERP SAP Aprendizaje Modulo Finanzas parte 1ERP SAP Aprendizaje Modulo Finanzas parte 1
ERP SAP Aprendizaje Modulo Finanzas parte 1magister845
 
ERP SAP Aprendizaje Modulo Logistica parte 1
ERP SAP Aprendizaje Modulo Logistica parte 1ERP SAP Aprendizaje Modulo Logistica parte 1
ERP SAP Aprendizaje Modulo Logistica parte 1magister845
 
Uso de texto, tabla o imágenes en dw
Uso de texto, tabla o imágenes en dwUso de texto, tabla o imágenes en dw
Uso de texto, tabla o imágenes en dwoatb33
 
Reinventar la escuela sed bogota
Reinventar la escuela sed bogotaReinventar la escuela sed bogota
Reinventar la escuela sed bogotapastoraledu
 
Antonio machado. Biografía
Antonio machado. BiografíaAntonio machado. Biografía
Antonio machado. Biografíakikecober
 
Diario de viaje ...
Diario de viaje                                                              ...Diario de viaje                                                              ...
Diario de viaje ...mspector64
 
Resultados objetivos sanitarios cancer
Resultados objetivos sanitarios cancerResultados objetivos sanitarios cancer
Resultados objetivos sanitarios cancerEvelyn Ríos C
 
Presentacion Marcelino Bueno en Business de Baleares
Presentacion Marcelino Bueno en Business de BalearesPresentacion Marcelino Bueno en Business de Baleares
Presentacion Marcelino Bueno en Business de BalearesGabriele Bernetti
 

Destacado (20)

Framework .net
Framework .netFramework .net
Framework .net
 
ERP SAP Aprendizaje Modulo Finanzas parte 3
ERP SAP Aprendizaje Modulo Finanzas parte 3ERP SAP Aprendizaje Modulo Finanzas parte 3
ERP SAP Aprendizaje Modulo Finanzas parte 3
 
ERP SAP Aprendizaje Modulo Finanzas parte 2
ERP SAP Aprendizaje Modulo Finanzas parte 2ERP SAP Aprendizaje Modulo Finanzas parte 2
ERP SAP Aprendizaje Modulo Finanzas parte 2
 
Introduccion Sistemas Gestion Empresarial ERP
Introduccion Sistemas Gestion Empresarial ERPIntroduccion Sistemas Gestion Empresarial ERP
Introduccion Sistemas Gestion Empresarial ERP
 
ERP SAP Aprendizaje Modulo Logistica parte 2
ERP SAP Aprendizaje Modulo Logistica parte 2ERP SAP Aprendizaje Modulo Logistica parte 2
ERP SAP Aprendizaje Modulo Logistica parte 2
 
ERP SAP Aprendizaje Modulo Finanzas parte 1
ERP SAP Aprendizaje Modulo Finanzas parte 1ERP SAP Aprendizaje Modulo Finanzas parte 1
ERP SAP Aprendizaje Modulo Finanzas parte 1
 
ERP SAP Aprendizaje Modulo Logistica parte 1
ERP SAP Aprendizaje Modulo Logistica parte 1ERP SAP Aprendizaje Modulo Logistica parte 1
ERP SAP Aprendizaje Modulo Logistica parte 1
 
Proyectos multimedia
Proyectos multimediaProyectos multimedia
Proyectos multimedia
 
Que es la web 2.0
Que es la web 2.0Que es la web 2.0
Que es la web 2.0
 
Uso de texto, tabla o imágenes en dw
Uso de texto, tabla o imágenes en dwUso de texto, tabla o imágenes en dw
Uso de texto, tabla o imágenes en dw
 
Presentación1
Presentación1Presentación1
Presentación1
 
Reinventar la escuela sed bogota
Reinventar la escuela sed bogotaReinventar la escuela sed bogota
Reinventar la escuela sed bogota
 
Antonio machado. Biografía
Antonio machado. BiografíaAntonio machado. Biografía
Antonio machado. Biografía
 
Presentac..
Presentac..Presentac..
Presentac..
 
Tacita
TacitaTacita
Tacita
 
Nuevo areopuerto de quito
Nuevo areopuerto de quitoNuevo areopuerto de quito
Nuevo areopuerto de quito
 
Diodos
DiodosDiodos
Diodos
 
Diario de viaje ...
Diario de viaje                                                              ...Diario de viaje                                                              ...
Diario de viaje ...
 
Resultados objetivos sanitarios cancer
Resultados objetivos sanitarios cancerResultados objetivos sanitarios cancer
Resultados objetivos sanitarios cancer
 
Presentacion Marcelino Bueno en Business de Baleares
Presentacion Marcelino Bueno en Business de BalearesPresentacion Marcelino Bueno en Business de Baleares
Presentacion Marcelino Bueno en Business de Baleares
 

Similar a Net y WPF

Andre.paola9 blos blospot blogger
Andre.paola9 blos blospot bloggerAndre.paola9 blos blospot blogger
Andre.paola9 blos blospot bloggerpaolatublog
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Wilberth Rojas Aguilar
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010David Vázquez Palestino
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010David Vázquez Palestino
 
Tecnologia microsoft .net
Tecnologia microsoft .netTecnologia microsoft .net
Tecnologia microsoft .netlilymejia1979
 
Tecnologia Microsoft
Tecnologia  MicrosoftTecnologia  Microsoft
Tecnologia Microsoftbrobelo
 
Tecnologia microsoft .net
Tecnologia microsoft .netTecnologia microsoft .net
Tecnologia microsoft .netlilymejia1979
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Alex Barrios
 
Programacion
ProgramacionProgramacion
Programaciondanielrsd
 
itio de realizacion de un blog blogs blogspot
itio de realizacion de un blog blogs blogspotitio de realizacion de un blog blogs blogspot
itio de realizacion de un blog blogs blogspotpaolatublog
 

Similar a Net y WPF (20)

Andre.paola9 blos blospot blogger
Andre.paola9 blos blospot bloggerAndre.paola9 blos blospot blogger
Andre.paola9 blos blospot blogger
 
Andre.paola9
Andre.paola9Andre.paola9
Andre.paola9
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010
 
Tecnologia microsoft .net
Tecnologia microsoft .netTecnologia microsoft .net
Tecnologia microsoft .net
 
Tecnologia Microsoft
Tecnologia  MicrosoftTecnologia  Microsoft
Tecnologia Microsoft
 
Tecnologia microsoft .net
Tecnologia microsoft .netTecnologia microsoft .net
Tecnologia microsoft .net
 
Lenguaje C sharp
Lenguaje C sharpLenguaje C sharp
Lenguaje C sharp
 
Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010Curso.de.introducción.net.con.visual.basic.2010
Curso.de.introducción.net.con.visual.basic.2010
 
Programacion
ProgramacionProgramacion
Programacion
 
1 introduccion.net
1 introduccion.net1 introduccion.net
1 introduccion.net
 
1 introduccion.net
1 introduccion.net1 introduccion.net
1 introduccion.net
 
1 introduccion.net
1 introduccion.net1 introduccion.net
1 introduccion.net
 
Paola
PaolaPaola
Paola
 
Paola
PaolaPaola
Paola
 
Paola
PaolaPaola
Paola
 
itio de realizacion de un blog blogs blogspot
itio de realizacion de un blog blogs blogspotitio de realizacion de un blog blogs blogspot
itio de realizacion de un blog blogs blogspot
 
Paola
PaolaPaola
Paola
 
Paola 1001 jt
Paola 1001 jtPaola 1001 jt
Paola 1001 jt
 

Más de magister845

ERP SAP Aprendizaje Modulo Logistica parte 3
ERP SAP Aprendizaje Modulo Logistica parte 3ERP SAP Aprendizaje Modulo Logistica parte 3
ERP SAP Aprendizaje Modulo Logistica parte 3magister845
 
Listado Sociedades CO
Listado Sociedades COListado Sociedades CO
Listado Sociedades COmagister845
 
Listado Sociedades
Listado SociedadesListado Sociedades
Listado Sociedadesmagister845
 
Listado Moneda de la Cuenta
Listado Moneda de la CuentaListado Moneda de la Cuenta
Listado Moneda de la Cuentamagister845
 
Listado Grupo Cuentas
Listado Grupo CuentasListado Grupo Cuentas
Listado Grupo Cuentasmagister845
 
Listado Cuenta Tipo
Listado Cuenta TipoListado Cuenta Tipo
Listado Cuenta Tipomagister845
 
Listado Clase Cuenta
Listado Clase CuentaListado Clase Cuenta
Listado Clase Cuentamagister845
 
Notas macros excel
Notas macros excelNotas macros excel
Notas macros excelmagister845
 
Apuntes macros excel
Apuntes  macros excelApuntes  macros excel
Apuntes macros excelmagister845
 
Neurociencias y aprendizajes art18
Neurociencias y aprendizajes               art18Neurociencias y aprendizajes               art18
Neurociencias y aprendizajes art18magister845
 
Presion arterial-gama
Presion arterial-gamaPresion arterial-gama
Presion arterial-gamamagister845
 

Más de magister845 (12)

ERP SAP Aprendizaje Modulo Logistica parte 3
ERP SAP Aprendizaje Modulo Logistica parte 3ERP SAP Aprendizaje Modulo Logistica parte 3
ERP SAP Aprendizaje Modulo Logistica parte 3
 
Listado Sociedades CO
Listado Sociedades COListado Sociedades CO
Listado Sociedades CO
 
Listado Sociedades
Listado SociedadesListado Sociedades
Listado Sociedades
 
Listado Moneda de la Cuenta
Listado Moneda de la CuentaListado Moneda de la Cuenta
Listado Moneda de la Cuenta
 
Listado Grupo Cuentas
Listado Grupo CuentasListado Grupo Cuentas
Listado Grupo Cuentas
 
Listado Cuenta Tipo
Listado Cuenta TipoListado Cuenta Tipo
Listado Cuenta Tipo
 
Listado Clase Cuenta
Listado Clase CuentaListado Clase Cuenta
Listado Clase Cuenta
 
Notas macros excel
Notas macros excelNotas macros excel
Notas macros excel
 
Apuntes macros excel
Apuntes  macros excelApuntes  macros excel
Apuntes macros excel
 
Neurociencias y aprendizajes art18
Neurociencias y aprendizajes               art18Neurociencias y aprendizajes               art18
Neurociencias y aprendizajes art18
 
Javascript dom
Javascript domJavascript dom
Javascript dom
 
Presion arterial-gama
Presion arterial-gamaPresion arterial-gama
Presion arterial-gama
 

Último

ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJO
ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJOACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJO
ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJOBRIGIDATELLOLEONARDO
 
Estrategia de prompts, primeras ideas para su construcción
Estrategia de prompts, primeras ideas para su construcciónEstrategia de prompts, primeras ideas para su construcción
Estrategia de prompts, primeras ideas para su construcciónLourdes Feria
 
SESION DE PERSONAL SOCIAL. La convivencia en familia 22-04-24 -.doc
SESION DE PERSONAL SOCIAL.  La convivencia en familia 22-04-24  -.docSESION DE PERSONAL SOCIAL.  La convivencia en familia 22-04-24  -.doc
SESION DE PERSONAL SOCIAL. La convivencia en familia 22-04-24 -.docRodneyFrankCUADROSMI
 
Proyecto de aprendizaje dia de la madre MINT.pdf
Proyecto de aprendizaje dia de la madre MINT.pdfProyecto de aprendizaje dia de la madre MINT.pdf
Proyecto de aprendizaje dia de la madre MINT.pdfpatriciaines1993
 
Feliz Día de la Madre - 5 de Mayo, 2024.pdf
Feliz Día de la Madre - 5 de Mayo, 2024.pdfFeliz Día de la Madre - 5 de Mayo, 2024.pdf
Feliz Día de la Madre - 5 de Mayo, 2024.pdfMercedes Gonzalez
 
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESO
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESOPrueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESO
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESOluismii249
 
INSTRUCCION PREPARATORIA DE TIRO .pptx
INSTRUCCION PREPARATORIA DE TIRO   .pptxINSTRUCCION PREPARATORIA DE TIRO   .pptx
INSTRUCCION PREPARATORIA DE TIRO .pptxdeimerhdz21
 
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLA
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLAACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLA
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLAJAVIER SOLIS NOYOLA
 
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdf
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdfNUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdf
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdfUPTAIDELTACHIRA
 
Infografía EE con pie del 2023 (3)-1.pdf
Infografía EE con pie del 2023 (3)-1.pdfInfografía EE con pie del 2023 (3)-1.pdf
Infografía EE con pie del 2023 (3)-1.pdfAlfaresbilingual
 
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdfMiguelHuaman31
 
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...JAVIER SOLIS NOYOLA
 
Concepto y definición de tipos de Datos Abstractos en c++.pptx
Concepto y definición de tipos de Datos Abstractos en c++.pptxConcepto y definición de tipos de Datos Abstractos en c++.pptx
Concepto y definición de tipos de Datos Abstractos en c++.pptxFernando Solis
 
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VS
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VSOCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VS
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VSYadi Campos
 
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).ppt
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).pptPINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).ppt
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).pptAlberto Rubio
 

Último (20)

ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJO
ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJOACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJO
ACTIVIDAD DIA DE LA MADRE FICHA DE TRABAJO
 
Estrategia de prompts, primeras ideas para su construcción
Estrategia de prompts, primeras ideas para su construcciónEstrategia de prompts, primeras ideas para su construcción
Estrategia de prompts, primeras ideas para su construcción
 
SESION DE PERSONAL SOCIAL. La convivencia en familia 22-04-24 -.doc
SESION DE PERSONAL SOCIAL.  La convivencia en familia 22-04-24  -.docSESION DE PERSONAL SOCIAL.  La convivencia en familia 22-04-24  -.doc
SESION DE PERSONAL SOCIAL. La convivencia en familia 22-04-24 -.doc
 
Medición del Movimiento Online 2024.pptx
Medición del Movimiento Online 2024.pptxMedición del Movimiento Online 2024.pptx
Medición del Movimiento Online 2024.pptx
 
Proyecto de aprendizaje dia de la madre MINT.pdf
Proyecto de aprendizaje dia de la madre MINT.pdfProyecto de aprendizaje dia de la madre MINT.pdf
Proyecto de aprendizaje dia de la madre MINT.pdf
 
Interpretación de cortes geológicos 2024
Interpretación de cortes geológicos 2024Interpretación de cortes geológicos 2024
Interpretación de cortes geológicos 2024
 
Tema 11. Dinámica de la hidrosfera 2024
Tema 11.  Dinámica de la hidrosfera 2024Tema 11.  Dinámica de la hidrosfera 2024
Tema 11. Dinámica de la hidrosfera 2024
 
Feliz Día de la Madre - 5 de Mayo, 2024.pdf
Feliz Día de la Madre - 5 de Mayo, 2024.pdfFeliz Día de la Madre - 5 de Mayo, 2024.pdf
Feliz Día de la Madre - 5 de Mayo, 2024.pdf
 
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESO
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESOPrueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESO
Prueba de evaluación Geografía e Historia Comunidad de Madrid 2º de la ESO
 
INSTRUCCION PREPARATORIA DE TIRO .pptx
INSTRUCCION PREPARATORIA DE TIRO   .pptxINSTRUCCION PREPARATORIA DE TIRO   .pptx
INSTRUCCION PREPARATORIA DE TIRO .pptx
 
Unidad 3 | Metodología de la Investigación
Unidad 3 | Metodología de la InvestigaciónUnidad 3 | Metodología de la Investigación
Unidad 3 | Metodología de la Investigación
 
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLA
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLAACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLA
ACERTIJO DE POSICIÓN DE CORREDORES EN LA OLIMPIADA. Por JAVIER SOLIS NOYOLA
 
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdf
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdfNUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdf
NUEVAS DIAPOSITIVAS POSGRADO Gestion Publica.pdf
 
Infografía EE con pie del 2023 (3)-1.pdf
Infografía EE con pie del 2023 (3)-1.pdfInfografía EE con pie del 2023 (3)-1.pdf
Infografía EE con pie del 2023 (3)-1.pdf
 
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf
2024 KIT DE HABILIDADES SOCIOEMOCIONALES.pdf
 
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...
LABERINTOS DE DISCIPLINAS DEL PENTATLÓN OLÍMPICO MODERNO. Por JAVIER SOLIS NO...
 
Concepto y definición de tipos de Datos Abstractos en c++.pptx
Concepto y definición de tipos de Datos Abstractos en c++.pptxConcepto y definición de tipos de Datos Abstractos en c++.pptx
Concepto y definición de tipos de Datos Abstractos en c++.pptx
 
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VS
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VSOCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VS
OCTAVO SEGUNDO PERIODO. EMPRENDIEMIENTO VS
 
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).ppt
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).pptPINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).ppt
PINTURA DEL RENACIMIENTO EN ESPAÑA (SIGLO XVI).ppt
 
Supuestos_prácticos_funciones.docx
Supuestos_prácticos_funciones.docxSupuestos_prácticos_funciones.docx
Supuestos_prácticos_funciones.docx
 

Net y WPF

  • 1. Desarrollo de aplicaciones con .NET y WPF Andrés Marzal Departamento de Lenguajes y Sistemas Informáticos Universitat Jaume I Decharlas, 24 de mayo de 2010 ¿Qué es .NET? ¿Qué es C#? ¿Qué es WPF? ¿Qué es Visual Studio? ¿Qué es Expression Blend? odemos simplificar mucho y decir que .NET es la respuesta de Microsoft a Java. .NET ofrece un entorno de ejecución con máquina virtual para un lenguaje de máquina propio: IL, por Intermediate Language. Diferentes lenguajes se traducen a ese lenguaje de máquina y un compilador de última hora genera código nativo, que es lo que realmente se ejecuta. .NET sigue un estándar ECMA: “Standard ECMA-335, Common Language Infrastructure (CLI)”. La implementación de Microsoft del CLI se conoce por CLR (Common Language Runtime). Hay una implementación libre de CLI desarrollada por Novell: Mono. Acompaña al entorno un conjunto de librerías gigantesco, aspecto en el que .NET va significativamente por delante de Mono. El lenguaje de preferencia para .NET es C# (se lee “C Sharp”), un lenguaje que se diseñó para superar algunos problemas de Java. En particular, la diferencia sustancial entre valores y objetos y la carencia de delegados que facilitaran la implementación del patrón observador/observable. C# ha evolucionado mucho desde su aparición, pero mantiene una coherencia en el diseño que lo hace fácil de aprender. Aunque es un lenguaje con herencia simple, implementación de interfaces y memoria con recolección automática, como Java, se diferencia de éste en numerosos aspectos importantes. C# ha integrado eficazmente varios conceptos de la programación funcional, como las funciones anónimas y las clausuras. Cuenta además con un mini-lenguaje para efectuar consultas a fuentes de datos, LINQ, que facilita mucho la gestión de información proveniente de bases de datos, de colecciones en memoria, de ficheros XML, etcétera. Lo cierto es que LINQ facilita el trabajo con cualquier objeto que proporcione una enumeración de elementos. Las enumeraciones son muy corrientes en .NET, pues C# facilita su diseño e implementación mediante estructuras de control como “yield return”. C# evita, además, la verbosidad del patrón de consulta y asignación de valor a campos (“getters & setters”) propia de Java mediante las denominada propiedades. Finalmente cabe advertir que la implementación de tipos genéricos en C# es mucho más sólida que la de Java, pues conserva información de tipos y distingue entre valores y objetos en el parámetro de tipo, a diferencia de lo que ocurre en Java, que basa su implementación de genéricos en el borrado de tipos. C# está estandarizado y su definición se encuentra en “Standard ECMA-334 – C# Language Specification”. Va por la versión 4.0 tanto en .NET como en Mono. WPF son las siglas de Windows Presentation Foundation. Es un conjunto de librerías para implementar aplicaciones interactivas. Arrancó con el nombre en clave “Avalon”. Presenta muchos P
  • 2. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal ¿Quées.NET?¿QuéesC#?¿QuéesWPF?¿QuéesVisualStudio?¿QuéesExpressionBlend? 2 aspectos interesantes: separación de apariencia y lógica, soporte del patrón “orden” (command), fácil conexión a fuentes de datos vía ligaduras (bindings), simplificación de trabajo con objetos observables mediante propiedades de dependencia, herencia de valores para propiedades por relación jerárquica entre componentes, acceso directo a hardware gráfico, animaciones, personalización completa de componentes mediante plantillas, etcétera. La P de WPF viene de “Presentation” y es importante. WPF soporta el patrón arquitectónico Modelo-Vista- Presentador (frente al clásico Modelo-Vista-Controlador). La versión WPF de este patrón es la que se conoce por Modelo-Vista-Modelo de la Vista, o MVVM por Model-View-ViewModel. Hay una versión ligera de WPF diseñada para correr incrustada en navegadores (aunque también puede ejecutarse fuera del navegador): Silverlight. El proyecto arrancó con el nombre en clave WPF/E, por WPF Everywhere, y muchas veces se habla de él en términos de competencia directa con Flex y Flash. Mono ofrece una implementación libre de Silverlight: Moonlight (aunque suele ir retrasada con respecto a la de Microsoft: La versión actual de Silverlight es la 3.0, con la 4.0 a punto de salir, y Moonlight implementa la funcionalidad de la 2.0 y buena parte de 3.0). WPF propone separar apariencia de lógica y lo lleva al extremo de ofrecer una herramienta para diseñadores gráficos que se integra en el proceso de desarrollo. Cuando el programador crea una interfaz gráfica se concentra en los elementos desde el punto de vista lógico y en cómo se comunican entre sí y con los datos de la aplicación. Los ficheros generados son directamente accesibles con Microsoft Expression Blend. Allí, el diseñador encuentra una aplicación con la que es sencillo cambiar el aspecto visual de los elementos, aplicar efectos y diseñar animaciones. Blend es parte de la suite Microsoft Expression, que incluye más herramientas orientadas a diseñadores gráficos (como Microsoft Expression Design, una herramienta en la línea de Adobe Freehand). Visual Studio es la plataforma de desarrollo por excelencia para .NET. Su última versión es Visual Studio 2010 (VS 2010) y ofrece soporte nativo para lenguajes .NET como C#, Visual Basic, F# (un lenguaje funcional de la familia de Ocaml) y para proyectos “clásicos” con C o C++. VS 2010 es extensible y cuenta con soporte (de terceras partes) para herramientas como Subversion o Mercurial. El proyecto Mono cuenta con su propia plataforma de desarrollo: MonoDevelop. Y hay otra plataforma abierta, SharpDevelop, aunque de uso marginal. .NET y Software Libre El principal problema de .NET para la comunidad no es de carácter técnico, sino el estigma de ser obra de Microsoft. Muchas de las herramientas libres de uso común en Java están disponibles para .NET: NHibernate, NAnt, NUnit, Spring.NET, etcétera. Microsoft abrió en 2009 CodePlex, un espacio para dar soporte a proyectos de software libre y algunos de sus proyectos recientes se distribuyen con licencias que permite acceder al código fuente (MEF e IronPython, por ejemplo). Microsoft apoya oficiosamente la iniciativa Moonlight, de Mono, con la que se está desarrollando una versión abierta de Silverlight. Aunque CLI o C# se han publicado como estándares ECMA y la actitud de Microsoft ante la comunidad de software libre ha evolucionado mucho, la comunidad mira con recelo cualquier innovación que provenga de Microsoft. Por ejemplo, Miguel de Icaza, líder del proyecto Mono, es frecuentemente insultado o menospreciado por personas o asociaciones respetadas en la comunidad del software libre. (Se puede obtener información sobre uno de los últimos rifi-rafes en http://www.fsf.org/blogs/rms/microsoft- codeplex-foundation, http://www.linuxtoday.com/news_story.php3 ?ltsn=2009-09-21-028-35-OP-CY-EV). Parece que ahora le toca a Microsoft sufrir una campaña de FUD como las que montaba hace tiempo.
  • 3. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal PrimerospasosconWPF 3 Primeros pasos con WPF onstruyamos una aplicación WPF extremadamente sencilla para empezar a entender algunos de los elementos básicos de WPF y C#. Nuestra aplicación mostrará un formulario en el que el usuario puede poner nombre y primer apellido. Tras pulsar un botón, se mostrará un cuadro de diálogo modal con un saludo personalizado. Empezamos iniciando VS 2010 (Figura 1a). Con la opción FileNewProject… creamos un nuevo proyecto de tipo “WPF Application” al que denominamos HolaMundo (Figura 1b). Esto crea un directorio HolaMundo y una estructura de ficheros y directorios que facilita el desarrollo de la aplicación. El explorador de soluciones muestra esta estructura (Figura 2). Encontramos una carpeta para propiedades, otra para referencias a DLLs y “ficheros lógicos” de aplicación que pueden desplegarse en uno o más ficheros físicos. Los ficheros App.* contienen el punto de entrada a la aplicación y definen/construyen una instancia de la clase Application. Los ficheros MainWindow.* definen la ventana principal de la aplicación y App.* contiene una indicación de que MainWindow.* es la ventana principal o URI de inicio. Los ficheros que nos interesan tienen extensión “.xaml” o “.cs”. Los primeros son ficheros en un formato XML denominado XAML (eXtensible Application Markup Language); los segundos son ficheros C#. XAML sigue un esquema XML cuyos elementos se inscriben en el espacio de nombres {http://schemas.microsoft.com/winfx/2006/xaml}. XAML es un lenguaje de marcado diseñado para facilitar la instanciación de objetos .NET y la definición de sus propiedades (o “atributos”, en jerga XML). Hay una versión de XAML con un esquema cuyos elementos están en el espacio de nombres C (a) (b) Figura 1. (a) Pantalla inicial de Visual Studio 2010. (b) Cuadro de diálogo para crear un nuevo proyecto. Figura 2. Explorador de soluciones con el proyecto WPF recién creado.
  • 4. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal PrimerospasosconWPF 4 {http://schemas.microsoft.com/winfx/2006/xaml} que permite instanciar objetos WPF y definir sus propiedades. XML es un formato jerárquico y, por tanto, los objetos en XAML siempre forman un árbol. Esa estructura es natural en una interfaz de usuario. Veamos el aspecto de App.xaml: <Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application> El fichero instancia un objeto de la clase App (que es nuestra y hereda de otra, Application, propia de WPF) en el espacio de nombres C# HolaMundo (lo indica el atributo x:Class). Define los dos espacios de nombres XML que se usan: XAML y WPF y señala que la aplicación empieza en la URI MainWindow.xaml. A continuación, define una sección para los recursos de la aplicación, pero no los hay. La sintaxis es especial y merece que nos detengamos: la marca Application.Resources corresponde, en realidad, a un atributo “Resources” de la marca “Application”. Es decir, en principio (pero sólo en principio), podríamos haber encontrado algo de este estilo: <Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" Resources="…"> </Application> ¿Por qué se usa esa otra sintaxis, extraña en el mundo XML (aunque siga el estándar)? Porque así es posible que el atributo Resources contenga nuevos elementos XML y no sólo una simple cadena. Esta sintaxis alternativa es muy corriente en los ficheros XAML y conviene acostumbrarse a ella. El fichero App.xaml.cs no contiene gran cosa: using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { } }
  • 5. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Botonesyeventos 5 La clase App, en el espacio de nombres HolaMundo, es una clase parcial (adjetivo “partial”), lo que significa que parte de su código está definido en otro fichero. Ese otro fichero contiene el código auto- generado por VS 2010. La clase está vacía (en nuestro fichero, pero no en el auto-generado). En nuestra parte podríamos definir, por ejemplo, comportamientos relacionados con el ciclo de vida de la aplicación (creación, activación, minimización, cierre, etcétera). Veamos ahora qué contiene MainWindow.xaml: <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> </Grid> </Window> Se crea una instancia de un objeto de la clase MainWindow, que por herencia es también de la clase Window. Se definen algunos atributos: el título (Title), la altura (Height) y la anchura (Width). Las unidades en que se indican las medidas son independientes de la pantalla. Una pulgada corresponde siempre a 96 unidades. (Se escogió esta unidad de medida por facilitar la medida en monitores convencionales, que suelen presentar una resolución de 96 dpi, es decir, 96 puntos por pulgada.) Dentro de la ventana hay un Grid. Es un elemento de maquetación. Más adelante lo estudiaremos con detenimiento. Ejecutemos la aplicación (pulsamos la tecla F5) y aparece una ventana vacía (Figura 3). VS 2010 sigue estando accesible, pero no permite editar el contenido del proyecto. Cerramos la aplicación pulsando en el botón de cierre de la ventana (o pulsando Shift-F5 en VS 2010) y volvemos a VS 2010. Botones y eventos camos a crear un botón con el texto “Saluda” y haremos que al pulsarlo aparezca una ventana modal con un saludo: <Window x:Class="HolaMundo.MainWindow" … Title="MainWindow" Height="350" Width="525"> <Grid> <Button Click="Button_Click"> Saluda </Button> </Grid> </Window> El texto “Saluda” es el valor que se asigna a una propiedad por defecto: Content. Es decir, este código XAML es equivalente: V Figura 3. Ventana de la aplicación en ejecución.
  • 6. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Botonesyeventos 6 <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Content="Saluda" Click="Button_Click" /> </Grid> </Window> Algunos objetos tienen campos privilegiados en tanto que el contenido de la marca XML (el texto o código XML que va entre las marcas de apertura y cierre) se les asigna automáticamente. En un botón, el campo especial es Content, en una caja de texto, el campo especial es Text. El atributo Click corresponde a un evento. Cada vez que se pulse en el elemento de tipo Button con el botón izquierdo del ratón y se levante en el interior del elemento, se invocará automáticamente al método Button_Click. El asistente de VS 2010 nos ha preparado el método Button_Click en MainWindow.xaml.cs: using System; using System.Collections.Generic; using System.Linq; using System.Text; 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 HolaMundo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { } } } El parámetro sender de Button_Click contendrá una referencia al propio botón y el parámetro de tipo RoutedEventArgs contendrá ciertos datos relativos al evento cuando éste ocurra. Vamos a rellenar el método con la invocación al diálogo modal: private void Button_Click(object sender, RoutedEventArgs e) {
  • 7. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Maquetaciónconpaneles 7 MessageBox.Show("Hola"); } El código que acompaña al XAML se denomina “código trasero” (code behind). Y aunque ahora recurrimos a él, veremos que MVVM permite eliminar buena parte de él (si no todo). Ejecutemos la aplicación (F5). El botón ocupa toda el área de trabajo (Figura 5a). Al pulsar el botón aparece la ventana modal que bloquea el acceso a la ventana MainWindow (Figura 5b). Maquetación con paneles eamos qué ocurre si tratamos de añadir elementos gráficos nuevos, como una etiqueta o una caja para texto: <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Label>Nombre:</Label> <TextBox></TextBox> <Button Click="Button_Click">Saluda</Button> </Grid> </Window> En la ventana sólo vemos el último elemento. En realidad están todos, pero uno encima del otro (véase la Figura 5). Es cosa del elemento Grid, que dejamos para luego por ser complejo: si dos o más elementos están en la misma celda de un Grid, se superponen. V Figura 5. Los elementos se tapan unos a otros en el Grid, por lo que sólo se ve el botón, que está encima del todo. (a) (b) Figura 4. (a) Ventana con el botón “Saluda”, que ocupa toda la superficie de la ventana. Ventana de diálogo modal que aparece al ejecutar el método Button_Click.
  • 8. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Maquetaciónconpaneles 8 Empezamos por un elemento de maquetación más sencillo: StackPanel. Un StackPanel apila vertical u horizontalmente sus elementos. <StackPanel> <Label>Nombre:</Label> <TextBox></TextBox> <Button Click="Button_Click"> Saluda </Button> </StackPanel> La Figura 6 muestra el resultado de la nueva disposición de elementos en el StackPanel: uno sobre el otro, por orden de aparición en el fichero XAML. Cada elemento WPF contiene decenas de atributos. Podemos recurrir a un formulario para asignar valores distintos de los “por defecto”, pero lo cierto es que a la larga resulta conveniente usar el editor de XAML. Hay más elementos de maquetación y se pueden incorporar otros definidos por programadores. Los paneles que vienen de serie son:  Grid: distribución de elementos en una tabla, con la posibilidad de fundir filas y columnas.  StackPanel: distribución de elementos en sucesión vertical u horizontal.  DockPanel: distribución de elementos con anclaje a punto cardinal y posible expansión del último al área sobrante.  WrapPanel: distribución de elementos en sucesión vertical u horizontal en “líneas” (como el texto, que fluye de una línea a la siguiente).  UniformGrid: distribución de elementos en una matriz cuadrada.  Canvas: ubicación precisa de elementos. (Algunos paneles definidos por programadores disponen elementos gráficos de formas novedosas u ofrecen animaciones para el desplazamiento o selección de sus elementos. Se pueden encontrar ejemplos en http://www.codeproject.com/Articles/37348/Creating- Custom-Panels-In-WPF.aspx, http://www.wpftutorial.net/CustomLayoutPanel.html o http://www.codeproject.com/KB/WPF/Panels.aspx.) Figura 6. Los tres elementos se muestran uno sobre otro gracias al StackPanel. Propiedades Los elementos XAML tienen numerosos atributos y al principio cuesta un poco manejarse con tantos. Puede venir bien invocar el panel de edición de atributos. Con el cursor en el elemento XAML cuyos atributos se desea editar, aparece un panel de propiedades al pulsar F4. No obstante, a la larga es más productivo usar el editor de XAML en VS 2010, que asiste al programador con los menús Intellisense.
  • 9. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Maquetaciónconpaneles 9 Las maquetas más complejas suelen formarse combinando diferentes paneles. En nuestro caso, podemos crear un StackPanel vertical en el que apilar dos StackPanels adicionales, estos horizontales, y el botón. <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <StackPanel Orientation="Horizontal"> <Label>Nombre:</Label> <TextBox></TextBox> </StackPanel> <StackPanel Orientation="Horizontal"> <Label>Apellido:</Label> <TextBox></TextBox> </StackPanel> <Button Click="Button_Click">Saluda</Button> </StackPanel> </Window> La ventana que hemos creado tiene espacio muerto bajo el botón “Saluda”. Esto es así porque hemos creado la ventana con unas dimensiones fijas: 525 por 350 puntos. El problema es fácil de corregir: <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height"> El atributo SizeToContent puede tomar los valores Manual (que es el que toma por defecto), Height, Width y WidthAndHeight. El efecto de seleccionar Height para SizeToContent es una ventana con anchura fija y altura ajustada al contenido de la ventana (Figura 7a). Hay un par de problemas adicionales. Por una parte, los campos de texto son pequeños (aunque crecen automáticamente conforme tecleamos texto en ellos); por otra, el alineamiento de los campos de texto no es perfecto y depende del tamaño de las etiquetas (Figura 7b). El elemento de maquetación Grid permite solucionar los dos problemas. Un Grid consta de una sección de declaración de filas y columnas. En nuestro caso definiremos 3 filas y 2 columnas. La primera fila contendrá la etiqueta “Nombre:” y su campo de texto; la segunda, la etiqueta “Primer apellido:” y su campo de texto; y la tercera, el botón “Saluda”. Las etiquetas se dispondrán en la Figura 8. Ventana con espacio indeseado. (a) (b) Figura 7. (a) La ventana con altura ajustada a su contenido. (b) Efecto de mal alineamiento cuando las etiquetas tienen texto de diferentes longitudes.
  • 10. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Maquetaciónconpaneles 10 primera columna y los campos de texto en la segunda. El botón “Saluda” ocupará una columna que será resultado de fundir las dos. La primera columna se ajustará al contenido y la segunda ocupará el resto del espacio. Nuestra tabla presentará, pues, esta estructura: Etiqueta Campo de texto---------------------------------------------------------------------------------- Etiqueta Campo de texto---------------------------------------------------------------------------------- ---------------------------------------------------------Botón------------------------------------------------ El código XAML se complica un poco: <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0">Nombre:</Label> <TextBox Grid.Column="1" Grid.Row="0"></TextBox> <Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label> <TextBox Grid.Column="1" Grid.Row="1"></TextBox> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> Saluda </Button> </Grid> </Window> Por fin vemos lo conveniente de fijar atributos con la sintaxis Elemento.Atributo: no es apropiado definir los atributos RowDefinitions y ColumnDefinitions con una simple cadena de texto, pues son en realidad listas de elementos XML, algunos con sus propios atributos. Hay un nuevo elemento sintáctico. Hay atributos (no elementos, como antes) con la sintaxis Elemento.Atributo. Examinemos, por ejemplo, esta línea: <Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label> El atributo Grid.Column permite asignar un valor a una propiedad Column definida en Grid, no en Label. Los elementos de tipo Label no saben nada de los de tipo Grid y, aun así, pueden asociar un valor a una propiedad de Grid. Los elementos WPF mantienen un diccionario que permite asociar valores a claves (propiedades) de las que nada sabe. En este caso, esas propiedades permiten ubicar el elemento en una fila/columna del Grid. Si nosotros definiésemos un panel propio, digamos que con una clase ClockPanel, en el que hubiese que ubicar los elementos alrededor de una circunferencia, por ejemplo, necesitaríamos que cada
  • 11. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Contenidorico,diseñográficoyanimaciones 11 elemento especificase los grados en los que debe aparecer en la esfera del reloj. Podríamos, entonces, usar una etiqueta como ésta: <Label ClockPanel.Angle="90">Primer apellido:</Label> Nótese que Label no sabe nada de ClockPanel (de hecho, ClockPanel es una invención nuestra y, de momento, ni siquiera existe). Pese a ello, podemos gestionar atributos de este tipo, que reciben el nombre de “propiedades pegadas” (attached properties). Contenido rico, diseño gráfico y animaciones PF sigue un modelo de contenido rico. En muchos sistemas de construcción de aplicaciones con interfaz gráfica de usuario hay serias limitaciones al contenido de los elementos. En algunas, los botones sólo pueden contener, por ejemplo, texto y, opcionalmente, un icono. Escapar de esta restricción, cuando es posible, obliga a construir nuevos elementos, lo que supone un incremento de complejidad enorme. WPF, sin embargo, permite que muchos componentes contengan a otros componentes en su interior, lo que facilita el diseño de aplicaciones con un acabado gráfico espectacular (si se trabaja codo con codo con diseñadores gráficos, claro está). Podemos probar a añadir un smiley al botón “Saluda”. Lo haremos con ayuda de Miorosoft Expression Blend. Arrancamos Blend y desde su menú FileOpen Project/Solution… abrimos el proyecto VS 2010 HolaMundo y nos encontramos con la aplicación como se muestra en la Figura 9. La interfaz de Blend es bastante compleja y no la analizaremos en detalle. Sólo queremos llevarnos una impresión acerca de su uso y ver que es una herramienta especializada en adaptar la apariencia de nuestra aplicación, y no en la lógica. Podemos editar código XAML desde Microsoft Expression Blend del mismo modo que hacemos con VS 2010. Basta con pulsar el icono “<>” que hay en la parte superior derecha del panel central (el que contiene la ventana de nuestra aplicación). Con el editor XAML de Microsoft Expression Blend hemos escrito este texto en el fichero de texto MainWindow.xaml, es decir, en el mismo fichero que hemos estado editando en Visual Studio y que forma parte del proyecto HolaMundo: <Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> W Figura 9. Microsoft Expression Blend 4 tras abrir el proyecto HolaMundo.
  • 12. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Contenidorico,diseñográficoyanimaciones 12 <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox Grid.Column="1" Grid.Row="1"/> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <StackPanel> <TextBlock TextAlignment="Center">Saluda</TextBlock> <Canvas Width="64" Height="64"> </Canvas> </StackPanel> </Button> </Grid> </Window> El botón contiene ahora un StackPanel con dos elementos: un bloque de texto y un panel de tipo Canvas de tamaño 64x64. Ahí dibujaremos el smiley. Con ayuda de la paleta de herramientas y el panel de propiedades creamos el gráfico que se muestra en la Figura 10. Blend permite crear efectos gráficos y animaciones. Vamos a hacer que cuando el ratón entre en la región del botón el smiley dé una vuelta. Empezamos creando una animación. Seleccionamos el Canvas y en el icono “+” del panel Objects and Timeline seleccionamos New… y creamos una historia (storyboard) a la que denominamos ZoomStoryboard. Con la marca de tiempo (línea amarilla) en el instante 0, fijamos a 0 la propiedad Angle en el panel Transform del Canvas que contiene al smiley, y fijamos a 360 su valor en el instante 1. Con eso conseguiremos que el smiley dé una vuelta completa en un segundo. Seleccionamos ahora el botón Saluda y seleccionamos el activo de tipo Behaviors denominado ControlStoryBoardAction. En su panel, seleccionamos la historia ZoomStoryboard y el evento MouseEnter. Podemos probar a ejecutar la aplicación y comprobar que cada vez que el cursor entra en el botón, se ejecuta la animación. Bueno, no sólo entonces: también se dispara cuando cargamos la aplicación. Luego eliminaremos este efecto indeseado (que aunque podemos eliminar desde Blend, eliminaremos desde VS 2010). Todo lo que hemos hecho con Blend se podría haber hecho directamente con VS 2010, pero hubiese supuesto un esfuerzo considerablemente mayor, como comprobaremos en breve al analizar el XAML generado. Figura 10. Botón con dibujo creado con el editor de Blend.
  • 13. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal MássobreXAML:recursos,disparadores,transformacionesyreferencias 13 Más sobre XAML: recursos, disparadores, transformaciones y referencias s hora de volver a VS 2010. Cerramos Blend y volvemos a VS 2010, que detectará que hubo cambios en el proyecto y solicita, por tanto, recargarlo. Analicemos el XAML que se ha generado desde Blend: <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="HolaMundo.MainWindow" Title="MainWindow" Width="525" SizeToContent="Height"> <Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateT ransform.Angle)" Storyboard.TargetName="canvas"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Window.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/> </EventTrigger> </Window.Triggers> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox Grid.Column="1" Grid.Row="1"/> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseEnter"> <ei:ControlStoryboardAction Storyboard="{StaticResource ZoomStoryboard}"/> </i:EventTrigger> </i:Interaction.Triggers> <StackPanel> <TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock> <Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> E
  • 14. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal MássobreXAML:recursos,disparadores,transformacionesyreferencias 14 </TransformGroup> </Canvas.RenderTransform> <Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90" Fill="#FFF4F4F5" Height="12" Canvas.Left="20" Stretch="None" Stroke="Black" StartAngle="90" Canvas.Top="36.12" Width="23.5"/> </Canvas> </StackPanel> </Button> </Grid> </Window> Complejo. Pero podemos analizar su contenido y comprobar que todo lo hecho con Blend, acaba codificándose como texto en el fichero XAML. Por una parte tenemos una sección de recursos, que es código XAML asignado a la propiedad Resources de Window. <Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2] .(RotateTransform.Angle)" Storyboard.TargetName="canvas"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> La sección de recursos es un diccionario que permite asociar elementos WPF a claves. La historia es que hemos creado tiene por clave ZoomStoryboard y por valor una instancia de la clase DoubleAnimationUsingKeyFrames. Las animaciones en WPF se forman con elementos simples. Una DoubleAnimation, por ejemplo, es una animación consistente en el cambio de un valor de tipo de double a lo largo del tiempo. Si vincuamos el valor de una propiedad de un elemento de la interfaz gráfica a esa animación (como la escala, la altura, la opacidad…), su valor afectará al aspecto visual de ese elemento. Una DoubleAnimationUsingKeyFrames es eso mismo, pero fijando tramas clave (key frames) en las que el double debe tomar ciertos valores. En nuestro caso, en el instante 0 debe tener valor 0 y en el instante 0:0:1 (un segundo después), debe valer 360. La animación tiene efecto sobre una propiedad del Canvas en el que hemos puesto el smiley: el ángulo de rotación. Después de la sección de recursos hay otra con disparadores (triggers). Los disparadores permiten asociar acciones a eventos (entre otras cosas). <Window.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/> </EventTrigger> </Window.Triggers>
  • 15. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal MássobreXAML:recursos,disparadores,transformacionesyreferencias 15 Este disparador es el responsable de que tan pronto se carga la ventana se inicie la animación ZoomStoryboard. Los valores de atributos entre llaves son especiales. En este caso se indica que la historia que debe ejecutarse se encuentra almacenada en el diccionario de recursos con la clave ZoomStoryboard. Si eliminamos ese disparador (o, para el caso, su sección completa), eliminaremos la animación indeseada. Seguimos analizando el XAML. El botón tiene este código que fija el valor de algunas “propiedades pegadas”: <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseEnter"> <ei:ControlStoryboardAction Storyboard="{StaticResource ZoomStoryboard}"/> </i:EventTrigger> </i:Interaction.Triggers> Los elementos en los espacios de nombres con sufijos i y ei son propios de Microsoft Expression Blend. No entramos en detalles. Baste saber que los comportamientos (behaviors) que podemos fijar en Blend se han incrustado en el XAML así. El botón contiene un StackPanel y su primer elemento es un TextBlock, un bloque de texto. Su único componente es una instancia de Run. <TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock> Hay varios tipos de elemento que podemos poner en un TextBlock y entre ellos destacamos Run (texto normal), Italic (texto en cursiva) y Bold (texto en negrita). Pero no son los únicos. Y si ponemos texto a pelo, WPF sabe que queríamos poner una marca Run y la pone por nosotros. Mucha de la infraestructura de WPF nos permite eliminar algo de verbosidad en el código XAML (que aún así es muy verboso). Y llegamos por fin al Canvas: <Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Canvas.RenderTransform> <Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90" Fill="#FFF4F4F5" Height="12" Canvas.Left="20" Stretch="None" Stroke="Black" StartAngle="90" Canvas.Top="36.12" Width="23.5"/> </Canvas>
  • 16. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Accesoapropiedadesdesdecódigotrasero 16 Hay un atributo interesante con identificador x:Canvas. Permite asociar un nombre a un componente y así poder referenciarlo desde otros puntos. De hecho, ya hemos referenciado a éste desde uno recurso: <Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2] .(RotateTransform.Angle)" Storyboard.TargetName="canvas"> Así es como aplica la historia a un componente concreto: fijando como valor del objetivo el identificador o nombre del elemento. En el Canvas hay un grupo de elementos asignados al atributo RenderTransform: con componentes que aplican una transformación afín a un elemento en el momento de visualizarse. La expresión (UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle) Estaba seleccionando el tercer componente (índice 2) del grupo de transformación, es decir, la rotación, y centrando el interés en la propiedad Angle, que corresponde al ángulo. La rotación tiene lugar centrada en el centro del Canvas gracias a este otro atributo: <Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5"> El Canvas contiene tres elipses y un arco, elemento que forma parte de un espacio de nombres propio de Microsoft Expression. Cabe señalar otro aspecto interesante de los valores de ciertos atributos. En las elipses podemos ver que hay varias formas de expresar un color: <Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/> Una forma es con #AARRGGBB (alfa, rojo, verde, azul) y otra con el propio nombre del color. WPF sabe interpretar apropiadamente el valor que se desea usar a partir de una cadena. Para ello usa un rico juego de conversores. Con unidades de medida, por ejemplo, sabe que “48” es media pulgada, que también podríamos expresas con “0.5in”. Y “1cm” representa un centímeto, o lo que es lo mismo, “10mm”. ¡Ah! Y fijémonos en los atributos Canvas.Left y Canvas.Top, que permite fijar las coordenadas X e Y de la esquina superior izquierda del rectángulo de inclusión de la elipse en el Canvas que la contiene. Ya vimos algo parecido con Grid.Column y Grid.Row. Acceso a propiedades desde código trasero amos a acceder desde código trasero al valor que el usuario teclee en las cajas de texto. Para ello necesitaremos poder acceder a las cajas de texto y deberán tener un identificador. En el código XAML escribimos esto: <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1"/> V
  • 17. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Enlaces 17 Volvamos al código trasero. En particular, al método que se invoca al pulsar el botón. Hagamos que el método quede así: private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("¡Hola, " + nombre.Text + " " + apellido.Text + "!"); } Estamos accediendo a la propiedad Text de los elementos “nombre” y “apellido”. Decimos propiedad y no campo porque Text no es un campo de tipo cadena, sino una “propiedad C#”. Las propiedades C# son pares de métodos que permiten acceder o asignar un valor a, en principio, un campo. Lo cierto es que detrás puede haber un campo o no haberlo, pero el programador que usa la propiedad tiene la ilusión de que lo hay. En realidad se ejecutará código que podría calcular el valor que se quiere leer a partir de uno o más campos (o de ninguno). El acceso a la propiedad Text no es una mera consulta a un campo: probablemente consista en el acceso a un diccionario y en la aplicación de operaciones que nos permitan ver el resultado como una cadena. Pero no hay de qué preocuparse: es todo tarea de WPF y para nosotros se crea la ilusión de acceso a un simple campo. Enlaces odemos enlazar diferentes elementos gráficos. Probemos a asociar el título de la ventana con el nombre que se introduce en el formulario. Para eso hemos de asignar un nombre a la ventana y crear un enlace (binding) que ligue su valor al del campo de texto que deseemos (y que ha de tener un nombre): <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" … Title="{Binding ElementName=nombre, Path=Text}" Width="525" SizeToContent="Height"> El enlace indica que hemos vincular el valor del campo Title de la ventana al del campo Text del elemento llamado “nombre”. La sintaxis de las llaves puede reemplazarse por esta otra: <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" … Width="525" SizeToContent="Height"> <Window.Title> <Binding ElementName="nombre" Path="Text"/> </Window.Title> Al ejecutar la aplicación podemos comprobar que título de ventana y contenido de la caja de texto con el nombre coinciden en todo momento. Conforme tecleamos los caracteres del nombre, el título de la ventana se va actualizando. P Figura 11. El título de la ventana está vinculado al contenido de la primera caja de texto.
  • 18. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Vistasymodelos(ocasi) 18 Vistas y modelos (o casi) o usual en una aplicación interactiva bien diseñada es que haya una clara separación entre la representación de un objeto (la vista) y el propio objeto (el modelo). De hecho, lo ideal es que el modelo dependa lo menos posible de la interfaz gráfica. Vamos a hacerlo ahora en nuestra aplicación, aunque resultará un tanto impostado por ser ésta muy sencilla. Creamos una nueva clase Persona. En el menú contextual del proyecto HolaMundo seleccionamos AddClass… y creamos la clase Persona, que pasa a definirse así: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace HolaMundo { class Persona : INotifyPropertyChanged { private string _nombre; private string _apellido; public string NombreCompleto { get { return Nombre + " " + Apellido; } } public string Nombre { get { return _nombre; } set { if (value != _nombre) { _nombre = value; NotifyChange("Nombre"); NotifyChange("NombreCompleto"); } } } public string Apellido { get { return _apellido; } set { if (value != _apellido) { _apellido = value; NotifyChange("Apellido"); NotifyChange("NombreCompleto"); } } } void NotifyChange(string id) L
  • 19. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Vistasymodelos(ocasi) 19 { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } } La idea ahora es que el campo Nombre y el campo Apellido de una instancia de Persona estén siempre sincronizados con las cajas de texto correspondientes en el formulario. Hemos creado cierta infraestructura al efecto. Por una parte, Persona implementa la interfaz INotifyPropertyChanged. La interfaz obliga a que se implemente un evento que se disparará cada vez que alguien modifique el valor de una propiedad, avisando así a los suscriptores del evento. Nótese que el cambio del nombre o el apellido no sólo cambia Nombre y Apellido, respectivamente: también cambia NombreCompleto. Hemos de crear una instancia de Persona e indicar que el “contexto de datos” (DataContext) de la ventana principal es esa instancia: using System; using System.Collections.Generic; using System.Linq; using System.Text; 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 HolaMundo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public readonly Persona LaPersona; public MainWindow() { InitializeComponent(); LaPersona = new Persona { Nombre = "Tu nombre", Apellido = "Tu apellido" }; DataContext = LaPersona; } private void Button_Click(object sender, RoutedEventArgs e)
  • 20. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Propiedadesdedependencia 20 { MessageBox.Show("¡Hola, " + LaPersona.NombreCompleto + "!"); } } } Y ahora, vinculemos el contenido de las cajas de texto con los de LaPersona. De paso, vincularemos el título con el nombre completo: <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="HolaMundo.MainWindow" Width="525" SizeToContent="Height"> <Window.Title> <Binding Path="NombreCompleto"/> </Window.Title> … <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0" Text="{Binding Nombre}"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1" Text="{Binding Apellido}"/> Y ya está. Nuestra ventana tiene por título el nombre completo y la instancia LaPersona siempre está sincronizada con el formulario. Propiedades de dependencia PF ofrece una herramienta muy interesante para crear propiedades que notifican automáticamente de los cambios que experimentan: las propiedades de dependencia. De hecho, las propiedades de los elementos WPF son realmente propiedades de dependencia. Estas propiedades no sólo notifican de los cambios que experimentan: tienen valores por defecto, se pueden heredar sus valores en la jerarquía de objetos, se pueden ligar a otras propiedades de dependencia, pueden usarse en animaciones y, lo que quizá es más importante: no consumen memoria si no se les asigna un valor. Las propiedades de dependencia se almacenan en un diccionario cuando se les asigna un valor. Si no lo tienen, WPF se encarga de acceder al valor por defecto automáticamente. Se trata de un factor muy importante si tenemos en cuenta que un elemento WPF puede tener más de medio centenar de propiedades. Convirtamos nuestra persona en un objeto con propiedades de dependencia y, de momento, olvidemos la sincronización del título de la ventana con el nombre completo. Más tarde recuperaremos esa funcionalidad. La nueva definición de Persona es ésta: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Windows; W
  • 21. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Propiedadesdedependencia 21 namespace HolaMundo { public class Persona : DependencyObject { public string NombreCompleto { get { return Nombre + " " + Apellido; } } public string Nombre { get { return (string)GetValue(NombreProperty); } set { SetValue(NombreProperty, value); } } public static readonly DependencyProperty NombreProperty = DependencyProperty.Register("Nombre", typeof(string), typeof(Persona), new UIPropertyMetadata("")); public string Apellido { get { return (string)GetValue(ApellidoProperty); } set { SetValue(ApellidoProperty, value); } } public static readonly DependencyProperty ApellidoProperty = DependencyProperty.Register("Apellido", typeof(string), typeof(Persona), new UIPropertyMetadata("")); } } Complicado, ¿no? Afortunadamente VS 2010 nos ayuda con los denominados snippets, plantillas con fragmentos de código fácilmente utilizables. Si tecleamos propdp (por “property: dependency property”), el editor nos ofrece una plantilla como ésta: public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } // Using a DependencyProperty as the backing store for MyProperty… public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new UIPropertyMetadata(0)); Con la ayuda del tabulador podemos asignar valor a los cuatro campos de la plantilla (que aparecen con fondo verde en este documento). La clase Persona hereda de DependencyObject, clase que ofrece la infraestructura necesaria para soportar propiedades de dependencia. Una propiedad de dependencia es un objeto estático que se registra en un diccionario con el método DependencyProperty.Register. Las instancias de un DependencyObject pueden acceder al valor de su propiedad de dependencia con el método GetValue,y asignarle un valor con SetValue (ambos heredados de DependencyObject). La propiedad C# que da acceso a estos métodos nos facilita el acceso a su lógica.
  • 22. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal UnaintroducciónalpatrónarquitectónicoMVVM 22 No hemos tenido que notificar los cambios porque las propiedades de dependencia ya se ocupan de ello automáticamente. Como NombreCompleto no es una propiedad de dependencia y ya no notifica de los cambios, hemos perdido esa funcionalidad. No costaría nada recuperarla volviendo a implementar el notificador de cambios en propiedades. Pero es mejor que pasemos a hablar de un patrón de diseño importante en el mundo WPF: el patrón Modelo-Vista-Modelo de la Vista, o MVVM, por Model-View- ViewModel. Una introducción al patrón arquitectónico MVVM a separación entre vista y modelo que hemos hecho no es buena. Hemos acabado por tocar el modelo para ajustarlo a la vista hasta el punto de construirlo con componentes de WPF, y eso es muy mala práctica. Normalmente el modelo nos viene dado y tenemos poca capacidad de influencia sobre él. Este problema es crucial en el diseño de aplicaciones interactivas, salvo en las más triviales. Desde el inicio de la programación de aplicaciones interactivas se plantearon la cuestión de la separación de responsabilidades en este tipo de sistemas. Un patrón exitoso es el conocido como Modelo-Vista-Controlador o MVC, por Model-View- Controller, que divide el sistema en tres componentes: el modelo, la vista y el controlador. Podemos representar gráficamente el concepto como se muestra en la Figura 12. Un patrón de diseño, como MVC, no es una receta estructa acerca de cómo implementar cierta funcionalidad, sino una serie de criterios que deben considerarse al diseñar la arquitectura de la aplicación e implementarla. El usuario interactúa con dispositivos que “hablan” con un controlador, el cual manipula un modelo (los datos) cuyo cambio fuerza la actualización de una vista, que es lo que percibe el usuario. Hoy día el modelo presenta ciertas dificultades y se considera superado por otros, como el denominado MVP, por Model-View-Presenter. También es un patrón con tres componentes. En este caso se trata del modelo, la vista y el presentador. Vista y modelo parece claro lo que son, pero ¿qué es el presentador? Es una capa entre la vista y el modelo. Cuando la vista necesita algo del modelo, se lo solicita al presentador, que ofrece métodos que hacen cómodo para la vista el acceso a los datos relevantes del modelo. Supongamos que el modelo tiene la fecha de nacimiento de una persona, pero no la edad. El presentador podría ofrecer un método o propiedad Edad que accediese a la fecha de nacimiento y al día actual para proporcionar el dato deseado. Y en sentido inverso, cuando la vista necesita modificar el modelo, lo hace a través del presentador, que sabe cómo “traducir” elementos de la vista en datos del modelo. También modelo y presentador interactúan: el presentador lee y escribe sus datos y cuando el modelo cambia “espontáneamente” (es decir, por eventos no relacionados con la interacción con el usuario), notifica al presentador de los cambios y éste se encarga de actualizar la vista. El patrón arquitectónico MVVM es una versión especializada de MVP para WPF. Establece mecanismos propios de WPF para la comunicación Vista-Presentador (que aquí se denomina Modelo de la Vista). En particular y limita las herramientas que podemos usar en cada capa. L Figura 12. Diagrama del patrón Modelo- Vista-Controlador. (Imagen extraída de Wikipedia.)
  • 23. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal UnaintroducciónalpatrónarquitectónicoMVVM 23  La vista es XAML 100% (o casi).  El modelo de la vista expone lógica vía órdenes (de las que nos ocupamos en breve), expone el modelo mediante ligaduras entre propiedades y propiedades de dependencia y mantiene información de estado de la interacción (pero sólo de la interacción).  El modelo mantiene los datos y sus operaciones, pero no lógica dependiente de cómo se usa. (Si cambia espontáneamente, implementa un notificador de cambios en propiedades.) Ahora vamos a seguir el patrón MVVM para que la aplicación vuelva a funcionar. Devolvamos el modelo a una versión minimalista. El fichero Persona pasa a contener este texto: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HolaMundo { public class Persona { public string Nombre { get; set; } public string Apellido { get; set; } public string NombreCompleto { get { return Nombre + " " + Apellido; } } } } El modelo no “sabe” nada de WPF ni de cómo se usará en la aplicación. Se limita a mantener un par de datos. Podría tener, además, operaciones para serializar el objeto, almacenarlo en disco, etcétera. Preparemos una clase para el modelo de la vista: MainWindowViewModel. Recordemos que su papel es hacer de puente entre la vista y el modelo. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model; public string Nombre { get { return Model.Nombre;} set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); } } public string Apellido { get { return Model.Apellido; } set { Model.Apellido = value; NotifyChange("Apellido", "NombreCompleto"); } }
  • 24. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal UnaintroducciónalpatrónarquitectónicoMVVM 24 public string NombreCompleto { get { return Model.Nombre + " " + Model.Apellido; } } void NotifyChange(params string[] ids) { if (PropertyChanged != null) foreach (var id in ids) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } } Hemos de preocuparnos ahora de vincular Vista, Modelo y Modelo de la Vista. Lo haremos modificando en App.xaml el arranque de la aplicación. Su contenido actual es éste: <Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application> Y pasa a ser este otro: <Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Startup="Application_Startup"> <Application.Resources> </Application.Resources> </Application> El método Application_Startup se definirá en App.xaml.cs así: using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; using System.Windows.Input; namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary>
  • 25. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Órdenes:elpatrónCommand 25 public partial class App : Application { private void Application_Startup(object sender, StartupEventArgs e) { var model = new Persona { Nombre = "Tu nombre", Apellido = "Tu apellido" }; var viewModel = new MainWindowViewModel {Model = model}; var view = new MainWindow {DataContext = viewModel}; view.Show(); } } } Hemos creado un modelo, que se almacena en el modelo de la vista tan pronto se crea, y hemos creado una vista cuyo contexto de datos es el modelo de la vista. La última acción consiste en mostrar la vista, que es la ventana principal. Para que la aplicación funciones hemos de eliminar las referencias a LaPersona o sus campos. Una de ellas está en el método Button_Click, asociado al evento Click del Button. Vamos a deshacernos de los eventos, pues no son recomendables en una aplicación MVVM. Órdenes: el patrón Command l uso de eventos no es, en general, recomendable en aplicaciones de tamaño moderado o grande. Los eventos crean referencias entre objetos que pueden prolongar la vida de éstos más allá de lo que el programador supone. Es la causa principal de las fugas de memoria en aplicaciones .NET, por lo que conviene tomar medidas profilácticas. WPF soporta el patrón de diseño “orden” (command) que permite asociar lógica a acciones interactivas. Y no sólo eso: permite también habilitar o deshabilitar la interacción de ciertos componentes en función del estado de los datos. En principio hemos de definir una clase que implemente la interfaz ICommand para cada orden del sistema. Pero resulta más cómodo usar una clase única a la que suministrar, mediante delegados o funciones anónimas, la lógica que deseamos. Esta clase suele denominarse RelayCommand o DelegateCommand. Esta es nuestra versión: public class RelayCommand : ICommand { #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion // Fields #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) { } E
  • 26. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Órdenes:elpatrónCommand 26 public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion } Antes de estudiarla, veamos cómo usarla. Creemos una orden que se dispare cuando pulsamos el botón “saluda”. El lugar natural para las órdenes es el modelo de la vista. Este es el código que le corresponde: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model; private readonly ICommand _saludaCommand; public ICommand SaludaCommand { get { return _saludaCommand; } } public MainWindowViewModel()
  • 27. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Órdenes:elpatrónCommand 27 { _saludaCommand = new RelayCommand( o => MessageBox.Show("¡Hola, " + NombreCompleto + "!"), o => !string.IsNullOrEmpty(NombreCompleto.Trim())); } public string Nombre { get { return Model.Nombre;} set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); } } public string Apellido { get { return Model.Apellido; } set { Model.Apellido = value; NotifyChange("Apellido", "NombreCompleto"); } } public string NombreCompleto { get { return Model.Nombre + " " + Model.Apellido; } } void NotifyChange(params string[] ids) { if (PropertyChanged != null) foreach (var id in ids) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } } Al construir el RelayCommand proporcionamos dos funciones (anónimas en este caso): una que indica qué hacer y la otra qué nos permite saber si la orden puede ejecutarse. En nuestro caso, el qué hacer es lo de siempre: mostrar el diálogo modal. Y la orden podrá ejecutarse siempre que el nombre completo contenga algún carácter no blanco. El ICommand que se ejecutará al pulsar el botón se expone como propiedad de sólo lectura. Falta vincular la orden a la pulsación del botón en MainWindow.xaml: <Button Grid.Row="2" Grid.ColumnSpan="2" Command="{Binding SaludaCommand}"> Y ya está. Ejecutemos la aplicación y comprobemos que el botón funciona. Y comprobemos también que cuando no hay texto en las cajas de texto no podemos pulsar el botón, como se muestra en la Figura 13. Figura 13. El botón aparece deshabilitado porque no hay texto en las cajas.
  • 28. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Aúnhaymás,peronoparahoy 28 Podría pensarse que no hemos ganado mucho con las órdenes, pero el patrón orden aisla efectivamente la lógica de la vista y la encapsula en una entidad que reúne la acción con un método que permite saber cuándo es posible ejecutarla. Si ahora quisiésemos que otros elementos (opciones de menú, gestos de teclados, etcétera) disparasen esa misma lógica, bastaría con asociarlos al mismo RelayCommand. Todos esos elementos se activarían y desactivarían gracias al método CanExecute y dispararían, cuando fuera posible, la misma acción vía Execute. Aún hay más, pero no para hoy s imposible presentar todos los elementos que conforman WPF en una simple charla. He pretendido mostrar algunos elementos básicos e introducir, aunque sea someramente, el patrón arquitectónico de preferencia: MVVM. Entre lo que nos dejamos en el tintero:  Infinidad de controles.  Diferentes tipos de animaciones.  Estilos y plantillas que permiten personalizar prácticamente todo.  Gestión de colecciones que facilita la interacción con listas o árboles de datos.  La conexión a fuentes de datos provenientes de bases de datos o XML.  El modelo de navegación.  La versión empotrable en páginas web (Silverlight).  Los componentes multimedia.  Los efectos de bitmap.  Diseño de controles y paneles personalizados.  El trabajo con elementos 3D.  El diseño de pruebas unitarias para componentes MVVM.  Librerías de ayuda para el diseño de aplicaciones MVVM.  Uso de inyección de dependencias para facilitar la asociación entre elementos de la tríada MVVM.  Extensiones para tinta, tacto, etcétera.  Conversores.  Eventos y órdenes enrutadas. Espero haber despertado la curiosidad por WPF y C# para que acudáis ahora a las fuentes bibliográficas o los blogs temáticos para aprender más. E
  • 29. Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal Fuentesbibliográficasrecomendables 29 Fuentes bibliográficas recomendables ólo queda recomendar algunos libros que permiten profundizar en WPF. Windows Presentation Foundation Unleashed, de Adam Natham. (Sacará una nueva edición en breve para cubrir WPF 4.0.) Programming WPF, 2ª edición, de Chris Sells. Essential Windows Presentation Foundations, de Chris Anderson. Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation, de Charles Petzold. Hay también varios blogs recomendables. Entre ellos, os cito estos:  http://blogs.msdn.com/llobo/  http://sachabarber.net/  http://joshsmithonwpf.wordpress.com/  http://blogs.msdn.com/jgoldb/default.aspx  http://houseofbilz.com/Default.aspx  http://jesseliberty.com/ S