Anda di halaman 1dari 29

Desarrollo de aplicaciones con .

NET y WPF
Andrs Marzal
Departamento de Lenguajes y Sistemas Informticos 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 ejecucin con mquina virtual para un lenguaje de mquina propio: IL, por Intermediate Language. Diferentes lenguajes se traducen a ese lenguaje de mquina y un compilador de ltima hora genera cdigo nativo, que es lo que realmente se ejecuta. .NET sigue un estndar ECMA: Standard ECMA-335, Common Language Infrastructure (CLI). La implementacin de Microsoft del CLI se conoce por CLR (Common Language Runtime). Hay una implementacin libre de CLI desarrollada por Novell: Mono. Acompaa al entorno un conjunto de libreras 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 implementacin del patrn observador/observable. C# ha evolucionado mucho desde su aparicin, pero mantiene una coherencia en el diseo que lo hace fcil de aprender. Aunque es un lenguaje con herencia simple, implementacin de interfaces y memoria con recoleccin automtica, como Java, se diferencia de ste en numerosos aspectos importantes. C# ha integrado eficazmente varios conceptos de la programacin funcional, como las funciones annimas y las clausuras. Cuenta adems con un mini-lenguaje para efectuar consultas a fuentes de datos, LINQ, que facilita mucho la gestin de informacin proveniente de bases de datos, de colecciones en memoria, de ficheros XML, etctera. Lo cierto es que LINQ facilita el trabajo con cualquier objeto que proporcione una enumeracin de elementos. Las enumeraciones son muy corrientes en .NET, pues C# facilita su diseo e implementacin mediante estructuras de control como yield return. C# evita, adems, la verbosidad del patrn de consulta y asignacin de valor a campos (getters & setters) propia de Java mediante las denominada propiedades. Finalmente cabe advertir que la implementacin de tipos genricos en C# es mucho ms slida que la de Java, pues conserva informacin de tipos y distingue entre valores y objetos en el parmetro de tipo, a diferencia de lo que ocurre en Java, que basa su implementacin de genricos en el borrado de tipos. C# est estandarizado y su definicin se encuentra en Standard ECMA-334 C# Language Specification. Va por la versin 4.0 tanto en .NET como en Mono. WPF son las siglas de Windows Presentation Foundation. Es un conjunto de libreras para implementar aplicaciones interactivas. Arranc con el nombre en clave Avalon. Presenta muchos

