Assistento a qualche demo di applicazione WPF sarete stati quasi certmente vittime della novità più "evidente": le animazioni.
Le animazioni di WPF non sono poi così diverse da quelle ottenibili nelle applicazioni WinForm, entrambe si ottengono modificando dinamicamente il valore di una o più proprietà di un elemento di UI, quello che distingue WPF è il mettere a disposizione una serie di classi che rendono la modifica più semplice rispetto all'equivalente Windows Forms 2.0.
Le classi di animazione sono divise in due gruppi e utilizzano un naming ben preciso: <Type>Animation e <Type>AnimationUsingKeyFrames dove <Type> rappresenta il tipo della proprietà da animare.
Supponendo di voler animare la proprietà Width di un Button (btnAnimate) questo verrà premuto, il codice da scrivere nell'evento Click del pulsante sarà:
DoubleAnimation da = new DoubleAnimation();
da.From = 100;
da.To = 200;
da.AutoReverse = true;
da.Duration=new Duration(TimeSpan.FromSeconds(4));
btnAnimate.BeginAnimation(Button.WidthProperty, da);
Essendo la proprietà Width di tipo double, l'animazione utilizzata è una DoubleAnimation la quale modificherà la proprietà Width dal valore 100 a 200 in 4 secondi e viceversa, quindi la durata totale dell'animazione sarà 8 secondi.
Non tutte le proprietà in WPF possono essere target di un animazione, i requisiti necessari sono:
- Che sia una dependency property
- Che per il tipo di proprietà esista la relativa classe di animazione
- Che l'oggetto animato erediti da DependencyObject (per il necessario supporto al punto 1)
Ovviamente le animazioni possono essere descritte con XAML, in questo caso però l'animazione deve essere racchiusa in una Timeline.
La Timeline, come per altro l'animazione stessa, rappresenta un intervallo di tempo, all'interno del quale posso animare contemporaneamente più proprietà e affinchè questo sia possibile devo racchiudere le animazioni in un contenitore di Timelines/Animazioni rappresentato dall'oggetto StoryBoard.
Una volta definita l'animazione va specificato come questa deve partire, ecco perchè le animazioni sono normalmente contenute all'interno di un EventTrigger anche se posso essere contenute anche in un style.
Un simil-equivalente XAML sarà quindi:
<StackPanel Orientation="Vertical" Name="sp1">
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="btnAnimate">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="100" To="200" Duration="0:0:4" AutoReverse="True" RepeatBehavior="Forever" Storyboard.TargetName="btnAnimate" Storyboard.TargetProperty="Width" />
<DoubleAnimation By="0.5" Duration="0:0:1.5" AutoReverse="True" RepeatBehavior="2x" Storyboard.TargetName="btnAnimate" Storyboard.TargetProperty="Opacity" BeginTime="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</StackPanel.Triggers>
</StackPanel>
In questo caso l'evento Click di btnAnimate fara scattare il trigger che darà il via alla DoubleAnimation associata alla proprietà Width, l'animazione della proprietà non terminerà mai a causa della presenza dell'attributo RepeatBehaviour=Forever.
All'interno della stessa storyboard è presente un altra animazione che partirà 2 secondi dopo l'inizio della precedente (BeginTime="0:0:2") portando il valore della proprietà Opacity dal valore attuale al valore 0.5 (By="0.5") e viceversa in 1.5 secondi (la descrione della durata è nel formato giorni:ore:secondi) ripetendo il tutto due volte (RepeatBehaviour=2x)
Le animazioni possono essere interrotte, sospese, riprese etc... come nel seguente frammento XAML.
</StackPanel.Triggers>
...
<EventTrigger RoutedEvent="Button.Click" SourceName="btnPause">
<PauseStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="btnResume">
<ResumeStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="btnStop">
<StopStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
</StackPanel.Triggers>
Nel caso l'animazione non sia lineare bisogna utilizzare l'altro gruppo di animazioni il quale, attraverso la definizione di KeyFrames permette di indicare il valore che una determinata proprietà deve avere ad un ben preciso intervallo di tempo e il tipo di transizione da realizzare tra un keyframe e il successivo.
<BeginStoryboard>
<Storyboard FillBehavior="HoldEnd" x:Name="sb1">
<DoubleAnimation From="30" To ="350" Duration="0:0:5" Storyboard.TargetName="rect1" Storyboard.TargetProperty="(Canvas.Left)" />
<DoubleAnimationUsingKeyFrames Duration="0:0:5" Storyboard.TargetProperty="(Canvas.Top)" Storyboard.TargetName="rect1">
<LinearDoubleKeyFrame Value="50" KeyTime="0:0:1" />
<LinearDoubleKeyFrame Value="100" KeyTime="0:0:1.5" />
<LinearDoubleKeyFrame Value="50" KeyTime="0:0:2.0" />
<LinearDoubleKeyFrame Value="250" KeyTime="0:0:2.5" />
<LinearDoubleKeyFrame Value="50" KeyTime="0:0:2.8" />
<LinearDoubleKeyFrame Value="300" KeyTime="0:0:3" />
<LinearDoubleKeyFrame Value="100" KeyTime="0:0:5" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
Questo esempio attraverso due animazioni muove un rettangolo da sinistra verso destra, la posizione verticale è determinata dell'animazione DoubleAnimationUsingKeyFrames la quale attraverso un insieme di LinearDoubleKeyFrames specifica in maniera precisa il valore che l'attached property Canvas.Top avrà ad un ben preciso istante, avendo usato in questo caso un LinearDoubleKeyFrame l'interpolazione tra keyframes sarà lineare.
In aggiunta a LinearDoubleKeyFrame è possibile indicare dei DiscreteDoubleKeyFrames (interpolazione discreta) o dei SplineDoubleKeyFrames (interpolazione spinlined) permettendo in quest'ultimo caso di ottenere delle animazioni più fluide.