Miguel Angel Morán

Machines take me by surprise with great frequency...

2D y animaciones con WPF


Existen muchas razones por las cuales una aplicación podría tener la capacidad de dibujar en pantalla y contenido visual como rectángulos, elipses, triángulos, círculos etc. WPF incluye una gran cantidad de funcionalidad para tener este tipo de gráficos y realmente existe una mejora significativa al dibujar con esta tecnología respecto a tecnologías previas como GDI+.

Shapes

Las Shapes (formas) son primitivas que nos permiten dibujar automáticamente algunas formas básicas y muy usadas en la definición de gráficos. La ventaja de utilizar Shapes es que incluyen todo lo necesario para definir la presentación y aspecto visual en las figuras sin necesidad de componer árboles de objetos complejos.

Las Shapes definidas por WPF se encuentran en el siguiente listado

Shape (Forma)

 

 

Ellipse

Dibuja círculos y elipses de circunferencia fija si se le especifican los atributos Width y Height o variable de acuerdo a su objeto contenedor si no se le especifican dichos atributos.

Line

Dibuja una línea dados dos puntos. Es posible especificar la presentación de la línea utilizando los atributos Stroke, StokeThikness entre otros.

Rectangle

Dibuja rectángulos y cuadrados y tiene la capacidad de modificar la presentación de las esquinas y el trazado de las líneas del rectángulo

Polyline

Dibuja rectas continuas

Polygon

Dibuja rectas continuas y añade un segmento de recta adicional para cerrar la figura.

Path

Dibuja líneas, curvas y en general cualquier caso siguiendo un conjunto de instrucciones especificadas mediante XAML o una notación especial

 

Ellipse:

El siguiente código muestra la manera de dibujar dos elipses. La primera elipse tiene especificados los atributos Width Y Height por lo que al momento de cambiar de tamaño la ventana el dibujo de la elipse permanecerá inamovible mientras que en la elipse llamada MiElipseFija ocurrirá un redimensionamiento toda vez que hereda el tamaño especificado por su objeto contenedor

XAML

<Ellipse Margin="68,47,82,86" Width="50" Height="50" Name="MiElipseMovible" StrokeThickness="5" Stroke="Red" /> <Ellipse Margin="68,47,82,86" Name="MiElipseFija" Stroke="Blue" StrokeThickness="5"></Ellipse>

 

El resultado es el siguiente:

Para definir que una elipse será rellenada con determinado color es posible utilizar el atributo Fill.

Line:

Las líneas son uno de los elementos visuales más utilizados en las aplicaciones. Para definir una línea es necesario indicar los puntos origen y destino a través de los cuales se dibujará el segmento de recta. Para definir estos puntos hacemos uso de los atributos X1, Y1 para definir el primer punto y X2, Y2 para definir el segundo punto. El ejemplo siguiente muestra cómo definir dos líneas.

XAML

<Line X1="0" Y1="0" X2="300" Y2="280"

Stroke="Blue" StrokeThickness="14" />

 <Line X1="0" Y1="260" X2="280" Y2="0"

StrokeThickness="14">

<Line.Stroke>

<ImageBrush ImageSource="C:\Revista USERS\2D\fondo.jpg" />

</Line.Stroke>

</Line>

La primera línea define para el atributo Stroke un color fijo mientras que la segunda línea utilizada un elemento ImageBrush para definir el trazado de dicha línea utilizada como fondo una imagen especificada en un archivo el resultado del código anterior es el siguiente.

Polyline

Este objeto representa una colección de líneas. Las líneas son definidas a través de la propiedad Points donde se definen los puntos por los cuales pasará el conjunto de líneas. De la misma manera que sucede con el objeto Line, podemos establecer el formato del trazado de la línea mediante los atributos Stroke, StrokeThikness entre otros. Además es posible utilizar el atributo Fill para llenar el área por la que pasan las líneas.

El código a continuación genera un triángulo:

XAML

<Polyline Points="100,100 200,40 300,100 104,100" Stroke="Black"

StrokeThickness="10" Margin="0,50" StrokeLineJoin="Round" Fill="Red"/>

 

Resultado

 

 

Polygon

La funcionalidad entre Polyline y Polygon es prácticamente la misma. La única diferencia que existe entre ambos objetos es que Polygon automáticamente añade una línea entre el primero y el último punto y de esta manera se genera una figura cerrada. A continuación mostramos el mismo ejemplo anterior utilizando ahora para ver la diferencia entre uno y otro

XAML

<Polygon Points="100,100 200,40 300,100 170,100" Stroke="Black"

StrokeThickness="10" Fill="Red" Margin="0,50" StrokeLineJoin="Round" />

 

Path

Con la clase Path es posible dibujar prácticamente cualquier trazo. A diferencia de las figuras vistas anteriormente, con éste objeto es posible dibujar inclusive curvas y rectas figuras abiertas y figuras cerradas, para eso, hace uso de la propiedad Data que tiene una notación que permite definir los movimientos, las curvas y las líneas mediante una notación sintaxis que se detalla a continuación.

