# VBForums CodeBank > CodeBank - C# >  Shape Editor, including many shapes, Resize and more

## NickThissen

*-- Updated, see below! --*


Hi there,

This is codebank submission loosely based on jmcilhinney's Manipulate GDI+ Drawings. It mimics a simple "Shape Editor" much like the Windows Forms Editor in Visual Studio.

So, first of all, credits to jmcilhinney for his excellent base sample.



Here's a screenshot:
-- Updated, see below! --

As you can see, this shape editor includes:
Different shapes, easily extensible with your own shapes,Resizing using drag handles,Each Shape has properties you can edit, even during run-time using a PropertyGrid.
-- Updated, see below! --

I think a very brief explanation is in order. 

The attached example solution contains two projects: the ShapeEditorLibrary, containing the basic shape editor classes, and the ShapeEditorExampleVs2008 project, containing the form you see in the screenshot, including the property grid + selection combobox logic. As you can see, this is separated from the ShapeEditorLibrary so you can implement it completely differently if you want.

The most important classes in the ShapeEditorLibrary are the *Shape*, *Canvas* and *ShapeCollection* classes.

The *Shape* class is an abstract class and implements most of the basic methods and properties that every Shape will have. In order to create your own Shapes, you can create a class that inherits Shape, and override the Draw method to provide your own drawing. If you want a Triangle shape, you implement the drawing such that it looks like a triangle. There is a RectangleShape and EllipseShape already included in the example so you can take a look how you do that. You usually won't have to do anything else except provide your own drawing. All the moving and resizing and even the drawing of the drag handles is implement automatically.

The *Canvas* class is the panel the Shapes live in. It is the control you put on your Form. It has the properties you'll most likely need: a collection of Shapes and a SelectedShape property. In addition, it has useful events such as the SelectedShapeChanged and ShapesCollectionChanged events.

The *ShapeCollection* class represents a collection of Shapes. The Canvas class has an instance of this class to keep track of the Shapes. It behaves exactly like any other strongly typed collection (such as a List(Of Shape)), except that it raises the CollectionChanged event when an item in the collection is changed, added or removed.
Also, the ShapeCollection class has a method that uses a rather complicated (to explain anyway) algorithm to determine the name that a new Shape should have. For example, if you have four RectangleShapes on your form called RectangleShape1, RectangleShape3, Xyz and Hello, then when you add a new RectangleShape (with a blank name), it will be named RectangleShape2, as that is the next available name.

The names are used, in my example, in the 'selection combobox' and the property grid, just like in Visual Studio.


Well, that's it for the explanation for now, on to the facts...

There are a few things that aren't completed yet, but I'm planning to add them as soon as I find some time:
There is no 'live drawing' to add shapes yet. Shapes are added via code at the moment.There is no docking or anchoring or anything like that.


Finally, here's a very short 'tutorial' on how to create your own shapes. Let's suppose you want to create a triangular shape.

1. Create a new class called TriangleShape in the Shapes folder.

2. Edit the class declaration so that it inherits Shape.

csharp Code:
public class TriangleShape : Shape

3. Override the Draw method and provide your own drawing. In our case, we want to draw a triangle. We need to draw within the Bounds rectangle, because that defines the location and size of our shape.

csharp Code:
public override void Draw(System.Drawing.Graphics g)
        {
            float middle = this.Bounds.X + this.Bounds.Width / 2f;
             PointF left = new PointF(this.Bounds.Left, this.Bounds.Bottom);
            PointF right = new PointF(this.Bounds.Right, this.Bounds.Bottom);
            PointF top = new PointF(middle, this.Bounds.Top);
             using (var b = new SolidBrush(this.BackColor))
            {
                g.FillPolygon(b, new PointF[] { left, right, top });
            }
        }

4. Finally, we need to add a non-default constructor because each Shape should accept a Point (its location) in the constructor. All you need to do is call the base constructor and pass the point on to let it handle it:

csharp Code:
public TriangleShape(Point location)
            : base(location)
        {
        }

Final code:

csharp Code:
using System.Drawing;
 namespace ShapeEditorLibrary.Shapes
{
    public class TriangleShape : Shape
    {
        public TriangleShape(Point location)
            : base(location)
        {
        }
         public override void Draw(System.Drawing.Graphics g)
        {
            float middle = this.Bounds.X + this.Bounds.Width / 2f;
             PointF left = new PointF(this.Bounds.Left, this.Bounds.Bottom);
            PointF right = new PointF(this.Bounds.Right, this.Bounds.Bottom);
            PointF top = new PointF(middle, this.Bounds.Top);
             using (var b = new SolidBrush(this.BackColor))
            {
                g.FillPolygon(b, new PointF[] { left, right, top });
            }
        }
    }
}


