Resumé

Silverlight 3 Beta TrackballBehavior

Cristian Merighi () 0,00

Combinare la PlaneProjection ed i Behaviors di Blend 3 per manipolare interattivamente la rotazione di un UIElement in Silverlight 3 Beta.
Questo articolo è da considerarsi obsoleto. Alcune funzionalità potrebbero non essere più disponibili e non è possibile aggiungere commenti.

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 intendo modificare questo articolo per aggiornarlo alle future versioni di Silverlight.

zip file « Silverlight TrackballBehavior

Take care. Bye.

feedback
 

Syndicate

Autore

Cristian Merighi facebook twitter google+ youtube

Ultimi articoli

Top rated

Archivio

Dove sono?

Autore

Cristian Merighi facebook twitter google+ youtube

Le mie letture

Feeds