Freeform rotation
Next up, let's take a look at a real-world example of how we can put the Math.Atan2() function to work for us. We're going to create an application that allows us to rotate an object by dragging a handle with the mouse. To code along, open the ImageRotate project. This project contains a MainPage. xaml file that has a simple blue gradient fill, and a user control called Rotateltem.
1. Open the Rotateltem.xaml file, and take a look at how the file is structured. There is a root Canvas called ItemCanvas, which contains an Image element and a yellow Ellipse element. The image does not currently have a source assigned—we will be doing this programmatically. The Ellipse element is named Handle and will be used as a handle to rotate the entire container canvas. Note that the container canvas has a named Rotate transform available.
<Canvas x:Name="ItemCanvas" Width="320" Height="240" Canvas.Left="77" Canvas.Top="57" Background="#FFFFFFFF" RenderTransform0rigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup>
<RotateTransform x:Name="RotateItemCanvas" Angle="0"/> </TransformGroup> </Canvas.RenderTransform>
<Image x:Name="Image" Width="300" Height="220" Canvas.Left="10" Canvas.Top="10" Source="" Stretch="Fill"/> <Ellipse x:Name="Handle" Width="15" Height="15" Fill="#FFEAFF00" Stroke="#FF000000" Canvas.Left="313" Canvas.Top="233"/>
2. This time, all of the work is going to be done inside the user control to make the control reusable, so open the RotateImage.xaml.es file.
3. Since we're creating a handle that will be used to rotate an object, we'll need a flag to determine if the mouse has been captured.
We're also going to use three Point variables. The first, MousePosition, is used to get the current mouse position. The next, LastPosition, is used to store the last position of the mouse pointer. The last, CanvasCenter, is a public variable that will be assigned a value when the object is instantiated and is used to provide easy access to the center coordinate of the canvas container.
The final three variables we need are all of type double. We will be storing a CurrentAngle calculation, a LastAngle calculation, and the difference between the two as AngleDelta.
The code for all of the variables is shown in the following listing. This code should be added before the MainPageQ constructor.
private bool IsMouseCaptured;
private Point MousePosition;
private Point LastPosition; public Point CanvasCenter;
private double LastAngle;
private double CurrentAngle;
private double AngleDelta;
4. Following the InitializeComponent() call in the RotateItem() constructor, add event handlers for MouseLeftButtonDown and MouseLeftButtonUp on the element named Handle:
Handle.MouseLeftButtonDown +=
new MouseButtonEventHandler(Handle_MouseLeftButtonDown); Handle.MouseLeftButtonUp +=
new MouseButtonEventHandler(Handle_MouseLeftButtonUp);
5. Inside the event handler function for the MouseLeftButtonDown event, add the code shown in the following listing. This code should look somewhat familiar to you. We are creating a FrameworkElement object named Item, on which the mouse is captured. The cursor is changed to a hand, our Boolean flag that is used to keep track of a drag operation is set to true, and the LastPosition variable is initialized with the current position of the mouse.
private void Handle_MouseLeftButtonDown(object sender,^-
MouseButtonEventArgs e)
FrameworkElement Item = sender as FrameworkElement;
Item.CaptureMouse();
Item.Cursor = Cursors.Hand;
IsMouseCaptured = true;
LastPosition = e.GetPosition(null);
6. The code for the MouseLeftButtonUp event handler function is used to once again create a FrameworkElement object named Item, from which the mouse capture is released. The Boolean flag is changed to false, indicating that the mouse is no longer being captured, and the cursor for the item that was clicked is reset to the default.
private void Handle_MouseLeftButtonUp(object sender,^-
MouseButtonEventArgs e)
FrameworkElement Item = sender as FrameworkElement;
Item.ReleaseMouseCaptureQ;
IsMouseCaptured = false;
Item.Cursor = null;
7. We will also need to add our function to convert radian values to degrees, because we are rotating an object. The Rotate transform angle value for an object in Silverlight is expressed in degrees.
private double RadiansToDegrees(double Radians) {
return Radians * 180 / Math.PI;
8. Now for the good stuff. The handle will point in the direction of the mouse as the mouse moves. Since this is a move operation, we'll need to add an event handler for MouseMove. This code goes inside the RotateItem() constructor with the other handlers:
Handle.MouseMove += new MouseEventHandler(Handle_MouseMove);
9. Inside the event handler function for MouseMove, we'll do the work of figuring out how much the image should be rotated based on the current location of the mouse pointer. The first thing the event handler does is get the current position of the mouse and store it in the
MousePosition variable.
Then, if the mouse is being dragged, LastAngle is calculated by passing the coordinates that result when the center coordinates of the canvas being rotated are subtracted from the last pointer position to the Atan2() method. Notice that they are passed y and then x.
Next, CurrentAngle is calculated using the same method, but by subtracting the canvas center position from the current mouse position.
The difference between the two angles is determined, and the Rotate transform angle of the Canvas object is incremented by the difference after it's converted to degrees.
The LastPosition variable is then updated to the current mouse position for the next time the mouse moves.
private void Handle_MouseMove(object sender, MouseEventArgs e) {
MousePosition = e.GetPosition(null);
if (IsMouseCaptured) {
LastAngle = Math.Atan2(LastPosition.Y - CanvasCenter.Y, LastPosition.X - CanvasCenter.X);
CurrentAngle = Math.Atan2(MousePosition.Y - CanvasCenter.Y,
MousePosition.X - CanvasCenter.X); AngleDelta = CurrentAngle - LastAngle; RotateltemCanvas.Angle += RadiansToDegrees(AngleDelta); LastPosition = MousePosition;
Now, all we need to do is add some code to the MainPage.xaml.es file to instantiate our Rotateltem object and see what kind of results we get.
10. We will be assigning images to Image elements, so we'll need to add a library reference to the list of references at the top of the page:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Systern.Windows.Media.Irnagingj
The System.Windows.Media.Imaging library makes it possible for us to work with bitmaps.
11. Before the MainPage() constructor, create a new instance of the Rotateltem object called
Picturel.
private Rotateltem Picturel = new RotateItem();
12. Inside the MainPage() constructor, assign the source of the Picturel object's Image element, as shown in the following code listing. I've already added two sample JPGs to the project for you to work with. The code tells Silverlight where the Marigold.jpg image is relative to the application.
The left and top properties of the Picturel object are then set to 100 to position it near the top-left corner of the root canvas. Next, the public CanvasCenter property for the object is assigned a value. The angle of the object is preset to -15 degrees so it looks interesting when it loads, and the object is added to the LayoutRoot Canvas.
public MainPage() {
InitializeComponent();
Picturel.Image.Source =
new Bitmaplmage(new Uri("Marigold.jpg", UriKind.Relative)); Picturel.SetValue(Canvas.LeftProperty, 100.00); Picturel.SetValue(Canvas.TopProperty, 100.00);
Picturel.CanvasCenter.X =
(double)Picturel.GetValue(Canvas.LeftProperty) + Picturel.Width / 2;
Picturel.CanvasCenter.Y =
(double)Picturel.GetValue(Canvas.TopProperty) + Picturel.Height / 2;
Picturel.RotateltemCanvas.Angle = -15;
LayoutRoot.Children.Add(Picturel);
Compile and run the application. You should get something similar to Figure 6-16. Dragging the yellow handle with the mouse will rotate the image. Notice how the handle always points to the location of the mouse pointer.
- Figure 6-16. The ImageRotate project creates images with rotate handles.
When the program runs, the code that does the rotation is essentially saying "Here are the coordinates of the mouse. Draw a line from the center of the canvas to these coordinates, calculate the angle that forms, and then calculate the angle difference between this angle and the last angle." This code runs constantly as the mouse moves, calculating the angle offsets in real time. In the application, the movements can be very small, but Figure 6-17 shows larger-scale movement to illustrate the code functionality.
- Figure 6-17. As the mouse is dragged, the code constantly calculates the new angle based on the distance from the center of the canvas to the mouse position.
Since the Rotateltem user control is completely self-contained, it takes about a minute to add another instance of the object. You can add the following code to get a second image in the application.
13. Start by declaring a second object, named Picture2: private Rotateltem Picture2 = new RotateItem();
14. Next, set the initial properties for the new object as per the following code listing. Be sure to reference the new object (Picture2) when setting the CanvasCenter property, or the rotation will give unexpected results.
Picture2.Image.Source =
new Bitmaplmage(new Uri("PurpleFlower.jpg", UriKind.Relative)); Picture2.SetValue(Canvas.LeftProperty, 400.00); Picture2.SetValue(Canvas.TopProperty, 300.00); Picture2.CanvasCenter.X =
(double)Picture2.GetValue(Canvas.LeftProperty) + Picture2.Width / 2; Picture2.CanvasCenter.Y =
(double)Picture2.GetValue(Canvas.TopProperty) + Picture2.Height / 2; Picture2.RotateItemCanvas.Angle = -15; LayoutRoot.Children.Add(Picture2);
If you compile and run the project at this point, you'll see both of the images drawn, and each can be rotated independently. If you'd like to use your own images, add them to the project by right-clicking the project name in Visual Studio's Solution Explorer and selecting Add > New Item from the menu, as shown in Figure 6-18. They will then be available for use in the same way as the original two. The code shown in this example is available in the ImageRotateCompleted project.
- Figure 6-18. Right-click the project in Solution Explorer, and select Add >■ New Item to add your own images to the project.
Average user rating: 5 stars out of 2 votes
Post a comment