MSDN Tips & Tricks

I consigli degli esperti italiani per sfruttare al meglio gli strumenti di sviluppo e semplificare l’attività quotidiana.

In questa pagina

Un pugno di classi per coordinare le animazioni in Silverlight Un pugno di classi per coordinare le animazioni in Silverlight
Creare una “behavior” per attivare porzioni di XAML Creare una “behavior” per attivare porzioni di XAML

Un pugno di classi per coordinare le animazioni in Silverlight

Di Andrea Boschin - Microsoft MVP

Fai clic qui per scaricare il codice d’esempio.

Ormai è risaputo che a differenza di altre piattaforme le animazioni di Silverlight, così come quelle di Windows Presentation Foundation sono di tipo time-based anzichè frame-based. La differenza consiste nell’approccio utilizzato. Mentre con le animazioni frame-based occorre determinare uno stato di partenza e uno di arrivo e il runtime si occuperà di gestire l’interpolazione in base al numero di frame stabilito, nel caso di Silverlight animare un oggetto significa decidere quali e quante proprietà subiranno una modifica del proprio valore nell’arco di un tempo determinato. Dopo aver apprezzato tutti i vantaggi di questo approccio, ci si imbatte nella difficoltà di coordinare diverse animazioni allo scopo di ottenere una sequenza più complessa. Mentre infatti operando con i frame possiamo gestire delle timeline ben precise coordinando sovrapposizioni e sequenze, ragionando con l’unità di misura del tempo tutto questo diventa molto più complesso.

Allo scopo di semplificare questo problema ho provveduto a scrivere una serie di classi javascript, facendo uso del Type System della Microsoft AJAX Library come supporto per poter contare sulla potenza e flessibilità della programmazione object oriented. Ecco di seguito un basilare diagramma che descrive queste classi

tips1

La classe fulcro è Animation, che rappresenta il mattoncino fondamentale in quanto si occupa di incapsulare al suo interno una Storyboard di silverlight e conferire ad essa la medesima interfaccia (Animatable) – che hanno le altre classi. La caratteristica principale di questa gerarchia è che essa è facilmente componibile. A titolo esemplificativo si potrebbe istanziare una serie di Animation e comporle all’interno di una SequenceAnimation che si occuperà di eseguirle una dopo l’altra nell’ordine specificato. Oppure ancora, inserire le stesse istanze in una ParallelAnimation e vederle eseguire contemporaneamente.

Ma si può anche andare oltre ed inserire animazioni composite all’interno di altre animazioni composite e ottenere una complessità molto elevata con poche e semplici dichiarazioni. Ecco un esempio (scaricabile in allegato a questo tip):

var animations = [];
// Popolamento dell'array di animazioni da mettere in sequenza
animations.push(new Elite.Animation(rootElement.findName('s01')));
animations.push(new Elite.Animation(rootElement.findName('s11')));
animations.push(new Elite.Animation(rootElement.findName('s10')));
animations.push(new Elite.Animation(rootElement.findName('s09')));
animations.push(new Elite.Animation(rootElement.findName('s08')));
animations.push(new Elite.Animation(rootElement.findName('s07')));
animations.push(new Elite.Animation(rootElement.findName('s06')));
animations.push(new Elite.Animation(rootElement.findName('s05')));
animations.push(new Elite.Animation(rootElement.findName('s04')));
animations.push(new Elite.Animation(rootElement.findName('s03')));
animations.push(new Elite.Animation(rootElement.findName('s02')));

// creazione della sequenza
var sequence = new Elite.SequenceAnimation(animations);

// aggancio dell'evento completed
sequence.add_completed(
    Silverlight.createDelegate(this, this.handleSequenceCompleted));

// creazione dell'animazione parallela            
var parallel = new Elite.ParallelAnimation(
    [sequence, new Elite.Animation(rootElement.findName('c12'))]);

// inizio dell'animazione
parallel.begin();