5. Finally, we need some way to add the Shape so we can test it. If you are still using my example project, you can very easily edit, for example, the 'addRectangle' toolstrip button. Instead of creating a new RectangleShape, just create a new TriangleShape:

csharp Code:
private void addRectangle_Click(object sender, EventArgs e)
        {
            Shape s = new TriangleShape(new Point(rnd.Next(0, canvas1.Width), rnd.Next(0, canvas1.Height)));
            s.ContextMenuStrip = shapeContextMenu;
            canvas1.Shapes.Add(s);
        }
Nevermind the fact that the names don't match up, this is just for testing...

So test it! This is what you should get:

A fully functional triangular shape.

That wasn't very hard was it?


Enjoy!


-- Updated, see below! --

----------


## ForumAccount

Very cool. 

Two quick things I saw were when you try to click a shape it doesn't account for Z-Order. For example if I have a huge triangle behind a smaller square, I can't select the square. Also, when you change the location's/size's of shapes in the designer it should tell the property editor to refresh their values.

----------


## NickThissen

Yes, the Z-order selection was something I was going to implement but decided to leave it for now because it wasn't working out the way I wanted it. I forgot to mention it in my post.
I don't think the Visual Studio designer even does that, right? I think the only way you can select a control behind another control in VS is to rightclick and select it from the context menu. I was going to do it differently, where each successive click selects the next control in the z-order. So if you have 4 controls stacked on top of each other (at least partly) and you click in the part where they all overlap, each time you click you will select a different control (one next up in the z-order). I'm not sure how to implement that though.

For updating the property grid, do you mean when you move/resize a shape with the mouse? I suppose I could do that, but I'd do that on the form level, by letting the form handle the Move / Resize events of the shapes. Since the shapes don't have those events yet, I didn't implement it yet  :Stick Out Tongue:

----------


## akhileshbc

That's cool.  :Thumb:  But I like to see the VB version of it.  :wave:

----------


## NickThissen