Shape (Forma)

 

 

M

Define el punto inicial de una figura

L

Define una recta a partir de la posición actual y hasta el punto especificado

V, H

Definen una línea vertical u horizontal respectivamente

C, Q, S, T

Definen curvas de Bezier (cúbicas, cuadráticas, cúbicas suaves y cuadráticas suaves respectivamente)

A

Dibuja un arco elíptico

Z

Cierra la figura actual

 

El siguiente ejemplo muestra como utilizar el objeto Path

XAML

<Path Stroke="Black" Fill="Blue">

<Path.Data>

<PathGeometry Figures="M 10,100 C 10,300 300,-100 300,100 " />

</Path.Data>

</Path>

 

El código previo genera la siguiente salida:

 

 

Cabe señalar que cuando es necesario dibujar una figura compleja no es muy práctico editar a mano el XAML; es por eso que podemos hacer uso de herramientas especializadas en diseño gráfico como Expression Blend que automáticamente genera el código requerido para representar los trazos y dibujos creados en la herramienta misma.

A continuación un ejemplo de código generado automáticamente con Expression Blend utilizando su herramienta Pencil.

XAML

<Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="154.833,0,0,121.5" VerticalAlignment="Bottom" Width="3" Height="59" Data="M155.33333,264 C155.76028,283.21278 157.33333,302.47815 157.33333,322"/>

 <Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="189.5,0,0,122.167" VerticalAlignment="Bottom" Width="1.667" Height="59" Data="M190,263.33333 C190,282.62739 190.66667,301.97267 190.66667,321.33333"/>

 <Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="156.833,0,0,152.167" VerticalAlignment="Bottom" Width="36.334" Height="2.503" Data="M157.33333,291.33333 C168.83269,289.12798 180.58536,290 192.66667,290"/>

 <Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="198.432,0,0,127.415" VerticalAlignment="Bottom" Width="29.565" Height="27.752" Data="M214.66667,290 C204.25621,291.76448 202.49111,287.14113 200,303.33333 198.87719,310.63157 195.22353,319.6057 213.33333,314.66667 222.48637,312.17038 240.35052,303.42693 212.66667,289.33333"/>

 <Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="238.167,0,0,125.5" VerticalAlignment="Bottom" Width="1.666" Height="55.667" Data="M239.33333,263.33333 C239.33333,281.67256 238.66667,299.67663 238.66667,318"/>

 <Path Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="246.122,0,0,127.5" VerticalAlignment="Bottom" Width="33.045" Height="34.448" Data="M276,291.33333 C265.47034,284.113 258.73579,272.80992 249.33333,298.66667 245.70658,308.64023 241.38342,318.87219 268,310 270.13969,303.58094 273.33333,299.49906 273.33333,292 268.11259,304.18173 262.25661,306.1878 278.66667,316"/>

 

Resultado:

 

 

Animaciones

WPF incluye clases que nos permiten generar animaciones de una manera muy sencilla. Dentro de WPF básicamente las animaciones se logran mediante la variación de una o varias propiedades a través del tiempo; éstas propiedades pueden ser el tamaño del objeto, su posición, el color, su opacidad etc. Cuando las propiedades cambian durante el tiempo se percibe el efecto de animación.

Para crear una animación primero debemos de definir el objeto al cual queremos animar. Durante los siguientes pasos definiremos un círculo que variará su tamaño constantemente.

1) Antes que nada definimos un círculo con el siguiente código

XAML

<Ellipse Name="MiBalon" Height="50" Width="50" Stroke="Black" StrokeThickness="5"> </Ellipse>

 

2) Posteriormente utilizaremos la propiedad Ellipse.Triggers que define el suceso o condición que dará inicio a la animación. Existen varios tipos de Triggers como el DataTrigger que especifica una condición que se debe de cumplir para realizar alguna acción. También tenemos el EventTrigger que será el objeto que utilizaremos en éste ejemplo y que se usa cuando queremos definir un conjunto de acciones en respuesta a un evento. Cuando utilizamos EventTrigger debemos definir el evento que debe de suceder para disparar la animación mediante el atributo RoutedEvent, en este caso, definiremos el evento Ellipse.Loaded es decir: En el momento de que se cargue la Ellipse se iniciará inmediatamente la animación

XAML

<Ellipse Name="MiBalon" Height="50" Width="50" Stroke="Black" StrokeThickness="5"> <Ellipse.Triggers>

 <EventTrigger RoutedEvent="Ellipse.Loaded"> </EventTrigger> </Ellipse.Triggers> </Ellipse>

 

3) Después de definir el evento utilizaremos las clases StoryBoard y BeginStoryBoard que representan una línea de tiempo y su inicio.