Nel codice di esempio vengono istanziate 11 animazioni incapsulando altrettante Storyboard definite nelle risorse del file XAML. L’array così ottenuto viene incapsulato a sua volta all’interno di una animazione sequenziale che provvederà ad eseguire una dopo l’altra le animazioni. L’evento Completed, esposto dalla SequenceAnimation viene utilizzato per riavviare l’animazione al suo termine così da creare un loop infinito. Infine viene definita una ParallelAnimation contenente la sequenza stessa e una ulteriore Storyboard incapsulata in Animation. All’esecuzione di begin() vedremo una serie molto complessa di attività di alcuni dischi che non avremmo potuto ottenere con tanta semplicità altrimenti.

Nel progetto incluso che può essere aperto con Visual Studio 2008 Beta 2 sono presenti due versioni della libreria, una per il debug e una seconda per l’ambiente di release e il codice completo dell’esempio ivi descritto. Nella libreria è anche presente una ConditionalAnimation, il cui scopo è di condizionare l’esecuzione di rami dell’animazione in base ad eventi definiti a runtime per mezzo dell’evento DecisionRequest.

Creare una “behavior” per attivare porzioni di XAML

Di Andrea Boschin - Microsoft MVP

Fai clic qui per scaricare il codice d’esempio.

Lavorare in Silverlight 1.0 comporta spesso il dover scrivere molto codice javascript per ottenere dei risultati adeguati alle proprie esigenze. La mancanza dei ControlTemplate di WPF ad esempio costringe a ripetere molte volte il medesimo codice per conferire a differenti elementi comportamenti analoghi. Si pensi ad esempio all’esigenza di avere il medesimo comportamento ripetuto su una serie di pulsanti che fanno parte dell’interfaccia che si sta sviluppando. Casi come questo possono essere facilmente semplificati adottanto delle classi “Behavior”, ovvero dei componenti che siano in grado di incapsulare uno o più comportamenti conferiti ad un elmento grafico passato come argomento del costruttore. Si pensi, a titolo di esempio, ai comportamenti che normalmente caratterizzano un pulsante. Tipicamente esso potrà gestire gli eventi mouseOver, mouseOut, e click e ad ognuno di essi far corrispondere una particolare “azione visuale” che lo caratterizza.

Un’idea vincente perciò potrebbe essere quella di costruire una classe “astratta” ButtonBehavior che si occupi di intercettare questi eventi e gestirli all’interno di alcuni metodi che poi debbano essere estesi dal comportamento concreto. Naturalmente, dato che stiamo parlando di Javascript ovviamente non potremmo utilizzare classi “astratte”, ma facendo uso del Type System della Microsoft AJAX Library possiamo arrivare ad una buona simulazione. Ecco un esempio:

Type.registerNamespace('Elite.Silverlight.Library');

Elite.Silverlight.Library.ButtonBehavior = function(element)
{
    this.element = element;
    this.click$listener = null;
    this.mouseOver$listener = null;
    this.mouseOver$listener = null;
    this.initialize();
}
Elite.Silverlight.Library.ButtonBehavior.prototype = 
{
    get_element : function()
    { return this.element; },
    set_element : function(value)
    { this.element = value; },

    // pseudo-abstract Methods

    _onClick : function(sender, args)       { },
    _onMouseOver : function(sender, args)   { },
    _onMouseOut : function(sender, args)    { },

    // Methods

    initialize : function()
    {
        var element = this.get_element();
        
        element.Cursor = 'Hand';
        
        this.click$listener = 
            element.addEventListener(
                'MouseLeftButtonUp', 
                Function.createDelegate(this, this._onClick));

        this.mouseOver$listener = 
            element.addEventListener(
                'MouseEnter', 
                Function.createDelegate(this, this._onMouseOver));

        this.mouseOver$listener = 
            element.addEventListener(
                'MouseLeave', 
                Function.createDelegate(this, this._onMouseOut));
    },
    dispose : function()
    {
        var element = this.get_element();

        if (this.click$listener) 
            element.removeEventListener(
                'MouseLeftButtonUp', 
                this.click$listener);
        if (this.mouseOver$listener) 
            element.removeEventListener(
                'MouseEnter', 
                this.mouseOver$listener);
        if (this.mouseOut$listener) 
            element.removeEventListener(
                'MouseLeave', 
                this.mouseOut$listener);
    }    
}

