Move and resize multiple shapes in WPF and C#

[multiple shapes]

The example Move and resize multiple rectangles in WPF and C# shows how to let the user move and resize multiple rectangles in a WPF program. This example extends that one to let you move and resize multiple shapes.

See the earlier example for the basic idea.

Instead of storing a list of rectangles that you can move, this example stores a list containing multiple shapes that you can move. The following code declares and initializes the list.

// The shapes that the user can move and resize.
private List<Shape> Shapes;

// Make a list of the shapes that the user can move.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    Shapes = new List<Shape>();
    foreach (UIElement child in canvas1.Children)
    {
        if (child is Shape)
            Shapes.Add(child as Shape);
    }

    // Reverse the list so the shapes on top come first.
    Shapes.Reverse();
}

The program’s window holds a Canvas control that contains an Ellipse, Polygon, and Rectangle. It also contains a Border to show that you don’t need to let the user move every control.

The window’s Loaded event handler creates the list and then loops through the Canvas control’s children. It adds the Shape controls that it finds to the Shapes list.

After it has examined all of the children, the method reverses the order of the Shapes list so they appear inside the list in top-to-bottom order.

The following method determines what if anything is at a particular point.

// The part of the rectangle under the mouse.
private HitType MouseHitType = HitType.None;

// The shape that was hit.
private Shape HitShape = null;

// If the point is over any shape,
// return the shape and the hit type.
private void FindHit(Point point)
{
    HitShape = null;
    MouseHitType = HitType.None;

    foreach (Shape shape in Shapes)
    {
        MouseHitType = SetHitType(shape, point);
        if (MouseHitType != HitType.None)
        {
            HitShape = shape;
            return;
        }
    }

    // We didn't find a hit.
    return;
}

This method loops through the Shapes list and calls the SetHitType method for each of the controls in the list. If SetHitType finds that the target point is over a shape, then FindHit saves the hit type and the shape hit in form-level variables and exits.

Much of the rest of the program is similar to the earlier example. The SetHitType method compares the mouse’s position to the shape’s left, right, top, and bottom coordinates to see if the point is over one of the shape’s corners, edges, or body.

One big change to the method is how it finds the shape’s left, right, top, and bottom coordinates. The earlier version used Canvas.GetLeft and Canvas.GetTop to get the shape’s left and top coordinates. That works for most shapes but doesn’t work for polygons.

A polygon’s Points list contains the points that make up the polygon. The coordinates of those points are relative to the polygon’s left and top coordinates within the Canvas control, so the program must add the Points coordinates to the left and top values to see where the polygon actually lies.

The new version of the SetHitType method calls the following GetLRTB method to get the shape’s actual position.

// Return the shape's left, right, top, and bottom.
private void GetLRTB(Shape shape,
    out double left, out double right,
    out double top, out double bottom)
{
    if (!(shape is Polygon))
    {
        left = Canvas.GetLeft(shape);
        top = Canvas.GetTop(shape);
        right = left + shape.ActualWidth;
        bottom = top + shape.ActualHeight;
        return;
    }

    // Handle polygons separately.
    Polygon polygon = shape as Polygon;
    left = polygon.Points[0].X;
    right = left;
    top = polygon.Points[0].Y;
    bottom = top;
    foreach (Point point in polygon.Points)
    {
        if (left > point.X) left = point.X;
        if (right < point.X) right = point.X;
        if (top > point.Y) top = point.Y;
        if (bottom < point.Y) bottom = point.Y;
    }

    // Add the polygon's left and top coordinates.
    left += Canvas.GetLeft(shape);
    right += Canvas.GetLeft(shape);
    top += Canvas.GetTop(shape);
    bottom += Canvas.GetTop(shape);
}

If the shape is not a polygon, this method uses the same method used by the previous program. It uses the Canvas class’s GetLeft and GetTop methods to get the shape’s location on the Canvas control. It then adds the shape’s actual width and height to get its right and bottom edges.

If the shape is a polygon, the method loops through its points to find the largest and smallest X and Y coordinates. It then adds the shape’s left and top positions on the Canvas control.

The rest of the program is similar to the earlier example, although it has some problems. First, it doesn’t work with polygons. We need to modify the code to adjust the polygon’s points rather than changing its left and top coordinates on the Canvas control. I’ll fix that in my next post.

Second, the program may think it has hit part of a shape when the mouse is not actually over the shape. For example, when the mouse is over the upper left corner of an ellipse’s bounding rectangle, it is not actually over the ellipse itself. In fact, the mouse may be over another shape that lies below the ellipse and the program will still think the mouse is over the ellipse. This wasn’t a problem when we were moving only rectangles because a rectangle coincides with its bounding rectangle. Right now I don’t see a great need to improve this part of the program so I’m not going to fix this.

Download the example to experiment with it and to see additional details. If you like, try to fix the program so it can move and resize polygons. I’ll show my solution in my next post.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in controls, graphics, wpf, XAML and tagged , , , , , , , , , , , . Bookmark the permalink.

7 Responses to Move and resize multiple shapes in WPF and C#

  1. Pingback: Move and resize polygons in WPF and C# - C# HelperC# Helper

  2. Aso says:

    Dear Sir
    Would you please look at this youtube video which its link bellow, and tell me how they do it… they used DevExpress.com UI control…

    https://www.youtube.com/watch?v=czd4P_ZWyEs
    time line:7:50second

    my regards

    • RodStephens says:

      What feature are you wondering about? Or is it the whole thing? I can’t really tell what he’s doing and can’t understand what he’s saying.

      I don’t know much about the DevExpress UI control so I won’t be able to say exactly how they used that control, but I may have ideas about how you could do it with WindowsForms.

  3. Aso says:

    Ok Sir…
    What I want to learn about this application is that:
    How the user can change the picture so easily, he rotate the tooth and turn its color and select part of the tooth so easily…
    To be exact see:
    https://www.youtube.com/watch?v=czd4P_ZWyEs
    time line: 6:43 second

    That is what I wounder about!
    Is this a picture (an image) or its a User Control he put some button above that picture and turn it as he wanted…

    My regards

    • RodStephens says:

      My guess is that this is equivalent to a PictureBox and the program is drawing on it. The program may have a hierarchy of objects. Each tooth may include its fillings so they can easily translate and rotate together.

      You can use transformations to translate and rotate an image fairly easily and quickly. For example, see Interactively rotate images in C#.

  4. Aso says:

    Dear Sir
    How can I record button actions (click), and undo its action…
    Is there a way to store all buttons (as an object) with all its action as a collection and undo their actions any time a user wanted.

    OR: How do you create an event to undo the previous event in WPF with C#?

    Thank for your reply

    • RodStephens says:

      You’re going to need some sort of data structure to hold the actions and undo them. It’s usually easier to keep track of the state of the system after each operation and then restore a state when needed rather than trying to figure how to undo an action. For example, it’s not easy to just erase a line on a complex picture, but it’s easy to restore a previously saved version. (Although it takes more memory.)

      See this example: Provide undo and redo in C#

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.