Silverlight 3 Beta brings along, as we all know, many new possibilities
in terms of graphics rendering and assets' management if compared with its previous version.
With this article I aim to describe a couple of these brand new features and, at the same time,
to provide some "useful" code (at least I'll try).
So let's pick out from the lottery bulle the
PlaneProjection class and the Behavior extension that comes with Blend 3 libraries.
Before we start I think an introduction could be useful:
- PlaneProjection: that's the one and only implementation of the
base class System.Windows.Media.Projection so far. It is also some serious break-through income
into the "flat" (in the bidimensional meaning) world of Silverlight, unfortunately it just permits
trivials 3D effects and developers cannot pretend to lean on it alone to implement a fully featured 3D engine.
Hopefully it won't remain alone for long...
You can see plane projection in action by giving appropriate value to UIElement's
Projection property.
- Behavior (aka Microsoft.Expression.Interactivity.Behavior<T>):
in its concept it is similar to the Sys.UI.Behavior of ASP.Net Ajax Framework, it allows
to centralize, reuse and atomize functionalities in addition to the standard native ones of
any Silverlight object.
Implementation is straightforward, it is just needed to override the base methods
OnAttached and OnDetaching (think as about initialize and dispose
in ASP.Net Ajax) and bind the just built behavior to the desired element by some tiny markup
declarations...
We're still missing the goal of this tutorial: we want to create a TrackballBehavior that
permit, once attached to an UIElement, to rotate it in a 3D fashion using mouse and keyboard modifiers.
Warning: this is a simplified trackball implementation, the resulting behavior is similar
to that you are maybe get used in the Autodesk® 3ds Max environment by combining
Alt+MiddleClick+Drag.
Here's the code of the 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;
}
}
}
So how does our Behavior ...behave?! The gesture Ctrl+MouseDown triggers it, then
by dragging around you'll se the associated object rotating consequently.
Shift+Click resets the default position by removing the rotations.
A simple sample aplication is picted by the following Xaml markup: TrackballBehavior
is there associated to an image object.
<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>
Live demo is available, so it is the source code.
Nota bene: demo and code have been implemented on top of Silverlight 3 Beta, I
do not tend
nor intend to
modify the content of this article to match the future releases of the Silverlight plugin.
«
Silverlight TrackballBehavior
Take care. Bye.
Feedbacks
no feedbacks yet.