Desarrollo de Aplicaciones con .NET y WPF aspectos interesantes: separacin de apariencia y lgica, soporte del patrn orden (command), fcil conexin a fuentes de datos va ligaduras (bindings), simplificacin de trabajo con objetos observables mediante propiedades de dependencia, herencia de valores para propiedades por relacin jerrquica entre componentes, acceso directo a hardware grfico, animaciones, personalizacin completa de componentes mediante plantillas, etctera. La P de WPF viene de Presentation y es importante. WPF soporta el patrn arquitectnico Modelo-VistaPresentador (frente al clsico Modelo-Vista-Controlador). La versin WPF de este patrn es la que se conoce por Modelo-Vista-Modelo de la Vista, o MVVM por Model-View-ViewModel. Hay una versin ligera de WPF diseada para correr incrustada en navegadores (aunque tambin 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 trminos de competencia directa con Flex y Flash. Mono ofrece una implementacin libre de Silverlight: Moonlight (aunque suele ir retrasada con respecto a la de Microsoft: La versin 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 lgica y lo lleva al extremo de ofrecer una herramienta para diseadores grficos que se integra en el proceso de desarrollo. Cuando el programador crea una interfaz grfica se .NET y Software Libre

Andrs Marzal

El principal problema de .NET para la comunidad no es de carcter tcnico, sino el estigma de ser obra de Microsoft. Muchas de las herramientas libres de uso comn en Java estn disponibles para .NET: NHibernate, NAnt, NUnit, Spring.NET, etctera. 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 cdigo fuente (MEF e IronPython, por ejemplo). Microsoft apoya oficiosamente la iniciativa Moonlight, de Mono, con la que se est desarrollando una versin abierta de Silverlight. Aunque CLI o C# se han publicado como estndares ECMA y la actitud de Microsoft ante la comunidad de software libre ha evolucionado mucho, la comunidad mira con Microsoft. Por ejemplo, Miguel de Icaza, lder del proyecto Mono, es frecuentemente insultado o menospreciado por personas o asociaciones respetadas en la comunidad del software libre. (Se puede obtener informacin sobre uno de los ltimos rifi-rafes en http://www.fsf.org/blogs/rms/microsoftcodeplex-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 campaa de FUD como las que montaba hace tiempo. recelo cualquier innovacin que provenga de

concentra en los elementos desde el punto de vista lgico y en cmo se comunican entre s y con los datos de la aplicacin. Los ficheros generados son directamente accesibles con Microsoft Expression Blend. All, el diseador encuentra una aplicacin con la que es sencillo cambiar el aspecto visual de los elementos, aplicar efectos y disear animaciones. Blend es parte de la suite Microsoft Expression, que incluye ms herramientas orientadas a diseadores grficos (como Microsoft Expression Design, una herramienta en la lnea de Adobe Freehand). Visual Studio es la plataforma de desarrollo por excelencia para .NET. Su ltima versin 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 clsicos 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.

Qu es .NET? Qu es C#? Qu es WPF? Qu es Visual Studio? Qu es Expression Blend?

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Primeros pasos con WPF


onstruyamos una aplicacin WPF extremadamente sencilla para empezar a entender algunos de los elementos bsicos de WPF y C#. Nuestra aplicacin mostrar un formulario en el que el usuario puede poner nombre y primer apellido. Tras pulsar un botn, se mostrar un cuadro de dilogo modal con un saludo personalizado. Empezamos iniciando VS 2010 (Figura 1a). Con la opcin FileNewProject creamos un nuevo proyecto de tipo WPF Application al que denominamos HolaMundo (Figura 1b).

(a)

(b)

Figura 1. (a) Pantalla inicial de Visual Studio 2010. (b) Cuadro de dilogo para crear un nuevo proyecto.

Esto crea un directorio HolaMundo y una estructura de ficheros y directorios que facilita el desarrollo de la aplicacin. El explorador de soluciones muestra esta estructura (Figura 2). Encontramos una carpeta para propiedades, otra para referencias a DLLs y ficheros lgicos de aplicacin que pueden desplegarse en uno o ms ficheros fsicos. Los ficheros App.* contienen el punto de entrada a la aplicacin y definen/construyen una instancia de la clase Application. Los ficheros MainWindow.* definen la ventana principal de la aplicacin y App.* contiene una indicacin de que MainWindow.* es la ventana principal o URI de inicio. Los ficheros que nos interesan tienen extensin .xaml o .cs. Los primeros son ficheros en un formato XML Language); los segundos son ficheros C#. Primeros pasos con WPF denominado XAML (eXtensible Application Markup
Figura 2. Explorador de soluciones con el proyecto WPF recin creado.

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 diseado para facilitar la instanciacin de objetos .NET y la definicin de sus propiedades (o atributos, en jerga XML). Hay una versin de XAML con un esquema cuyos elementos estn en el espacio de nombres

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

{http://schemas.microsoft.com/winfx/2006/xaml} que permite instanciar objetos WPF y definir sus propiedades. XML es un formato jerrquico 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 seala que la aplicacin empieza en la URI MainWindow.xaml. A continuacin, define una seccin para los recursos de la aplicacin, 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 slo en principio), podramos 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, extraa en el mundo XML (aunque siga el estndar)? Porque as es posible que el atributo Resources contenga nuevos elementos XML y no slo 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 using using using using using System; System.Collections.Generic; System.Configuration; System.Data; System.Linq; System.Windows;

Primeros pasos con WPF

namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { } }

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

La clase App, en el espacio de nombres HolaMundo, es una clase parcial (adjetivo partial), lo que significa que parte de su cdigo est definido en otro fichero. Ese otro fichero contiene el cdigo autogenerado por VS 2010. La clase est vaca (en nuestro fichero, pero no en el auto-generado). En nuestra parte podramos definir, por ejemplo, comportamientos relacionados con el ciclo de vida de la aplicacin (creacin, activacin, minimizacin, cierre, etctera). 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 tambin de la clase Window. Se definen algunos atributos: el ttulo (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 resolucin de 96 dpi, es decir, 96 puntos por pulgada.) Dentro de la ventana hay un Grid. Es un elemento de maquetacin. Ms adelante lo estudiaremos con detenimiento. Ejecutemos la aplicacin (pulsamos la tecla F5) y aparece una ventana vaca (Figura 3). VS 2010 sigue estando accesible, pero no permite editar el contenido del proyecto. Cerramos la aplicacin pulsando en el botn de cierre de la ventana (o pulsando Shift-F5 en VS 2010) y volvemos a VS 2010.

Botones y eventos
camos a crear un botn 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 cdigo XAML es equivalente:
Figura 3. Ventana de la aplicacin en ejecucin.

Botones y eventos

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

<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 cdigo XML que va entre las marcas de apertura y cierre) se les asigna automticamente. En un botn, 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 botn izquierdo del ratn y se levante en el interior del elemento, se invocar automticamente al mtodo Button_Click. El asistente de VS 2010 nos ha preparado el mtodo Button_Click en MainWindow.xaml.cs: using using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Windows; System.Windows.Controls; System.Windows.Data; System.Windows.Documents; System.Windows.Input; System.Windows.Media; System.Windows.Media.Imaging; System.Windows.Navigation; 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) { } } } Botones y eventos El parmetro sender de Button_Click contendr una referencia al propio botn y el parmetro de tipo RoutedEventArgs contendr ciertos datos relativos al evento cuando ste ocurra. Vamos a rellenar el mtodo con la invocacin al dilogo modal: private void Button_Click(object sender, RoutedEventArgs e) {

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

MessageBox.Show("Hola"); } El cdigo que acompaa al XAML se denomina cdigo trasero (code behind). Y aunque ahora recurrimos a l, veremos que MVVM permite eliminar buena parte de l (si no todo). Ejecutemos la aplicacin (F5). El botn ocupa toda el rea de trabajo (Figura 5a). Al pulsar el botn aparece la ventana modal que bloquea el acceso a la ventana MainWindow (Figura 5b).