Elite.Silverlight.Library.ButtonBehavior.registerClass(
    'Elite.Silverlight.Library.ButtonBehavior', 
    null);

La classe in questione è molto semplice. Essa definisce una serie di “delegate”, che ordinatamente collega agli opportuni eventi e li distrugge al termine del loro lavoro. Questa è un’operazione molto importante al fine di evitare spiacevoli memory leaks all’interno del browser. Ad ognuno degli eventi viene collegato un metodo il cui corpo rimane completamente vuoto. Essi sono i metodi che dovranno contenere il comportamento specifico della serie di pulsanti che dobbiamo definire. A questo punto potremmo ipotizzare di voler creare un behavior per un pulsante animato. Esso riceverà in input oltre all’elemento da trasformare in pulsante, anche tre Storyboard, definite in una sezione di risorse e opportunamente sintonizzate per agire sul pulsante in modo tale da animare gli eventi cui abbiamo accenato poco fa. Ecco un esempio molto semplificato:

Type.registerNamespace('Elite.Silverlight.Library');

Elite.Silverlight.Library.AnimatedButtonBehavior = 
    function(element, mouseOverSB, mouseOutSB, mouseClickSB)
{
    Elite.Silverlight.Library.AnimatedButtonBehavior.initializeBase(this, [element]);
    
    this.mouseOverSB = 
        element.findName(mouseOverSB);
    this.mouseOutSB = 
        element.findName(mouseOutSB);
    this.mouseClickSB = 
        element.findName(mouseClickSB);
}
Elite.Silverlight.Library.AnimatedButtonBehavior.prototype =
{
    _onClick : function(sender, args)
    {
        this.mouseOutSB.stop();
        this.mouseOverSB.begin();

    },
    _onMouseOver : function(sender, args)
    {
        this.mouseOverSB.stop();
        this.mouseOutSB.begin();
    },
    _onMouseOut : function(sender, args)
    {    
        this.mouseClickSB.begin();
    }
}

Elite.Silverlight.Library.AnimatedButtonBehavior.registerClass(
    'Elite.Silverlight.Library.AnimatedButtonBehavior', 
    Elite.Silverlight.Library.ButtonBehavior);

Come si vede, nel costruttore il riferimento ad element viene passato alla classe base ButtonBehavior e le storyboard vengono cercate all’interno delle risorse nello XAML. Inoltre gli eventi _onClick, _onMouseOver e _onMouseOut vengono ridefiniti in modo da azionare le storyboard referenziate. Il vantaggio di questa tecnica è che con una sola riga di codice siamo in grado di conferire il comportamento voluto a qualunque elemento definito nello XAML di una scena Silverlight.

var bt1 = new Elite.Silverlight.Library.AnimatedButtonBehavior(
     rootElement.findName('button1'), "sb2", "sb3", "sb4");

L’esempio qui riportato ha un solo problema dovuto alla necessaria semplificazione introdotta per spiegare al meglio la tecnica. La sua adozione richiede che le storyboard relative le azioni da compiere per ogni singolo pulsante, debbano essere create nello XAML una ad una, obbligandoci così ad avere ben tre Storyboard per ciascun pulsante. Nulla vieta però di scrivere meglio il codice dell’AnimatedButtonBehavior sfruttanto il metodo createFromXaml() per generare le Storyboard all’interno dello stesso Behavior e quindi semplificare ulteriormente il codice eliminando inutili ridondanze anche nello XAML.

Infine è opportuno ricordare che si possono ottenere facilmente dei Behavior un po’ più dinamici esponendo delle proprietà che valorizzate intervengano per modificare leggermente le caratteristiche dell’animazione prescelta.

Nel codice di esempio allegato ho preparato la versione completa del tip qui descritto sfruttanto animazioni differenti per caratterizzare i pulsanti.


Mostra: