net y wpf

29
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

Upload: magister845

Post on 15-Jun-2015

339 views

Category:

Education


2 download

TRANSCRIPT

Page 1: Net y WPF

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

Page 2: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

¿Q

ué e

s .N

ET?

¿Qu

é e

s C

#?

¿Qu

é e

s W

PF?

¿Qu

é e

s V

isu

al Stu

dio

? ¿Q

ué e

s Exp

ress

ion

Ble

nd

?

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.

Page 3: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pri

mero

s p

aso

s co

n W

PF

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 FileNewProject… 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.

Page 4: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pri

mero

s p

aso

s co

n W

PF

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 { } }

Page 5: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Bo

ton

es

y e

ven

tos

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.

Page 6: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Bo

ton

es

y e

ven

tos

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) {

Page 7: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

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.

Page 8: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

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.

Page 9: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

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.

Page 10: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

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

Page 11: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Co

nte

nid

o r

ico

, d

iseñ

o g

ráfi

co y

an

imaci

on

es

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ú FileOpen 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.

Page 12: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Co

nte

nid

o r

ico

, d

iseñ

o g

ráfi

co y

an

imaci

on

es

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.

Page 13: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

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].(RotateTransform.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

Page 14: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

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>

Page 15: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

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>

Page 16: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Acc

eso

a p

rop

ied

ad

es

desd

e c

ód

igo

tra

sero

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

Page 17: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

En

lace

s

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.

Page 18: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Vis

tas

y m

od

elo

s (o

casi

)

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

AddClass… 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

Page 19: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Vis

tas

y m

od

elo

s (o

casi

)

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)

Page 20: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pro

pie

dad

es

de d

ep

en

den

cia

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

Page 21: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pro

pie

dad

es

de d

ep

en

den

cia

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.

Page 22: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

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

Page 23: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

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

Page 24: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

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>

Page 25: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

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

Page 26: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

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()

Page 27: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

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.

Page 28: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

n h

ay m

ás,

pero

no

para

ho

y

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

Page 29: Net y WPF

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Fu

en

tes

bib

lio

grá

fica

s re

com

en

dab

les

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