(a)

(b)

Figura 4. (a) Ventana con el botn Saluda, que ocupa toda la superficie de la ventana. Ventana de dilogo modal que aparece al ejecutar el mtodo Button_Click.

Maquetacin con paneles


eamos qu ocurre si tratamos de aadir elementos grficos 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> Maquetacin con paneles

En la ventana slo vemos el ltimo elemento. En realidad estn todos, pero uno encima del otro (vase la Figura 5). Es cosa del elemento Grid, que dejamos para luego por ser complejo: si dos o ms elementos estn en la misma celda de un Grid, se superponen.
Figura 5. Los elementos se tapan unos a otros en el Grid, por lo que slo se ve el botn, que est encima del todo.

Desarrollo de Aplicaciones con .NET y WPF Empezamos por un elemento de maquetacin ms 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 disposicin de elementos en el StackPanel: uno sobre el otro, por orden de aparicin 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 ms elementos de maquetacin y se pueden incorporar otros definidos por programadores. Los paneles que vienen de serie son: Grid: distribucin de elementos en una tabla, con la posibilidad de fundir filas y columnas. StackPanel: distribucin de elementos en sucesin vertical u horizontal. DockPanel: distribucin de elementos con anclaje a punto cardinal y posible expansin del ltimo al rea sobrante. WrapPanel: distribucin de elementos en sucesin vertical u horizontal en lneas (como el texto, que fluye de una lnea a la siguiente). UniformGrid: distribucin de elementos en una matriz cuadrada. Canvas: ubicacin precisa de elementos. Propiedades

Andrs Marzal

Los elementos XAML tienen numerosos atributos y al principio cuesta un poco manejarse con tantos. Puede venir bien invocar el panel de edicin 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 ms productivo usar el editor de XAML en VS 2010, que asiste al programador con los mens Intellisense.

(Algunos paneles definidos por programadores disponen elementos grficos de formas novedosas u ofrecen animaciones para el desplazamiento o seleccin de sus http://www.codeproject.com/Articles/37348/CreatingCustom-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.

Maquetacin con paneles

elementos. Se pueden encontrar ejemplos en

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Las maquetas ms 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 botn. <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 botn Saluda. Esto es as porque hemos creado la ventana con unas dimensiones fijas: 525 por 350 puntos. El problema es fcil 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 pequeos (aunque crecen automticamente conforme tecleamos texto en ellos); por otra, el alineamiento de los campos de texto no es perfecto y depende del tamao de las etiquetas (Figura 7b). El elemento de maquetacin Grid permite una seccin de declaracin 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 botn Saluda. Las etiquetas se dispondrn 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.

Maquetacin con paneles

solucionar los dos problemas. Un Grid consta de

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

primera columna y los campos de texto en la segunda. El botn 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 ------------------------------------------------------------------------------------------------------------------------------------------Botn -----------------------------------------------El cdigo 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 sintctico. Hay atributos (no elementos, como antes) con la sintaxis Elemento.Atributo. Examinemos, por ejemplo, esta lnea: <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 (propiedades) de las que nada sabe. En este caso, esas propiedades permiten ubicar el elemento en una fila/columna del Grid. Si nosotros definisemos un panel propio, digamos que con una clase ClockPanel, en el que hubiese que ubicar los elementos alrededor de una circunferencia, por ejemplo, necesitaramos que cada Maquetacin con paneles propiedad de Grid. Los elementos WPF mantienen un diccionario que permite asociar valores a claves

10

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

elemento especificase los grados en los que debe aparecer en la esfera del reloj. Podramos, entonces, usar una etiqueta como sta: <Label ClockPanel.Angle="90">Primer apellido:</Label> Ntese que Label no sabe nada de ClockPanel (de hecho, ClockPanel es una invencin 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, diseo grfico y animaciones


PF sigue un modelo de contenido rico. En muchos sistemas de construccin de aplicaciones con interfaz grfica de usuario hay serias limitaciones al contenido de los elementos. En algunas, los botones slo pueden contener, por ejemplo, texto y, opcionalmente, un icono. Escapar de esta restriccin, 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 diseo de aplicaciones con un acabado grfico espectacular (si se trabaja codo con codo con diseadores grficos, claro est). Podemos probar a aadir un smiley al botn Saluda. Lo haremos con ayuda de Miorosoft Expression Blend. Arrancamos Blend y desde su men FileOpen Project/Solution abrimos el proyecto VS 2010 HolaMundo y nos encontramos con la aplicacin como se muestra en la Figura 9. La interfaz de Blend es bastante compleja y no la analizaremos en detalle. Slo queremos llevarnos una impresin acerca de su uso y ver que es una herramienta especializada en adaptar la apariencia de nuestra aplicacin, y no en la lgica. Podemos editar cdigo 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 aplicacin). Con el editor XAML de Microsoft Expression Blend hemos escrito este texto en el fichero de texto que hemos estado editando en Visual Studio y que forma parte del proyecto HolaMundo:
Figura 9. Microsoft Expression Blend 4 tras abrir el 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"/>

Contenido rico, diseo grfico y animaciones

MainWindow.xaml, es decir, en el mismo fichero

11

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

<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 botn contiene ahora un StackPanel con dos elementos: un bloque de texto y un panel de tipo Canvas de tamao 64x64. Ah dibujaremos el smiley. Con ayuda de la paleta de herramientas y el panel de propiedades creamos el grfico que se muestra en la Figura 10. Blend permite crear efectos grficos y animaciones. Vamos a hacer que cuando el ratn entre en la regin del botn el smiley d una vuelta. Empezamos creando una animacin. 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 (lnea 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 botn 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 aplicacin y comprobar que cada vez que el cursor entra en el botn, se ejecuta la animacin. Bueno, no slo entonces: tambin se dispara cuando cargamos la aplicacin. Luego eliminaremos este efecto indeseado (que aunque podemos eliminar desde Blend, eliminaremos Contenido rico, diseo grfico y animaciones desde VS 2010). Todo lo que hemos hecho con Blend se podra haber hecho directamente con VS 2010, pero hubiese supuesto un esfuerzo considerablemente mayor, como comprobaremos en breve al analizar el XAML generado.
Figura 10. Botn con dibujo creado con el editor de Blend.

12

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Ms 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/>

Ms sobre XAML: recursos, disparadores, transformaciones y referencias

13

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

</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 codificndose como texto en el fichero XAML. Por una parte tenemos una seccin de recursos, que es cdigo 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 seccin 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 animacin 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 grfica a esa animacin (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 despus), debe valer 360. La animacin tiene efecto sobre una propiedad del Canvas en el que hemos puesto el smiley: el ngulo de rotacin. Despus de la seccin 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>

Ms sobre XAML: recursos, disparadores, transformaciones y referencias

14

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Este disparador es el responsable de que tan pronto se carga la ventana se inicie la animacin 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 seccin completa), eliminaremos la animacin indeseada. Seguimos analizando el XAML. El botn tiene este cdigo 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 botn 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 queramos poner una marca Run y la pone por nosotros. Mucha de la 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> Ms sobre XAML: recursos, disparadores, transformaciones y referencias infraestructura de WPF nos permite eliminar algo de verbosidad en el cdigo XAML (que an as es muy

15

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

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 transformacin afn a un elemento en el momento de visualizarse. La expresin (UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle) Estaba seleccionando el tercer componente (ndice 2) del grupo de transformacin, es decir, la rotacin, y centrando el inters en la propiedad Angle, que corresponde al ngulo. La rotacin 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 sealar 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 tambin podramos expresas con 0.5in. Y 1cm representa un centmeto, o lo que es lo mismo, 10mm. Ah! Y fijmonos en los atributos Canvas.Left y Canvas.Top, que permite fijar las coordenadas X e Y de la vimos algo parecido con Grid.Column y Grid.Row. Acceso a propiedades desde cdigo trasero esquina superior izquierda del rectngulo de inclusin de la elipse en el Canvas que la contiene. Ya

Acceso a propiedades desde cdigo trasero


amos a acceder desde cdigo trasero al valor que el usuario teclee en las cajas de texto. Para ello necesitaremos poder acceder a las cajas de texto y debern tener un identificador. En el cdigo 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"/>

16

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Volvamos al cdigo trasero. En particular, al mtodo que se invoca al pulsar el botn. Hagamos que el mtodo 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 mtodos que permiten acceder o asignar un valor a, en principio, un campo. Lo cierto es que detrs puede haber un campo o no haberlo, pero el programador que usa la propiedad tiene la ilusin de que lo hay. En realidad se ejecutar cdigo que podra calcular el valor que se quiere leer a partir de uno o ms 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 aplicacin 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 ilusin de acceso a un simple campo.

Enlaces
odemos enlazar diferentes elementos grficos. Probemos a asociar el ttulo 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 aplicacin podemos comprobar que ttulo de ventana y contenido de la caja de texto con el nombre coinciden en todo momento. Conforme tecleamos los caracteres del nombre, el ttulo de la ventana se va actualizando. Enlaces
Figura 11. El ttulo de la ventana est vinculado al contenido de la primera caja de texto.

17

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Vistas y modelos (o casi)

o usual en una aplicacin interactiva bien diseada es que haya una clara separacin entre la representacin 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 grfica. Vamos a hacerlo ahora en nuestra aplicacin, aunque resultar un tanto impostado por ser sta muy sencilla. Creamos una nueva clase Persona. En el men contextual del proyecto HolaMundo seleccionamos AddClass y creamos la clase Persona, que pasa a definirse as: using using using using using System; System.Collections.Generic; System.Linq; System.Text; 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)

Vistas y modelos (o casi)

18

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

{ 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 estn 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. Ntese que el cambio del nombre o el apellido no slo cambia Nombre y Apellido, respectivamente: tambin 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 using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Windows; System.Windows.Controls; System.Windows.Data; System.Windows.Documents; System.Windows.Input; System.Windows.Media; System.Windows.Media.Imaging; System.Windows.Navigation; 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)

Vistas y modelos (o casi)

19

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

{ MessageBox.Show("Hola, " + LaPersona.NombreCompleto + "!"); } } } Y ahora, vinculemos el contenido de las cajas de texto con los de LaPersona. De paso, vincularemos el ttulo 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 ttulo 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 automticamente 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 slo notifican de los cambios que experimentan: tienen valores por defecto, se pueden heredar sus valores en la jerarqua de objetos, se pueden ligar a otras propiedades de dependencia, pueden usarse en animaciones y, lo que quiz es ms 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 automticamente. Se trata de un factor muy importante si tenemos en cuenta que un elemento WPF puede tener ms de medio centenar de propiedades. Convirtamos nuestra persona en un objeto con propiedades de dependencia y, de momento, olvidemos la sincronizacin del ttulo de la ventana con el nombre completo. Ms tarde recuperaremos esa funcionalidad. La nueva definicin de Persona es sta: using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.ComponentModel; System.Windows; Propiedades de dependencia

