[WPF] PlayAnimations ou comment jouer plusieurs animations les unes après les autres

Jeudi 8 janvier 2009

Lors du développement de SmartCooking (notre projet Imagine Cup) en Mai 2008, j’ ai été confronté à quelques détails techniques avec WPF. Parmi eux : les animations !

Notre application se découpe en plusieurs écrans (menu principal, visualisation du stock, ajout de produits, etc…) tous liés entre eux par de petites animations pour y jouer des transitions entre chaque écran.

image image image image

Plus globalement, si j’ ai trois écrans A, B et C, je crée quelques animations (storyboards) que je nomme ‘StoryboardOpenScreenA’, ‘StoryboardCloseScreenA’, ‘StoryboardOpenScreenB’, etc… Avec donc, à chaque fois, une animation de fermeture et une autre pour l’ ouverture de mon écran !

Il me suffit donc, dans mon code ou par l’ intermédiaire de Triggers, qu’ a jouer ces animations pour réaliser les transitions d’ un écran à l’ autre. Oui mais voila, si je place un trigger jouant plusieurs animations, du type :

1
2
3
4
5
6
<window.triggers> 
    <eventtrigger routedevent="ButtonBase.Click" sourcename="bt1"> 
        <beginstoryboard storyboard="{StaticResource animation1}" /> 
        <beginstoryboard storyboard="{StaticResource animation2}" /> 
    </eventtrigger> 
</window.triggers>

… ou directement dans le code, par appel de la fonction BeginStoryboard, mes deux animations se joueront en même temps !! Et croyez-moi, cela n’ est pas très joli surtout quand il y a plus de deux animations en même temps !

Ce que je veux donc, c’ est jouer les animations les unes après les autres ! Pour cela deux façons à ma connaissance :

  • Soit, prévoir dans les storyboards des « blancs » pour démarrer l’ animation après quelques secondes (calé sur le temps de la précédente animation !). Mais de suite je vous arrête car cette méthode n’ est pas du tout convenable !
  • Soit s’ abonner à l’ événement Completed de l’ objet Storyboard pour être notifié lors de la fin de l’ animation et ainsi lancer les animations suivantes !

Je me suis donc mis à la tache pour simplifier l’ utilisation de cette méthode par la création d’ une méthode d’ extension sur l’ objet Window (System.Windows.Window) permettant de jouer X animation(s) dans l’ ordre en précisant simplement leurs noms dans l’ ordre voulu !

On pourra ensuite, par exemple, dans notre Window et en une ligne de code, jouer les animations AnimA, AnimB et AnimC les une après les autres :

1
this.PlayAnimations("AnimA", "AnimB", "AnimC");

Le nom des animations étant un « params string[] » on pourra y jouer autant d’ animation que l’ on veut (une seule, deux, trois, … dix, … ou plus).

Voici le code de cette extension :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public static class MyWpfExtensions
{
    public static void PlayAnimations(this Window window, params string[] animationsName)
    {
        // If animationsName is empty: return
        if (animationsName.Length <= 0) return;
        // For each animation
        for (int i = 0; i < animationsName.Length; i++)
        {
            // Get Storyboard object
            var anim = window.GetStoryBoard(animationsName[i]);
            if (anim != null)
            {
                // If not the last animation,
                if (i < (animationsName.Length - 1))
                {
                    // Add handler on the Completed event of the current storyboard to play the te next animation
                    anim.Completed += new EventHandler(
                        new StoryboardPlayNextHandler(window, anim, animationsName[i + 1]).OnHandler);
                }
            }
        }
        // Finally, play the 1st animation
        window.BeginStoryboard(window.GetStoryBoard(animationsName[0]));
    }
 
    public static Storyboard GetStoryBoard(this Window window, string name)
    {
        if (window.Resources.Contains(name))
            return (Storyboard)window.Resources[name];
        else
            return null;
    }
 
    public class StoryboardPlayNextHandler
    {
        private string _nextAnimationName;
        private Storyboard _actualStoryBoard;
        private Window _window;
 
        public StoryboardPlayNextHandler(Window window,
            Storyboard actualStoryBoard, string nextAnimationName)
        {
            this._actualStoryBoard = actualStoryBoard;
            this._nextAnimationName = nextAnimationName;
            this._window = window;
        }
 
        public void OnHandler(object sender, EventArgs e)
        {
            if (_actualStoryBoard != null)
            {
                // Remove the current handler on the completed event !
                _actualStoryBoard.Completed -= new EventHandler(this.OnHandler);
                // Launch next storyboard
                _window.BeginStoryboard(_window.GetStoryBoard(_nextAnimationName));
            }
        }
    }
}

En espérant que cela pourra vous être utile :)

Code complet de ma classe en cliquant ici !

[WPF] S’amuser avec le MediaElement sous WPF

Samedi 21 octobre 2006

Le contrôle MediaElement que j’ai decouvert au cours de cette semaine a revolutionné ma petite vie de developpeur :) Ce contrôle permet de contenir des images, sons ou videos.
Pour charger un media, rien de plus simple :

1
2
MediaElement monMedia = new MediaElement();
monMedia.Source = new Uri("monURI");

Sachant que mon Uri peut pointer sur un fichier local (c:\….) ou distant (http://…..) que se soit une video, une image ou un son….

Le media sera joué immédiatement. Bien que vous disposez des methodes Play(), Stop() et Pause() pour contrôler le media, utilisez plutot la proprieté LoadedBehavior qui permet de definir et/ou obtenir l’etat du media :

1
2
3
monMedia.LoadedBehavior = MediaState.Pause; //Pause
monMedia.LoadedBehavior = MediaState.Stop; //Arret
monMedia.LoadedBehavior = MediaState.Play; //Play

A ce sujet, pour pouvoir jouer une video (ou son) en boucle, la seule doc que j’avais trouvé sur le msdn2 était de mettre un MediaTimeline. Chose un peu tordu dans le code alors qu’il y a une solution toute simple :
1) Abonnez-vous à l’evenement MediaEnded (fin du media) :

1
2
monMedia.MediaEnded +=
          new RoutedEventHandler(monMedia_MediaEnded);

2) Remettez la position du media à zero (debut) et relancez le media :

1
2
3
4
5
void monMedia_MediaEnded(object sender, RoutedEventArgs e) {
     ((MediaElement)sender).LoadedBehavior = MediaState.Stop;
     ((MediaElement)sender).Position = new TimeSpan(0);
     ((MediaElement)sender).LoadedBehavior = MediaState.Play;
}

Ce qui est quand même beaucoup plus simple :)

C’est bien beau tout ca, mais la vous n’avez aucun apercu à l’ecran ce qui est quand meme un peu dommage ^^ Mais là où ca devient fort bien interressant, c’est qu’un VisualBrush peut prendre en parametre un MediaElement pour pouvoir remplir un rectangle par exemple :

1
2
Rectangle monRectangle = new Rectangle();
monRectangle.Fill = new VisualBrush(monMedia);

Et hop la, votre video (par exemple) sera joué dans ce rectangle. Enfin pour rajouter votre rectangle à un canvas que vous aurez au préalable placé dans votre code XAML :

1
monCanvas.Children.Add(monRectangle);

A partir de la on peut vraiement s’amuser et aller plus loin dans la démarche.. Par exemple, les rectangles ont une proprieté Clip qui va nous permettre de clipper notre media, OpacityMask pour appliquer un masque d’opacité, et bien d’autre chose pour appliquer differents effets a notre rectangle/video, … Et encore je ne parle que des rectangles, a partir du moment où votre controle pourra etre remplit par un VisualBrush on pourra tout y mettre :)
Je ne vais pas vous devoiler sur ce quoi je travaille en ce moment, mais il faut bien se rendre compte de tout ce qu’on peut faire avec car c’est carrement dément…

[WPF] Z-Index dans WPF

Jeudi 12 octobre 2006

Tout comme en CSS, on retrouve une notion de ZIndex en WPF, qui permet de spécifier l’empilement des différents calques.

Sous WPF, les ZIndex s’appliquent uniquement sur des objets ‘conteneurs’ comme les Panels, Grid, StackPanels et Canvas.

En XAML, la syntaxe est :

1
2
3
4
<Canvas>
    <Image Name="Image1" Canvas.ZIndex="2" Height="60" Width="60" Source="test1.png" />
    <Image Name="Image2" Canvas.ZIndex="1" Height="50" Width="70" Source="test2.png" />
</Canvas>

Ici, l’image 1 se trouvera au dessus de l’image 2. On remarque l’ attribut Canvas.ZIndex placé dans la balise Image.

Dans le code, ici en C#, cela se déclare de cette manière :

1
2
Canvas.SetZIndex(Image1, 2);
Canvas.SetZIndex(Image2, 1);

Vous avez aussi la possibilité de récupérer la proprièté ZIndex d’un element par :

1
int image1_ZIndex = Canvas.GetZIndex(Image1)