Behaviors: rendere l’interattività semplice in Blend

Tra gli elementi che restano più ostici in Blend 2 per il lavoro che un interaction designer può realizzare nell’implementazione degli elementi di una interfaccia grafica in Silverlight 2, c’è sicuramente il collegamento degli eventi di interazione sull’interfaccia con azioni da innescare nel codice o sugli oggetti presenti nell’interfaccia stessa. Questa operazione, infatti, ad oggi richiede , se pur in modo semplice, lo sviluppo di alcune righe di codice con cui non sempre ha familiarità chi si occupa di interaction design .

Ad esempio immaginiamo di aver realizzato un’animazione e di volerla far eseguire alla pressione di un tasto sull’interfaccia, oppure di voler eseguire il play sul MediaElement inserito nella nostra interfaccia Silverlight in conseguenza di un click su un elemento del Visual Tree. Al momento tutte queste azioni in Blend 2 con Silverlight 2 comportano l’inserimento di un handler su uno specifico evento contenente almeno una riga di codice per invocare la specifica azione sull’oggetto che rappresenta l’animazione o il MediaElement all’interno dell’interfaccia in sviluppo.

Per semplificare questo tipo di attività di sviluppo e consentire una rapida interazione per il designer che compone l’interfaccia con gli eventi e le azioni che si vogliono innescare in conseguenza degli eventi stessi in Blend 3 e Silverlight 3 sono stati inseriti i Behaviors come ben ci descrive Roberto sul suo blog  .

I Behaviors consentono per l’appunto l’esecuzione di specifiche azioni su di un target e sono associabili in modo dichiarativo ad uno specifico evento su un elemento della nostra interfaccia, sia attraverso una dichiarazione espressa con lo XAML sia direttamente dai menù di Blend associati all’elemento che poi implementano la dichiarazione nello XAML su cui si sta lavorando.

Come si utilizzano i Behaviors ? Come prima cosa occorre referenziare nel progetto su cui si sta lavorando

clip_image002

la DLL che contiene l’implementazione dei Behaviors che vogliamo utilizzare la libreria di base per questa funzionalità che è contenuta per la preview di Blend 3, nella cartella ..Program Files\Microsoft Expression\Blend 3 Preview\Libraries\Silverlight con il nome Microsoft.Expression.Interactivity.dll . Per poter provare potete utilizzare il semplice esempio che ho implementato e che trovate qui per eseguire una animazione. Altri esempi di Behaviors potete scaricarli da https://gallery.expression.microsoft.com/en-us/SampleSLBehaviors .

Effettuata questa operazione, selezionando dal menù dei controlli di Blend la Asset Library e da li selezionando la pagina dei Behaviors si potrà selezionare il Behaviors StartAnimation e trascinarlo sul bottone presente nella nostra interfaccia a cui vogliamo associare l’azione.

clip_image004

Fatta questa operazione, vedremo associato al bottone il behavior

clip_image006

e potremo configurare con il tab delle proprietà il nome dell’animazione da eseguire. Lo XAML generato per associare il Behaviors sarà il seguente :

<Button x:Name="button" >

     <i:Interaction.Triggers>

           <i:EventTrigger EventName="Click">

                   <si:StartAnimation AnimationName="test"/>

           </i:EventTrigger>

       </i:Interaction.Triggers>

…………

dove i: e si: sono rispettivamente i namespace che corrispondono alla libreria di base dei Behaviors e alla mia libreria che implementa il semplice StartAnimation.

Attraverso la finestra delle proprietà si potrà impostare le proprietà del Behaviors tra cui il nome della animazione da eseguire sull’evento click del bottone a cui abbiamo associato il Behaviors stesso.

clip_image008

Eseguendo l’applicazione e facendo click sul bottone si eseguirà l’animazione test che dovrà ovviamente essre implementata nello usercontrol in cui si stautilizzando il Behaviors.