20

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

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 cdigo fcilmente 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 esttico que se registra en un diccionario con el mtodo DependencyProperty.Register. Las instancias de un DependencyObject pueden acceder al valor de su propiedad de dependencia con el mtodo GetValue,y asignarle un valor con SetValue (ambos heredados de DependencyObject). La propiedad C# que da acceso a estos mtodos nos facilita el acceso a su lgica. Propiedades de dependencia

21

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

No hemos tenido que notificar los cambios porque las propiedades de dependencia ya se ocupan de ello automticamente. Como NombreCompleto no es una propiedad de dependencia y ya no notifica de los cambios, hemos perdido esa funcionalidad. No costara nada recuperarla volviendo a implementar el notificador de cambios en propiedades. Pero es mejor que pasemos a hablar de un patrn de diseo importante en el mundo WPF: el patrn Modelo-Vista-Modelo de la Vista, o MVVM, por Model-ViewViewModel.

Una introduccin al patrn arquitectnico MVVM

a separacin 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 prctica. Normalmente el modelo nos viene dado y tenemos poca capacidad de influencia sobre l. Este problema es crucial en el diseo de aplicaciones interactivas, salvo en las ms triviales. Desde el inicio de la programacin de aplicaciones interactivas se plantearon la cuestin de la separacin de responsabilidades en este tipo de sistemas. Un patrn exitoso es el conocido como Modelo-Vista-Controlador o MVC, por Model-ViewController, que divide el sistema en tres componentes: el modelo, la vista y el controlador. Podemos representar grficamente el concepto como se muestra en la Figura 12. Un patrn de diseo, como MVC, no es una receta estructa acerca de cmo implementar cierta funcionalidad, sino una serie de criterios que deben considerarse al disear la arquitectura de la aplicacin e implementarla. El usuario interacta con dispositivos que hablan con un controlador, el cual manipula un modelo (los datos) cuyo cambio fuerza la actualizacin de una vista, que es lo que percibe el usuario. Hoy da el modelo presenta ciertas dificultades y se considera superado por otros, como el denominado MVP, por Model-View-Presenter. Tambin es un patrn con tres componentes. En este caso se trata del modelo, la vista y el presentador. Vista y modelo parece necesita algo del modelo, se lo solicita al presentador, que ofrece mtodos que hacen cmodo 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 podra ofrecer un mtodo o propiedad Edad que accediese a la fecha de nacimiento y al da actual para proporcionar el dato deseado. Y en sentido inverso, cuando la vista necesita modificar el modelo, lo hace a travs del presentador, que sabe cmo traducir elementos de la vista en datos del modelo. Tambin modelo y presentador interactan: el presentador lee y escribe sus datos y cuando el modelo cambia espontneamente (es decir, por eventos no relacionados con la interaccin con el usuario), notifica al presentador de los cambios y ste se encarga de actualizar la vista. El patrn arquitectnico MVVM es una versin especializada de MVP para WPF. Establece mecanismos propios de WPF para la comunicacin Vista-Presentador (que aqu se denomina Modelo de la Vista). En particular y limita las herramientas que podemos usar en cada capa. Una introduccin al patrn arquitectnico MVVM claro lo que son, pero qu es el presentador? Es una capa entre la vista y el modelo. Cuando la vista
Figura 12. Diagrama del patrn ModeloVista-Controlador. (Imagen extrada de Wikipedia.)

22

Desarrollo de Aplicaciones con .NET y WPF La vista es XAML 100% (o casi).

Andrs Marzal

El modelo de la vista expone lgica va rdenes (de las que nos ocupamos en breve), expone el modelo mediante ligaduras entre propiedades y propiedades de dependencia y mantiene informacin de estado de la interaccin (pero slo de la interaccin).

El modelo mantiene los datos y sus operaciones, pero no lgica dependiente de cmo se usa. (Si cambia espontneamente, implementa un notificador de cambios en propiedades.)

Ahora vamos a seguir el patrn MVVM para que la aplicacin vuelva a funcionar. Devolvamos el modelo a una versin minimalista. El fichero Persona pasa a contener este texto: using using using using System; System.Collections.Generic; System.Linq; 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 cmo se usar en la aplicacin. Se limita a mantener un par de datos. Podra tener, adems, operaciones para serializar el objeto, almacenarlo en disco, etctera. 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 using using using using System; System.Collections.Generic; System.Linq; System.Text; System.ComponentModel;

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"); } }

Una introduccin al patrn arquitectnico MVVM

namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model;

23

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

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 aplicacin. 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> Una introduccin al patrn arquitectnico MVVM </Application.Resources> </Application> El mtodo Application_Startup se definir en App.xaml.cs as: using using using using using using using System; System.Collections.Generic; System.Configuration; System.Data; System.Linq; System.Windows; System.Windows.Input;

namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary>

24

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

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 accin consiste en mostrar la vista, que es la ventana principal. Para que la aplicacin funciones hemos de eliminar las referencias a LaPersona o sus campos. Una de ellas est en el mtodo Button_Click, asociado al evento Click del Button. Vamos a deshacernos de los eventos, pues no son recomendables en una aplicacin MVVM.

rdenes: el patrn Command

l uso de eventos no es, en general, recomendable en aplicaciones de tamao moderado o grande. Los eventos crean referencias entre objetos que pueden prolongar la vida de stos ms 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 profilcticas. WPF soporta el patrn de diseo orden (command) que permite asociar lgica a acciones interactivas. Y no slo eso: permite tambin habilitar o deshabilitar la interaccin de ciertos componentes en funcin del estado de los datos. En principio hemos de definir una clase que implemente la interfaz ICommand para cada orden del sistema. Pero resulta ms cmodo usar una clase nica a la que suministrar, mediante delegados o funciones annimas, la lgica que deseamos. Esta clase suele denominarse RelayCommand o DelegateCommand. Esta es nuestra versin: public class RelayCommand : ICommand { #region Fields rdenes: el patrn Command readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion // Fields #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) { }

25

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

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 cmo usarla. Creemos una orden que se dispare cuando pulsamos el botn saluda. El lugar natural para las rdenes es el modelo de la vista. Este es el cdigo que le corresponde: using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.ComponentModel; System.Windows; System.Windows.Input;

private readonly ICommand _saludaCommand; public ICommand SaludaCommand { get { return _saludaCommand; } } public MainWindowViewModel()

rdenes: el patrn Command

namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model;

26

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

{ _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 (annimas 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 dilogo modal. Y la orden podr ejecutarse siempre que el nombre completo contenga algn carcter no blanco. El ICommand que se ejecutar al pulsar el botn se expone como propiedad de slo lectura. Falta vincular la orden a la pulsacin del botn en MainWindow.xaml: rdenes: el patrn Command <Button Grid.Row="2" Grid.ColumnSpan="2" Command="{Binding SaludaCommand}"> Y ya est. Ejecutemos la aplicacin y comprobemos que el botn funciona. Y comprobemos tambin que cuando no hay texto en las cajas de texto no podemos pulsar el botn, como se muestra en la Figura 13.
Figura 13. El botn aparece deshabilitado porque no hay texto en las cajas.

27

Desarrollo de Aplicaciones con .NET y WPF Podra pensarse que no hemos ganado mucho con las rdenes, pero el patrn orden aisla

Andrs Marzal

efectivamente la lgica de la vista y la encapsula en una entidad que rene la accin con un mtodo que permite saber cundo es posible ejecutarla. Si ahora quisisemos que otros elementos (opciones de men, gestos de teclados, etctera) disparasen esa misma lgica, bastara con asociarlos al mismo RelayCommand. Todos esos elementos se activaran y desactivaran gracias al mtodo CanExecute y dispararan, cuando fuera posible, la misma accin va Execute.

An hay ms, pero no para hoy

s imposible presentar todos los elementos que conforman WPF en una simple charla. He pretendido mostrar algunos elementos bsicos e introducir, aunque sea someramente, el patrn arquitectnico de preferencia: MVVM. Entre lo que nos dejamos en el tintero: Infinidad de controles. Diferentes tipos de animaciones. Estilos y plantillas que permiten personalizar prcticamente todo. Gestin de colecciones que facilita la interaccin con listas o rboles de datos. La conexin a fuentes de datos provenientes de bases de datos o XML. El modelo de navegacin. La versin empotrable en pginas web (Silverlight). Los componentes multimedia. Los efectos de bitmap. Diseo de controles y paneles personalizados. El trabajo con elementos 3D. El diseo de pruebas unitarias para componentes MVVM. Libreras de ayuda para el diseo de aplicaciones MVVM. Uso de inyeccin de dependencias para facilitar la asociacin entre elementos de la trada MVVM. Extensiones para tinta, tacto, etctera. Conversores. Eventos y rdenes enrutadas.

Espero haber despertado la curiosidad por WPF y C# para que acudis ahora a las fuentes bibliogrficas o los blogs temticos para aprender ms.

An hay ms, pero no para hoy

28

Desarrollo de Aplicaciones con .NET y WPF

Andrs Marzal

Fuentes bibliogrficas recomendables


lo queda recomendar algunos libros que permiten profundizar en WPF. Windows Presentation Foundation Unleashed, de Adam Natham. (Sacar una nueva edicin en breve para cubrir WPF 4.0.)

Programming WPF, 2 edicin, 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 tambin varios blogs recomendables. Entre ellos, os cito estos: http://blogs.msdn.com/llobo/ http://sachabarber.net/ http://blogs.msdn.com/jgoldb/default.aspx http://houseofbilz.com/Default.aspx http://jesseliberty.com/ Fuentes bibliogrficas recomendables http://joshsmithonwpf.wordpress.com/

29

Anda mungkin juga menyukai