Silverlight 3 Beta presenta, come ormai noto, maggiori possibilità
in termini di resa grafica e di gestione degli asset applicativi rispetto alla versione 2.0.
Provo a scrivere questo articolo sia per trattare di due di queste nuove caratteristiche sia per fornire
del codice - spero - "utile".
Peschiamo quindi dal calderone la classe
PlaneProjection e, dalle librerie di Blend, la funzionalità di Behavior attaching.
Una breve introduzione a questi due nuovi individui:
- PlaneProjection: è, al momento, l'unica implementazione della classe base
System.Windows.Media.Projection e si tratta di un ingresso non da poco nel "piatto" (nel senso di
bidimensionale) mondo di Silverlight. Rimarrà la sola? ho "ricevuto
rassicurazioni" in merito a tale preoccupazione: per quanto potente infatti, permette di
ottenere solo effetti tridimensionali banali ed il suo utilizzo come fondamenta
di un engine 3D non è neanche da considerarsi.
Viene utilizzata valorizzando la property Projection di un qualsivoglia UIElement.
- Behavior (ovvero Microsoft.Expression.Interactivity.Behavior<T>):
è analogo al Sys.UI.Behavior del framework ASP.Net Ajax, permette cioè di centralizzare,
riutilizzare e atomizzare funzionalità aggiuntive a quelle standard dei vari oggetti Silverlight.
L'implementazione è semplice consiste nello scrivere codice in override ai due metodi base OnAttached e OnDetaching
(per mantenere il parallelismo con ASP.Net Ajax, pensiamo ai corrispettivi initialize e dispose) ed aggiungere in pure declarative Xaml un minimo di markup
all'oggetto associato...
Definiamo ora lo scopo di questo tutorial: creare un TrackballBehavior che, quando innestato su un UIElement,
permetta di ruotarlo tridimensionalmente tramite mouse e tastiera.
Avviso: la matematica utilizzata semplifica l'implementazione completa di una Trackball,
quella proposta restituisce un comportamento simile a quello dell'ambiente di lavoro di
Autodesk® 3ds Max utilizzando la combinazione Alt+MiddleClick+Drag.
Vediamo il codice del Behavior:
public class TrackballBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
// custom initialization
this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_Reset;
}
protected override void OnDetaching()
{
// custom disposure
this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_Reset;
//
base.OnDetaching();
}
private bool active = false;
private Size size = Size.Empty;
private Point origin = new Point();
PlaneProjection existingProj = null;
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (active)
{
active = false;
Application.Current.RootVisual.MouseMove -= this.AssociatedObject_MouseMove;
Application.Current.RootVisual.MouseLeftButtonUp -= this.AssociatedObject_MouseLeftButtonUp;
size = Size.Empty;
e.Handled = true;
}
}
void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (!active) return;
double d = Math.Min(size.Width, size.Height);
Point actual = e.GetPosition(Application.Current.RootVisual);
double delta_x = actual.X - origin.X;
double delta_y = actual.Y - origin.Y;
// do the maths...
double angle_x = 90D * delta_y / d;
double angle_y = -90D * delta_x / d;
double angle_z = 0D;
if (this.existingProj != null)
{
// I don't take care of center and offsets,
// improve it yourself if you do... ;)
angle_y += existingProj.RotationY;
angle_z += existingProj.RotationZ;
double ycheck = Math.Abs(existingProj.RotationY % 360D);
if (270D > ycheck && ycheck > 90D)
angle_x *= -1D;
angle_x += existingProj.RotationX;
}
this.AssociatedObject.Projection = new PlaneProjection
{
CenterOfRotationX = .5D,
CenterOfRotationY = .5D,
CenterOfRotationZ = .5D,
RotationX = angle_x,
RotationY = angle_y,
RotationZ = angle_z
};
}
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!active)
{
active = Keyboard.Modifiers == ModifierKeys.Control;
if (active)
{
if (size.IsEmpty)
size = this.AssociatedObject.RenderSize;
origin = e.GetPosition(Application.Current.RootVisual);
Application.Current.RootVisual.MouseMove += this.AssociatedObject_MouseMove;
Application.Current.RootVisual.MouseLeftButtonUp += this.AssociatedObject_MouseLeftButtonUp;
if (this.AssociatedObject.Projection as PlaneProjection != null)
this.existingProj = (PlaneProjection)this.AssociatedObject.Projection;
else
this.existingProj = null;
e.Handled = true;
}
}
}
void AssociatedObject_Reset(object sender, MouseButtonEventArgs e)
{
if (!active && Keyboard.Modifiers == ModifierKeys.Shift)
{
this.AssociatedObject.Projection = null;
e.Handled = true;
}
}
}
Come si comporta quindi il nostro TrackballBehavior? La gesture da utilizzare per innescarlo
è Ctrl+MouseDown, trascinando il mouse si vede ruotare l'elemento associato.
Shift+Click invece resetta la rotazione ad un valore nullo.
Un semplice caso d'applicazione può essere quello seguente, nel quale il TrackballBehavior
è associato ad un'immagine:
<UserControl x:Class="TestSilverlightApplication.Trackball"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
xmlns:pacem="clr-namespace:Pacem.Silverlight.Toolkit;assembly=Pacem.Silverlight.Toolkit"
>
<Grid x:Name="LayoutRoot" Background="White">
<Image Source="images/sample.jpg" x:Name="img" Width="400" Height="225"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Projection>
<PlaneProjection CenterOfRotationX="0.5"
CenterOfRotationY="0.5"
CenterOfRotationZ="0.5"
RotationY="30"/>
</Image.Projection>
<i:Interaction.Behaviors>
<pacem:TrackballBehavior x:Name="trackball"/>
</i:Interaction.Behaviors>
</Image>
</Grid>
</UserControl>
È visionabile una demo di tale behavior, mentre il codice riportato sopra
è scaricabile dal link sottostante.
Nota bene: demo e codice sono stati implementati sopra Silverlight 3 Beta,
non prevedo
né intendo modificare questo articolo per aggiornarlo alle future versioni di Silverlight.
«
Silverlight TrackballBehavior
Take care. Bye.
Feedbacks
no feedbacks yet.