I am actually working on the library some more (although I don't know if I will bring out another example project like this), mainly by adding snap lines and things like that. I doubt I will translate it to VB completely as that would be quite a lot of work after all with no gain for me at all.

Can't you try converting it yourself? Using this converter you can probably get a very long way, and anything you can't get you can always ask here, I'll be glad to help.

----------


## akhileshbc

Ok... :Thumb:

----------


## NickThissen

Finally I got around to creating another test project for my updated library. 

Updates (at least those I can remember):
Multiple selectionsSnapping and aligning shapes to shapesSnapping shapes to the edge of the canvasZ-order selection (clicking multiple times on the same spot will select each underlying shape in turn)Updated grab handles to look more like Visual Studio IDE (rounded and smaller)Locked shapes can no longer move and also display locked grab handles

Multiple selections work just like in Visual Studio: one shape is the 'main selection' (white grab handles), all other selected shapes are secondary selections (black grab handles). Clicking a secondary selection will make it the main selection. Snapping happens only on the main selected shape, not on any secondary shapes.

I think that's about it. 

Unfortunately, I no longer have VS2008 so I did not create a VS2008 compatible version. The files should all work in VS2008 though, so you can add them to a new project manually if you cannot use VS2010.


New screenshots:

Aligning an ellipse to a rectangle and triangle:


Snapping a shape towards another shape (so they keep a fixed distance):


Multiple selections:



Known bugs:
- When snapping two shapes the snapline might not appear in the middle correctly. It just looks a bit strange but it still works.



A solution including two projects (the ShapeEditorLibrary and a test project) is attached (build or run before viewing the form).

Enjoy!

----------


## dtanh

hello @NickThissen!

can you help me about rotate rectangle in your code please!

----------


## magefesa

How to add text on a shape ???? 

I've added this property to base class shape _




> private string _Texto = String.Empty;
>         public string Texto
>         {
>             get { return _Texto; }
>             set
>             {
>                 if (value.Trim() == String.Empty)
>                     throw new ArgumentException("Texto cannot be empty.");
>                 _Texto = value;
> ...



But i need to extend to (for example Rectangle )...how ??

Thanks for your great work  !!

----------


## NickThissen

> hello @NickThissen!
> 
> can you help me about rotate rectangle in your code please!


Rotation of shapes is not supported. You could however create a new shape that has a Rotation property, and draw your rotated rectangle accordingly inside the current bounds. That would require a little trigonometry to figure out the locations of the four corners.




> How to add text on a shape ???? 
> 
> I've added this property to base class shape _
> 
> 
> 
> 
> But i need to extend to (for example Rectangle )...how ??
> 
> Thanks for your great work  !!


You could create your own class that inherits RectangleShape (or just Shape), add the Text property in that class (instead of the base class), and then override the Draw method and use Graphics.DrawString to draw the text.

----------


## magefesa

I've achieved !!!
HEre is the code !

On RectangleShapes.Cs :




> public override void Draw(System.Drawing.Graphics g)
>         {
>             using (var b = new SolidBrush(this.BackColor))
>             {
>                 g.FillRectangle(b, this.Bounds);
>                 g.DrawRectangle(Pens.Black, this.Bounds);
> 
>                AfegirText(g,Texto );
>             }
> ...


On Shape.cs (add this property)




> private string _Texto = String.Empty;
>         public string Texto
>         {
>             get { return _Texto; }
>             set
>             {
>                 if (value.Trim() == String.Empty)
>                     throw new ArgumentException("Texto cannot be empty.");
>                 _Texto = value;
> ...



How to call it ?? Example, on the sample project :




> private void AddShape(Shape s)
>         {
>             s.Texto = " sdf sd fsd fsdf sd fsfsdf";
>             canvas1.Shapes.Add(s);
>             canvas1.Invalidate();
>         }

----------


## dtanh

> Rotation of shapes is not supported. You could however create a new shape that has a Rotation property, and draw your rotated rectangle accordingly inside the current bounds. That would require a little trigonometry to figure out the locations of the four corners.


Thanks for your reply! i have been tried to rotate my rectangle, but not good. My rectangle is the current bound.

----------


## CreativeDreamer

Nice.

Perhaps Add polygon could possibly be next?

Codeproject also has an example of *rotating, shifting and resizing objects* by attaching adorners over here:
w w w.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part

Also a skew class is mentioned here:
w w w.codeproject.com/Messages/3295560/Skew-Class-as-addition-to-this-nice-article.aspx

Please change w w w to www in the above links.

Keep up the good work.

----------


## coolcurrent4u

Thanks for such a wonderful work.

----------


## Louise1998

Hi all,
Before I start, I apologize for answering the old post.
The source code of NickThissen was excellent.I want to use part of these codes in other programing languages like vb6.
Can anyone help me to convert a small part of this source code?

I just want to convert these codes into one of the following.
1-winapi functions 
2-vb6 syntax

I hope that the professors of this forum can help me.
Thanks.



```
 var oldBounds = new List<Rectangle>();
                if (this.SelectedShape != null)
                {
                    if (hitStatus == Shape.HitStatus.Drag)
                    {
                        // 1. Store the relative locations of all other selected shapes, if any
                        var relativeLocations = new List<Point>();
                        for (var i = 1; i < this.SelectedShapes.Count; i++)
                        {
                            var shape = this.SelectedShapes[i];
                            relativeLocations.Add(this.SelectedShape.Location.Subtract(shape.Location));
                            oldBounds.Add(shape.GrabHandles.TotalBounds);
                        }

                        // 2. Move the main selected shape
                        oldBounds.Add(this.SelectedShape.GrabHandles.TotalBounds);
                        var newLocation = e.Location.Subtract(moveStart);
                        if (this.SnapMode == SnapModes.SnapToGrid) newLocation = newLocation.Floor(10);
                        this.SelectedShape.Move(newLocation);
                        this.InvalidateShape(this.SelectedShape);

                        // 3. Move the remaining selected shapes back to their relative positions
                        for (var i = 1; i < this.SelectedShapes.Count; i++)
                        {
                            var shape = this.SelectedShapes[i];
                            oldBounds.Add(shape.GrabHandles.TotalBounds);
                            shape.Location = this.SelectedShape.Location.Subtract(relativeLocations[i - 1]);
                           this.InvalidateShape(shape);
                        }
                    }
```

----------


## deeronb

This shape editor is good even after 11 years.  Unfortunately, rotation is not implemented, it is not easy (coordinate transformation) to rotate at the center of the shape so that the grip points and the cursor arrows are at the same angle.

 Hopefully after all these years, there will be an entrepreneurial C# guru who will do the rotation of the shapes, I think a few would be happy for him.   :Smilie:

----------