Attraverso le proprietà possiamo anche modificare l’evento del Trigger che decide quando scatenare l’azione collegata al Behaviors come si vede nell’immagine precedente, in quei casi in cui sia esposta questa specifica funzionalità dal Behaviors associato all’elemento. Alcuni Behaviors permettono anche di specificare l’elemento target su cui l’azione associata deve essere eseguita come appunto l’esempio appena visto. E’ anche possibile, associare più Behaviors ad un singolo elemento.

Implementare i Behaviors

Per implementare dei nostri Behaviors che possano essere poi utilizzati all’interno di Blend per semplificare il lavoro dello sviluppo dell’interazione con l’applicazione, si possono realizzare delle classi specifiche partendo dalle classi base definite nella libreria principale di questa funzionalità ovvero la dll Microsoft.Expression.Interactivity.dll (nella versione finale di Blend3 e Silverlight 3 il nome della libreria ed alcuni dettagli implementativi potrebbero cambiare).

Un Behaviors è essenzialmente basato su tre concetti fondamentali : Trigger, Action e Behavior. I Trigger rappresentano uno degli elementi principali del funzionamento dei Behaviors, fornendo l’elemento che scatena l’ Action che poi produce lo specifico Behavior voluto.

Ad esempio nel caso del nostro Behaviors StartAnimation il Trigger è l’elemento associato al click sul bottone che produce l’innesco dell’azione (start dell’animazione il cui nome è impostato sulla proprietà specifica).

Per implementare un Trigger o estenderne di esistenti si utilizzando le classi definite nella libreria base citata sopra. Ad esempio per realizzare un nostro Trigger implementeremo una classe BaseBehaviorSample derivando dalla classe base specifica Microsoft.Expression.Interactivity .TriggerBase<DependencyObject> come da esempio:

public class BaseBehaviorSample : Microsoft.Expression.Interactivity.TriggerBase<DependencyObject>

{

}

Questo tipo di Behaviors può essere associato a qualunque oggetto Silverlight che deriva da DependencyObject e per il momento non implementa nessuna particolare azione. La classe TriggerBase<T>

public abstract class TriggerBase<T> : TriggerBase where T : System.Windows.DependencyObject

{

protected TriggerBase();

protected T AssociatedObject { get; }

protected override sealed Type AssociatedObjectTypeConstraint { get; }

}

Deriva a sua volta dalla classe :

[ContentProperty("Actions")]

public abstract class TriggerBase : DependencyObject, IAttachedObject

{

public static readonly DependencyProperty ActionsProperty;

public TriggerActionCollection Actions { get; }

protected DependencyObject AssociatedObject { get; }

protected virtual Type AssociatedObjectTypeConstraint { get; }

public void Attach(DependencyObject dependencyObject);

public void Detach();

protected void InvokeActions(object parameter);

protected virtual void OnAttached();

protected virtual void OnDetaching();

}

L’implementazione dalla classe TriggerBase ci permette di effettuare l’override di due importanti metodi che implementa la classe base OnAttached e OnDetaching che corrispondono ai metodi che vengono invocati rispettivamente all’avvio ed al rilascio degli oggetti creati a partire da questa classe che stiamo realizzando. Il metodo InvokeActions è il metodo che invoca le azioni che vengono eventualmente associate al Trigger e la Dependency Property AssociatedObject rappresenta l’elemento a cui è stato associato il Trigger .

Ad esempio, volendo implementare un Trigger che esegue un’azione sull’evento MouseLeftButtonDown sull’oggetto a cui associamo il trigger scriveremo il seguente codice, agganciando l’evento su cui vogliamo innescare l’azione in OnAttached e rimuovendolo su OnDetaching:

public class BaseTriggerSample: Microsoft.Expression.Interactivity.TriggerBase<UIElement>

{

    protected override void OnAttached()

      {

base.OnAttached();

this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);

}

void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

this.InvokeActions(null);

}

protected override void OnDetaching()

{

base.OnDetaching();

this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);

}

}

In questo modo è evidente che si possono pensare di implementare un’ampia gamma di Triggers che possono poi essere usati come base per invocare specifiche Action. Ad esempio si possono realizzare dei Triggers che si eseguono nel momento in cui cambia lo stato della rete o su uno specifico timer , l’architettura implementata offre una ampia possibilità di estensione ed utilizzo.

Oltre ai Trigger di base si possono implementare Behaviors che hanno un’associazione tra Trigger e una Action. Ad esempio derivando dalla classe TriggerAction :

public abstract class TriggerAction : DependencyObject, IAttachedObject

{

public static readonly DependencyProperty IsEnabledProperty;

protected DependencyObject AssociatedObject { get; }

protected virtual Type AssociatedObjectTypeConstraint { get; }

[DefaultValue(true)]

public bool IsEnabled { get; set; }

public void Attach(DependencyObject dependencyObject);

public void Detach();

protected abstract void Invoke(object parameter);

protected virtual void OnAttached();

protected virtual void OnDetaching();

}

Questa classe oltre ad avere a disposizione i metodi OnAttached e OnDetaching abbiamo la possibilità di implementare l’ovverride del metodo Invoke che è il metodo che viene invocato nel momento in cui si innesca il trigger. Ad esempio di seguito vediamo un semplice esempio che imposta l’opacity dell’oggetto su cui viene implementato:

public class BaseTriggerAction : TriggerAction<UIElement>

{

    public static readonly DependencyProperty OpacityProperty = DependencyProperty.Register("Opacity", typeof(double ), typeof(BaseTriggerAction), new PropertyMetadata(0.5));

    public double Opacity

   {

     get { return (double )this.GetValue(BaseTriggerAction.OpacityProperty ); }

     set { this.SetValue(BaseTriggerAction.OpacityProperty , value); }

   }

    protected override void Invoke(object parameter)

   {

     this.AssociatedObject.Opacity = this.Opacity ;

   }

}

Dall’interfaccia di Blend , quando agganciamo il nostro BaseTriggerAction ad un elemento che deriva da UIElement , ad esempio un bottone:

clip_image010

possiamo anche selezionare su quale Trigger si deve innescare la Action associata come da immagine:

clip_image012

Specifici attributi con cui possiamo decorare la classe ci consentono di predeterminare l’evento su cui si innesca il Trigger. Ad esempio ammettendo di voler implementare un Behaviors che imposti automaticamente il Trigger a Click nel caso in cui lo colleghiamo ad un elemento che deriva da ButtonBase o all’evento MouseLeftButtonDown nel caso di un elemento UIElement generico, potremo decorare con gli specifici attributi la precedente classe come segue:

[DefaultTrigger(typeof(ButtonBase), typeof(Microsoft.Expression.Interactivity.EventTrigger), new object[] { "Click" }),

DefaultTrigger(typeof(UIElement), typeof(Microsoft.Expression.Interactivity.EventTrigger), new object[] { "MouseLeftButtonDown" })]

public class BaseTriggerAction : TriggerAction<UIElement>

{

  public static readonly DependencyProperty OpacityProperty = DependencyProperty.Register("Opacity", typeof(double ), typeof(BaseTriggerAction), new PropertyMetadata(0.5));

  public double Opacity

{

   get { return (double )this.GetValue(BaseTriggerAction.OpacityProperty ); }

   set { this.SetValue(BaseTriggerAction.OpacityProperty , value); }

}

protected override void Invoke(object parameter)

{

    this.AssociatedObject.Opacity = this.Opacity ;

}

}

Derivando da TargetedTriggerAction

public abstract class TargetedTriggerAction<T> : TargetedTriggerAction where T : System.Windows.DependencyObject

{

  protected TargetedTriggerAction();

  protected T Target { get; }

  protected virtual void OnTargetChanged(T oldTarget, T newTarget);

}

Avremo a disposizione la proprietà Target che si potrà utilizzare per definire l’elemento Target della nostra Azione associata al Behavior. Per esempio, tornando al nostro semplice esempio di configurazione dell’Opacity , si potrà definire attraverso la proprietà Target , l’elemento su cui impostare l’Opacity con l’azione implementata sul metodo Invoke:

[DefaultTrigger(typeof(ButtonBase), typeof(Microsoft.Expression.Interactivity.EventTrigger), new object[] { "Click" }),

DefaultTrigger(typeof(UIElement), typeof(Microsoft.Expression.Interactivity.EventTrigger), new object[] { "MouseLeftButtonDown" })]

public class BaseTargetedTriggerAction : TargetedTriggerAction<UIElement>

{

  public static readonly DependencyProperty OpacityProperty = DependencyProperty.Register("Opacity", typeof(double ), typeof(BaseTargetedTriggerAction), new PropertyMetadata(0.5));

  public double Opacity

  {

      get { return (double )this.GetValue(BaseTriggerAction.OpacityProperty); }

      set { this.SetValue(BaseTriggerAction.OpacityProperty, value); }

  }

   protected override void Invoke(object parameter)

   {

      this.Target.Opacity=this.Opacity ;

   }

}

La proprietà Target potrà essere impostata dopo aver associato il BaseTargetedTriggerAction ad un elemento selezionandolo tra i Behaviors disponibili nella Asset Library e trascinandolo sopra l’elemento sull’interfaccia con cui lo si vuole associare, ad esempio un bottone, come già visto sopre per gli altri esempi. Selezionando il Beheviors si prota impostare dalle proprietà anche il Target e all’esecuzione del Trigger (in questo caso il Click sul bottono a cui abbiamo associato il Behavior ) si eseguirà l’azione che imposterà l’Opacity sull’oggetto Target in base al valore della proprietà Opacity del Behaviors.

Il meccanismo Trigger e Action permette di implementare soluzioni per molte esigenze di interazione sull’interfaccia, ma non soddisfa tutti i possibili scenari. Ad esempio nel caso in cui si voglia realizzare un Behaviors che consenta di impostare il comportamento di un oggetto per il Drag and Drop, occorre l’interazione di un modello Trigger-Action che non è limitato all’associazione di una coppia di questi due elementi, ma occorre la possibilità di costruire una combinazione di Trigger e Action per inserirle sempre in modo dichiarativo per ottenere questo tipo di comportamento.

Proprio per questo motivo attraverso la derivazione dalla classe Behavior si possono sviluppare Behaviors che implementano azioni specifiche e renderle associabili anche in modo dichiarativo con lo XAML o attraverso Blend ad uno più specifici Trigger, senza definirne l’associazione in modo vincolante come negli esempi delle classi base viste in precedenza.

Ad esempio implementando un Behaviors nel seguente modo, si consentirà un’enorme flessibilità nell’associare il comportamento a Triggers in modo completamente dichiarativo, fornendo la base per realizzare un’enorme flessibilità sia nello sviluppo delle interfacce che nella possibilità di personalizzazione del template dei controlli.

Derivando dalla classe Behavior si può implementare le Action attraverso l’implementazione di un’interfaccia ICommand :

public class BaseSampleBehavior : Behavior<DependencyObject>

{

  public BaseSampleBehavior()

  {

    this.BaseCommand = new ActionCommand(this.ExecCommand );

  }

  protected override void OnAttached()

  {

   base.OnAttached();

  }

  protected override void OnDetaching()

  {

    base.OnDetaching();

  }

  public ICommand BaseCommand

  {

   get;

   private set;

  }

  private void ExecCommand()

  {

     MessageBox.Show("Behavior") ;

  }

}

Associato il Behaviors implementato con un elemento sull’interfaccia si può dalla finestra delle proprietà del Behaviors associare i Triggers che dovranno produrre l’esecuzione del Command.

clip_image014

Attraverso questo pattern si possono così implementare Behaviors che permettano di gestire complesse interazioni in termini di Trigger e Action , consentendo anche la gestione di uno stato tra le diverse interazioni, fornendo un ottimo modello per la realizzazione di Behaviors che possano notevolmente semplificare lo sviluppo delle parte di interazione della interfaccia utente della nostra applicazione che dovranno produrre l’esecuzione del Command implementato, fornendo una grandissima flessibilità .