Rotating groups of items in WPF

 

No doubt you've grinned with delight at the ease with which you can rotate a single shape about its own axis, or any other axis for that matter. Simply add an adorner, and start rotating.

Then you think - Now, I'll group two items together, and rotate them. This seems to me like a reasonable thing to do. We've been doing it in other drawing applications for years.

But the grin disappears. It all seems so inordinately difficult.

Here is a little something which may help you on your way. The key is to create a list of shapes using List<Shape> = new List<Shape>(); then adding all of the shapes you wish to rotate to the list, and iterate through the list rotating each member against the centre point of rotation required.

In this case, the centre of the canvas is the centre of rotation. Click and drag around the edge with the mouse, and the two ellipses rotate. The rectangle is not part of the selection, so does not rotate.

Sorry about the colours.

Here's the XAML

<Window x:Class="TestEllipseMovement.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="300"

        Loaded="Window_Loaded"

        >

    <Grid>

        <Canvas x:Name="canvas"

                MouseLeftButtonDown="canvas_MouseLeftButtonDown"

                MouseMove="canvas_MouseMove"

                MouseLeftButtonUp="canvas_MouseLeftButtonUp"

                Background="Yellow"

                Height="Auto" Width="Auto"

            >

<Rectangle Height="30" Width="40" Canvas.Left="100" Canvas.Top="100" Fill="PaleTurquoise" />

        </Canvas>

    </Grid>

</Window>

And here's the code behind

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

 

namespace TestEllipseMovement

{

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

        }

 

        List<Shape> list = new List<Shape>();

 

        Ellipse e1;

        Ellipse e2;

 

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            SolidColorBrush brush = Brushes.Orange;

            e1 = new Ellipse();

            e1.Height = 10;

            e1.Width = 14;

            e1.Fill = brush;

            e2 = new Ellipse();

            e2.Height = 10;

            e2.Width = 14;

            e2.Fill = brush;

            canvas.Children.Add(e1);

            canvas.Children.Add(e2);

            Canvas.SetLeft(e1, 50);

            Canvas.SetTop(e1, 50);

 

            Canvas.SetLeft(e2, 200);

            Canvas.SetTop(e2, 200);

 

            centre = new Point(this.ActualWidth / 2, this.ActualHeight / 2);

 

            list.Add(e1);

            list.Add(e2);

        }

 

        RotateTransform rotate;

        Point from, to, centre;

 

        private void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            from = e.GetPosition(canvas);

        }

 

        private void canvas_MouseMove(object sender, MouseEventArgs e)

        {

            if (e.LeftButton==MouseButtonState.Pressed)

            {

                to = e.GetPosition(canvas);

                double angleChange = Vector.AngleBetween(from - centre, to - centre);

 

                foreach (Shape shape in list)

                {

                    rotate = shape.RenderTransform as RotateTransform;

                    if (rotate == null)

                    {

                        rotate = new RotateTransform(angleChange, canvas.ActualWidth / 2 - Canvas.GetLeft(shape), canvas.ActualHeight / 2 - Canvas.GetTop(shape));

                        shape.RenderTransform = rotate;

                    }

                    else

                        rotate.Angle += angleChange;

                }

                from = to;

            }

        }

        private void canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {

        }

 

    }

}

Click here to download the project files

Rotating text

To be even cleverer, it is possible to rotate by repositioning. This technique is used to move an object around a point without rotating it about its own axes. For example, if you wish to rotate something with some text attached, but want the text to remain horizontal.

In this case, another type of transform is required, but first a picture:

The example shows four textblocks. Two are rotated using the method described above, and the other two are repositioned alongside the ellipses. The code required to rotate the text is the same as to rotate the ellipses, except the textblocks are held in their own List.

To keep the code simple, I have not used a List to reposition the textblocks as further code would be required in the form of a dictionary and lookup to keep the textblock attached to the relevant ellipse.

Point pt = new Point(0, 0);

GeneralTransform g = e1.TransformToVisual(canvas);

Point p = g.Transform(pt);

Canvas.SetLeft(tb2, p.X);

Canvas.SetTop(tb2, p.Y);

The key is to get the GeneralTransform, which allows calculation of the position of the ellipse upon the canvas, In this case, GetLeft() and GetTop() do not give the 'right' answer as the canvas contents have been transformed.

Click here to download the project files