Non c'è che dire, è un periodo eccezionale per sviluppatori e appassionati di nuove tecnologie. È il momento delle interfacce utente "natural", ossia quelle interfacce che permettono una diretta interazione dell'utente mediante multi-touch, riconoscimento vocale o del movimento, tecnologie ormai disponibili all'utente finale ad un prezzo decisamente accettabile.
A tutti forse sono familiari quelle scene del film Minority Report nelle quali il protagonista si divertiva a spostare immagini e informazioni sullo schermo di un calcolatore mediante il semplice movimento delle mani. All'epoca sembrava proprio fantascienza, oggi basta recarsi in un qualsiasi centro commerciale, acquistare un personal computer HP Touchsmart e il gioco è fatto. Insomma: benvenuti nel futuro!
Eppure gli strumenti disponibili al programmatore, fino a qualche mese fa, non erano sufficienti per l'implementazione di "user experience" sofisticate. Il rilascio di Windows 7 ha sicuramente segnato una tappa importante integrando una serie di funzionalità dedicate all'interfacciamento con sensori esterni mediante la Windows Sensor And Location platform e un nuovo insieme di Touch API che permettono lo sviluppo di codice dedicato al controllo e interpretazione di azioni multi-touch, tecnicamente denominate gestures, generate dalla diretta azione dell'utente sullo schermo del personal computer.
Queste azioni possono essere semplici, come ad esempio il "toccare" un oggetto sullo schermo e trascinarlo senza usare il mouse, fino ad arrivare a comportamenti più complessi: il ruotare o ridimensionare componenti visuali presenti nell'applicazione con l'azione di due o più dita. Ce n'è per tutti i gusti: la lista completa delle gesture utilizzabili in Windows 7 è disponibile nella sezione MSDN Windows Touch Gestures Overview (figura 1).
Per poter impiegare tali funzionalità è necessario disporre di un personal computer dotato di uno schermo abilitato per il multi-touch, come, per citarne due tra i tanti, i modelli HP della serie TouchSmart oppure l'Acer Aspire 5738PG. Nel caso non si disponesse di un hardware touch-enabled, è possibile utilizzare per i test il progetto multi-touch Vista disponibile su Codeplex.
Multi-touch da surface a Silverlight
Le funzionalità presenti in Silverlight sono derivate direttamente dalle API presenti nel progetto Surface e dall'implementazione di un sottoinsieme delle stesse presente in WPF (Windows Presentation Foundation), il sottosistema grafico incluso in alcune versioni di Windows.
La derivazione non implica per forza la presenza di tutte le caratteristiche che, viste le dimensioni del plug-in Silverlight, sono attualmente ridotte in numero e funzionalità.
La prima implementazione multi-touch in Silverlight è apparsa nella versione 3 e contiene, tra gli altri, i seguenti oggetti presenti nel namespace System.Windows.Input. L
a classe "Touch" in grado di gestire gli eventi generati comprende:
- Un evento
Touch.FrameReported
che viene generato al verificarsi della pressione sullo schermo; - Una classe
TouchFrameEventArgs
impiegata dal precedente evento in grado di fornire informazioni dettagliate sui punti di contatto (touch points) correntemente selezionati dall'utente; - Una classe
TouchPoint
in grado di fornire le coordinate (X,Y) dei touch point e la modalità di interazione dell'utente (pressione, movimento o rilascio degli stessi).
Tali oggetti sono già sufficienti per la realizzazione di qualsiasi implementazione touch, sebbene interazioni più complesse (come rotazione, ridimensionamento di oggetti, effetti di "inerzia" corrispondenti all'accelerazione/decelerazione del movimento in base a parametri prefissati) richiedono, allo stato attuale, un bel po' di lavoro aggiuntivo.
Per tali casistiche è comunque possibile impiegare framework disponibili online, come il Microsoft Surface Manipulations and Inertia Sample for Microsoft Silverlight oppure le seguenti risorse predisposte dall'autore all'interno della "Microsoft Expression Gallery" e su Codeplex:
- Silverlight Multi-Touch Manipulation and Inertia Behavior (Silverlight/Expression Behavior per abilitare le gesture di traslazione, zoom, rotazione ed effetti di inerzia per un generico FrameworkElement;)
- Silverlight 4 and WPF 4 Multi-Touch Manipulation (oltre alla Behavior descritta precedentemente, questo progetto contiene anche un controllo WPF per abilitare le medesime gestures in applicazioni WPF4.)
Intercettare gli eventi Touch
Proviamo a costruire un primo esempio in grado di intercettare gli eventi touch generati e visualizzare le coordinate dei punti selezionati e la relativa action associata a ciascuno di essi. Per iniziare creiamo una nuova "Silverlight application" e aggiungiamo un controllo DataGrid per visualizzare le coordinate (X,Y) e l'Action dei Touch points correntemente selezionati:
Listato 1: MainPage.xaml
<UserControl x:Class="BasicMultiTouch.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Canvas x:Name="LayoutRoot" Background="White">
<my:DataGrid x:Name="touchPoints_DataGrid" AutoGenerateColumns="True" />
</Canvas>
</UserControl>
Di seguito la definizione dell'handler per l'evento Touch.FrameReported
:
Listato 2: MainPage.xaml.cs
public partial class MainPage
{
public MainPage()
{
InitializeComponent();
Touch.FrameReported += Touch_FrameReported;
}
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
touchPoints_DataGrid.ItemsSource = e.GetTouchPoints(this);
foreach (var touchPoint in e.GetTouchPoints(this))
{
if (touchPoint.Action == TouchAction.Up) continue;
var point = new Ellipse()
{
Width = 40,
Height = 40,
Fill = new SolidColorBrush(Colors.Green)
};
point.SetValue(Canvas.TopProperty, touchPoint.Position.Y - 64);
point.SetValue(Canvas.LeftProperty, touchPoint.Position.X - 64);
LayoutRoot.Children.Add(point);
}
}
}
La collezione e.GetTouchPoints()
permette il recupero di tutti i touchPoints correntemente selezionati dall'utente. Per ciascuno di essi è possibile verificarne lo stato (action) ovvero:
Stato (Action) | Descrizione |
---|---|
TouchAction.Down |
il point è stato appena selezionato, in modo simile al tradizionale evento MouseDown |
TouchAction.Up |
il point è stato appena rilasciato |
TouchAction.Move |
il point è in movimento ed è possibile recuperarne le coordinate mediante tp.Position.X , tp.Position.Y |
Nell'esempio descritto, per ogni azione che sia una "Up" o "Move", andremo ad aggiungere alla nostra client-area un controllo "Ellipse" corrispondente al touch-point selezionato in modo da creare un'applicazione simile a Paint. Di seguito è disponibile l'output:
Un passo avanti: abilitare le gesture
Vediamo ora come abilitare delle gesture più complesse mediante l'implementazione di una Silverlight/Blend Behavior che permetta l'abilitazione di un effetto zoom per il controllo a cui viene applicata. Il codice è abbastanza semplice: nel caso in cui il numero di touch points selezionati sia uguale a due, verifichiamo la distanza tra i due punti e applichiamo una ScaleTransform
:
Listato 3: TouchAction.Down
private void AssociatedObjectTouchDown(object sender, TouchFrameEventArgs e)
{
//Initialize the parameters
this._touchDown = true;
this.AssociatedObject.Opacity = .75;
this._tpInitialPoints = e.GetTouchPoints(this.AssociatedObject);
//Initialize the Scale transform
if (this._scaleTransform == null)
{
this._scaleTransform = new ScaleTransform();
if ((this.AssociatedObject.RenderTransform == null) || !(this.AssociatedObject.RenderTransform is TransformGroup))
this.AssociatedObject.RenderTransform = new TransformGroup();
if (this.AssociatedObject.RenderTransform is TransformGroup)
(this.AssociatedObject.RenderTransform as TransformGroup).Children.Add(this._scaleTransform);
}
}
Listato 4: TouchAction.Move
private void AssociatedObjectTouchMove(object sender, TouchFrameEventArgs e)
{
if (this._touchDown)
{
//Find the new points
TouchPointCollection newPoints = e.GetTouchPoints(AssociatedObject.Parent as FrameworkElement);
if (newPoints!=null && this._tpInitialPoints!=null)
if (this._tpInitialPoints.Count == 2 && newPoints.Count == 2)
{
// Zoom the Control using the ScaleTransform
double ratio = this.Distance(newPoints.First().Position, newPoints.Last().Position) /
this.Distance(this._tpInitialPoints.First().Position, this._tpInitialPoints.Last().Position);
if (ratio != double.NaN)
{
Point midPoint = this.MidPoint(newPoints.First().Position, newPoints.Last().Position);
this._scaleTransform.CenterX = midPoint.X; this._scaleTransform.CenterY = midPoint.Y;
this._scaleTransform.ScaleX = ratio; this._scaleTransform.ScaleY = ratio;
}
}
}
}
Listato 5: TouchAction.Up
private void AssociatedObjectTouchUp(object sender, TouchFrameEventArgs e)
{
// turn off Zoom mode
this._touchDown = false;
this.AssociatedObject.Opacity = 1;
}
Ed ecco il codice relativo alla Silverlight/Blend behavior:
Listato 6: Silverlight/Blend behavior
public class TouchZoom : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
Touch.FrameReported += AssociatedObjectTouchFrameReported;
}
protected override void OnDetaching()
{
base.OnDetaching();
Touch.FrameReported -= AssociatedObjectTouchFrameReported;
}
private void AssociatedObjectTouchFrameReported(object sender, TouchFrameEventArgs e)
{
if (this.TouchZoomEnabled)
{
TouchPointCollection tpCollection = e.GetTouchPoints(this.AssociatedObject);
tpCollection.ToList().
ForEach(tp =>
{
switch (tp.Action)
{
case TouchAction.Up:
this.AssociatedObjectTouchUp(sender, e);
break;
case TouchAction.Move:
this.AssociatedObjectTouchMove(sender, e);
break;
case TouchAction.Down:
this.AssociatedObjectTouchDown(sender, e);
break;
}
});
}
}
}
L'utilizzo della stessa in XAML è quindi molto semplice. Il codice che proponiamo contiene un riferimento ad una ulteriore Behavior denominata TouchDrag
che abilita anche il trascinamento dell'oggetto nello schermo con il semplice movimento delle dita. L'implementazione completa è disponibile nel codice allegato all'articolo.
Listato 7: Codice Xaml per il trascinamento dell'oggetto
<UserControl x:Class="SilverlightMultiTouch.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Behaviors="clr-namespace:SilverlightMultiTouch.Behaviors">
<Canvas Background="Black">
<Canvas>
<interactivity:Interaction.Behaviors>
<Behaviors:TouchDrag TouchDragEnabled="True"/>
<Behaviors:TouchZoom TouchZoomEnabled="True"/>
</interactivity:Interaction.Behaviors>
<Image Source="Images/image.png" Height="597" Width="448" Canvas.Left="404" Canvas.Top="96"/>
</Canvas>
</Canvas>
</UserControl>
Utilizzo di gesture complesse: manipulation e inertia
L'impiego di una (o più) Blend behavior per l'abilitazione di funzionalità multi-touch rende, a mio avviso, il codice molto elegante, modificabile via Blend e semplice da riutilizzare in progetti diversi. A questo scopo, a partire dal Surface Manipulation and Inertia Sample for Silverlight già menzionato, ho realizzato una Silverlight/Blend Behavior disponibile per il download all'indirizzo http://gallery.expression.microsoft.com/en-us/MultiTouch.
Tale codice permette l'abilitazione delle seguenti gesture ad un generico FrameworkElement
:
- Drag/Traslazione dell'elemento con uno o più touch point;
- Scale/Zoom con due dita;
- Rotation/Rotazione con due dita;
- Effetto "inertia" per l'applicazione di effetti di accelerazione/decelerazione alle gesture precedentemente elencate.
L'utilizzo in XAML è riportato di seguito:
Listato 8: manipolazione complesse
<Canvas>
<Image Source="Images/Desert.jpg" x:Name="image1">
<interactivity:Interaction.Behaviors>
<behaviors:MultiTouchManipulationBehavior InertiaEnabled="True"
TouchRotateEnabled="True"
TouchTranslateEnabled="True"
TouchScaleEnabled="True" />
</interactivity:Interaction.Behaviors>
</Image>
<p:CoreSmoothStreamingMediaElement x:Name="video" Background="{StaticResource GlossyBlack_BackgroundGradient}"
AutoPlay="True" SmoothStreamingSource="http://video3.smoothhd.com.edgesuite.net/ondemand/Big%20Buck%20Bunny%20Adaptive.ism/Manifest">
<interactivity:Interaction.Behaviors>
<behaviors:MultiTouchManipulationBehavior InertiaEnabled="True"
TouchRotateEnabled="True"
TouchTranslateEnabled="True"
TouchScaleEnabled="True"/>
</interactivity:Interaction.Behaviors>
</p:CoreSmoothStreamingMediaElement>
<Image Source="Images/Jellyfish.jpg" x:Name="image2">
<interactivity:Interaction.Behaviors>
<behaviors:MultiTouchManipulationBehavior InertiaEnabled="False"
TouchRotateEnabled="False"
TouchTranslateEnabled="True"
TouchScaleEnabled="True"/>
</interactivity:Interaction.Behaviors>
</Image>
</Canvas>
Di seguito (figura 3) è possibile visualizzare l'utilizzo della Behavior in Expressio Blend
Conclusioni
In questo articolo abbiamo analizzato l'implementazione multi-touch presente in Silverlight e qualche esempio di applicazione impiegando i tipi disponibili nel namespace System.Windows.Input
e alcune implementazioni di gesture disponibili in rete.
Tali implementazioni consentono, già da subito, lo sviluppo di applicazioni complesse con interfacce multi-touch avanzate in grado di essere eseguite all'interno di un browser impiegando semplicemente il plug-in Silverlight e non richiedendo quindi l'installazione del .NET framework.
Bibliografia
- Joshua Blake, Multitouch on Windows - NUI Development with WPF and Silverlight, Manning, 2010
- Jeff Paries, Foundation Silverlight 3 Animation, Friends of ED, 2009
Note sull'autore
Davide Zordan è Microsoft Silverlight MVP (Most Valuable Professional) e Silverlight Insider. Vive e lavora come insegnante e consulente relativamente a Silverlight / WPF in provincia di Vicenza. È rintracciabile on-line all'indirizzo http://davidezordan.net