<Ellipse Name="MiBalon" Height="50" Width="50" Stroke="Black" StrokeThickness="5">

<Ellipse.Triggers>

<EventTrigger RoutedEvent="Ellipse.Loaded">

<BeginStoryboard>

<Storyboard>

</Storyboard>

</BeginStoryboard>

</EventTrigger>

</Ellipse.Triggers>

</Ellipse>

 

 

4) Para finalizar la creación de nuestra animación utilizaremos la clase DoubleAnimation que representa una animación que se aplica a aquellas propiedades que sean del tipo Double. De la misma manera existen diferentes tipos de animaciones como BooleanAnimation que anima propiedades del tipo Boolean, ColorAnimation que anima propiedades del tipo Color y así sucesivamente existen objetos de diferentes tipos para animar las propiedades que tienen el tipo de datos compatible con la animación. En nuestro ejemplo podemos apreciar que utilizaremos el atributo Duration que indica el tiempo en el que tiene que ser completada la animación así como los atributos From y To que representan el valor inicial y el valor final de las propiedades que serán animadas. También utilizamos el atributo RepeatBehavior que indica la cantidad de veces que será repetida la animación. El código del ejemplo completo se presenta a continuación.

XAML

<Ellipse Name="MiBalon" Height="50" Width="50" Stroke="Black" StrokeThickness="5">

<Ellipse.Triggers>

<EventTrigger RoutedEvent="Ellipse.Loaded">

<BeginStoryboard>

<Storyboard>

<DoubleAnimation Duration="0:0:01" From="50" To="250" Storyboard.TargetProperty="Width" RepeatBehavior="Forever"/>

<DoubleAnimation Duration="0:0:01" From="50" To="250" Storyboard.TargetProperty="Height" RepeatBehavior="Forever"/>

</Storyboard>

</BeginStoryboard>

</EventTrigger>

</Ellipse.Triggers>

</Ellipse>

El código anterior produce el siguiente resultado:

Además de que podemos utilizar los colores sólidos como el fondo de cualquier objeto que tenga la propiedad Background, también es posible utilizar gradientes. Estos gradientes se establecen a través de brochas (objetos que heredan del tipo Brush) y pueden ser Lineales (LinearGradientBrush) o Radiales (RadialGradientBrush).

En nuestro ejemplo añadiremos un gradiente al fondo del Grid y posteriormente lo animaremos a través de su propiedad Offset.

A continuación utilizaremos para nuestro ejemplo otro tipo de animación llamado ColorAnimation cuyo objetivo será cambiar de color el fondo (Fill) del círculo. El XAML requerido es el siguiente.

XAML

<Grid>

<Grid.Background>

<LinearGradientBrush>

<GradientStop Color="Blue" Offset=".0"></GradientStop>

<GradientStop Color="Lime" Offset=".99"></GradientStop>

</LinearGradientBrush>

</Grid.Background>

<Grid.Triggers>

<EventTrigger RoutedEvent="Grid.Loaded">

<BeginStoryboard>

<Storyboard>

<DoubleAnimation Duration="0:0:02" From="0.0" To=".99" Storyboard.TargetProperty="(Grid.Background).(LinearGradientBrush.GradientStops)[0].(GradientStop.Offset)" RepeatBehavior="Forever" />

</Storyboard>

</BeginStoryboard>

</EventTrigger>

</Grid.Triggers>

<Ellipse Name="MiBalon" Height="50" Width="50" StrokeThickness="5">

<Ellipse.Fill>

<SolidColorBrush Color="Transparent"></SolidColorBrush>

</Ellipse.Fill>

<Ellipse.Triggers>

<EventTrigger RoutedEvent="Ellipse.Loaded">

<BeginStoryboard>

<Storyboard>

<DoubleAnimation Duration="0:0:02" From="50" To="200" Storyboard.TargetProperty="Width" RepeatBehavior="Forever"/>

<DoubleAnimation Duration="0:0:02" From="50" To="200" Storyboard.TargetProperty="Height" RepeatBehavior="Forever"/>

<ColorAnimation From="Red" To="Yellow" Duration="0:0:02" Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" RepeatBehavior="Forever"></ColorAnimation>

</Storyboard>

</BeginStoryboard>

</EventTrigger>

</Ellipse.Triggers>

</Ellipse>

</Grid>

 

Como podemos ver para poder animar el gradiente hacemos referencia a la ruta completa de la propiedad a la cual deseamos animar y nos referimos en específico a un GradientStop mediante su índice en la colección. El resultado que obtenemos es el siguiente:

 

Podemos notar que el color de la circunferencia ha cambiado de color rojo especificado hacia el color amarillo así como también se ha desplazado el gradiente hacia el color azul.

Eso es todo amigos cya next post!

Comments

mmonterroca said:

Degradado .aaa

# February 10, 2008 6:52 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Protected by FormShield
Refresh
Listen
Please enter the characters shown on the image


Code: