The attached project demonstrates how the user can manipulate GDI+ drawings as though they were actual objects. It was created in VS 2008 but all the code should be compatible with VS 2005.
The application is supposed to behave, in a very rudimentary way, like the VS WinForms designer. You can click and drag to draw boxes on the form. The boxes will be drawn with an apparent z-order, where newer boxes appear to be in front of older ones. You can right-click on a box and select "Bring to Front" or "Send to Back" to change that z-order. You can also click the button on the tool bar to put the application into drag mode, which allows you to click and drag a box to move it around the form.
For an explanation of the genesis of this code, visit my blog.
Last edited by jmcilhinney; Oct 23rd, 2009 at 07:56 AM.
Reason: Provided some explanation of how to use the app; Added blog link
jm, i have been trying to adapt your code to allow different colored boxes to be drawn without any luck.
I have replicated every variable and structure like so
Code:
Private startPoint As Point
Private startPoint2 As Point
'etc
Private Sub InvalidateRectangle(ByVal box As Rectangle)
'When invalidating a Rectangle the right-most
'column and bottom-most row of pixels is excluded.
'Inflate the box by 1 pixel in every direction to
'ensure those excluded pixels are alos repainted.
box.Inflate(1, 1)
Me.PictureBox1.Invalidate(box)
End Sub
Private Sub InvalidateRectangle2(ByVal box2 As Rectangle)
'When invalidating a Rectangle the right-most
'column and bottom-most row of pixels is excluded.
'Inflate the box by 1 pixel in every direction to
'ensure those excluded pixels are alos repainted.
box2.Inflate(1, 1)
Me.PictureBox1.Invalidate(box2)
End Sub
'etc
I have also used a if else statement in the PictureBox1_Paint sub for the different colored boxes
Code:
if label1.text = "A" then
Dim MyBrush As New SolidBrush(Color.FromArgb(150, 120, 120, 120))
.FillRectangle(MyBrush, box2)
.DrawRectangle(Pens.Black, box2)
else
Dim MyBrush As New SolidBrush(Color.FromArgb(100, 200, 255, 220))
.FillRectangle(MyBrush, box)
.DrawRectangle(Pens.Black, box)
I am able to get different colored boxs but when i draw the second colored box all the previous box's change color to the new color during the beginning of the mouse move sub.
Code:
Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
If Concrete.Visible = True Then
If Console.lblComponentType.Text = "Beam" Then
Dim width As Integer = 50
Dim height As Integer = 50
Dim source As New Bitmap(width, height)
Try
Using canvas As Graphics = Graphics.FromImage(source)
canvas.DrawImage(Me.PictureBox1.Image, source.GetBounds(GraphicsUnit.Pixel), New RectangleF(e.X - width \ 2, e.Y - height \ 2, source.Width, source.Height), GraphicsUnit.Pixel)
With Windows.Forms.Cursor.Current
Dim hotSpot As Point = .HotSpot
Dim cursorSize As Size = .Size
.Draw(canvas, New Rectangle(width \ 2 - hotSpot.X, height \ 2 - hotSpot.Y, cursorSize.Width, cursorSize.Height))
End With
End Using
Catch ex As Exception
End Try
'Only do work if the left mouse button is the one and only mouse button that is depressed.
If Control.MouseButtons = Windows.Forms.MouseButtons.Left Then
If Me.drawMode2 Then
'The old box area must be repainted in case the box has shrunk.
Me.InvalidateRectangle2(Me.GetRectangle2(Me.startPoint2, Me.endPoint2))
'A draw operation ends at the current mouse location.
Me.endPoint = e.Location
'The new box area must be repainted in case the box has grown.
Me.InvalidateRectangle2(Me.GetRectangle2(Me.startPoint2, Me.endPoint2))
Me.PictureBox1.Update()
ElseIf Me.selectedBoxIndex2 <> -1 Then
'Get the box that's being dragged.
Dim box2 As Rectangle = Me.boxes2(Me.selectedBoxIndex2)
Dim location2 As Point = e.Location
'The old box area must be repainted.
Me.InvalidateRectangle2(box2)
'Move the box the same distance as the mouse pointer has moved.
box2.Offset(location2.X - Me.startPoint2.X, location2.Y - Me.startPoint2.Y)
'The box we edited was a copy so replace the original.
Me.boxes2(Me.selectedBoxIndex2) = box2
'Reset the current location as the start point for the next drag.
Me.startPoint2 = location2
'The new box area must be repainted.
Me.InvalidateRectangle2(box2)
Me.PictureBox1.Update()
End If
RaiseEvent PreviewNeeded(Me, New ImageEventArgs(source))
End If
Else
Dim width As Integer = 50
Dim height As Integer = 50
Dim source As New Bitmap(width, height)
Try
Using canvas As Graphics = Graphics.FromImage(source)
canvas.DrawImage(Me.PictureBox1.Image, source.GetBounds(GraphicsUnit.Pixel), New RectangleF(e.X - width \ 2, e.Y - height \ 2, source.Width, source.Height), GraphicsUnit.Pixel)
With Windows.Forms.Cursor.Current
Dim hotSpot As Point = .HotSpot
Dim cursorSize As Size = .Size
.Draw(canvas, New Rectangle(width \ 2 - hotSpot.X, height \ 2 - hotSpot.Y, cursorSize.Width, cursorSize.Height))
End With
End Using
Catch ex As Exception
End Try
'Only do work if the left mouse button is the one and only mouse button that is depressed.
If Control.MouseButtons = Windows.Forms.MouseButtons.Left Then
If Me.drawMode Then
'The old box area must be repainted in case the box has shrunk.
Me.InvalidateRectangle(Me.GetRectangle(Me.startPoint, Me.endPoint))
'A draw operation ends at the current mouse location.
Me.endPoint = e.Location
'The new box area must be repainted in case the box has grown.
Me.InvalidateRectangle(Me.GetRectangle(Me.startPoint, Me.endPoint))
Me.PictureBox1.Update()
ElseIf Me.selectedBoxIndex <> -1 Then
'Get the box that's being dragged.
Dim box As Rectangle = Me.boxes(Me.selectedBoxIndex)
Dim location As Point = e.Location
'The old box area must be repainted.
Me.InvalidateRectangle(box)
'Move the box the same distance as the mouse pointer has moved.
box.Offset(location.X - Me.startPoint.X, location.Y - Me.startPoint.Y)
'The box we edited was a copy so replace the original.
Me.boxes(Me.selectedBoxIndex) = box
'Reset the current location as the start point for the next drag.
Me.startPoint = location
'The new box area must be repainted.
Me.InvalidateRectangle(box)
Me.PictureBox1.Update()
End If
RaiseEvent PreviewNeeded(Me, New ImageEventArgs(source))
End If
End If
End If
End Sub
Should i be cloning all the code or is there a better way to do this?
jm, i have been trying to adapt your code to allow different colored boxes to be drawn without any luck.
You need to remember that every time a Paint event is raised you are drawing every box. If you are selecting the colour at that point and you're only using a single colour then every box will be that colour each time you draw. If you expect every box to remember the colour that was selected when it was drawn then that's exactly what you need to do: remember the colour that was selected when a box was first drawn by storing it with the box information. Then, when you loop through the boxes to be drawn in the Paint event, you'll read the colour for each box individually as well as its location and size. If you need to define your own type to store the information then so be it. Maybe a class or structure with a Rectangle property and a Color property.
I hope you don't mind, but I'm trying to extend this sample a bit. The most important stuff I plan to add is:
Multiple shapes
Selecting multiple shapes
Resizing shapes
Selection rectangles
So far I got two shapes (but adding shapes is easy; all I need is to add a class that derives from the base shape and implement its own drawing method) and the selection rectangle. The selection rectangle looks similar to that in VS, but it's not functional yet (no resizing).
I hope you don't mind, but I'm trying to extend this sample a bit.
I don't mind at all. This was specifically supposed to be a base example from which useful extensions could be created. Sounds like that's just what you're doing.
I hope you don't mind, but I'm trying to extend this sample a bit. The most important stuff I plan to add is:
Multiple shapes
Selecting multiple shapes
Resizing shapes
Selection rectangles
So far I got two shapes (but adding shapes is easy; all I need is to add a class that derives from the base shape and implement its own drawing method) and the selection rectangle. The selection rectangle looks similar to that in VS, but it's not functional yet (no resizing).
Even cooler! Do you plan on sharing this with us eventually?
Even cooler! Do you plan on sharing this with us eventually?
Of course, that's the purpose
I do have some plans for applications like this, but they all involve some much more complicated stuff and I've given up on them for now and decided to try just the easier stuff like this.
What I really like to add is a zoom function, but that would radically change even the original example because it has to use vector based drawing instead of the usual 'draw the rectangle at it's current location', so I doubt I can pull that off...
Just letting you know I'm still working on this, but it's taking its time. I am trying to make it look like a very rudimentary version of Visual Studio, with a 'toolbox' on the left, a list of the shapes on the right and a property grid.
There's still some problems to be solved but I'll get there in the end.
The 'Draw Mode' is going to have to go in the end. Shapes should be drawn by dragging them from the toolbox, as in Visual Studio, or Visio, or most other 'shape drawing' software.
Hello there... I've finally got around to do some more work on this shape editor. I've decided to start from scratch as I had some concepts wrong which made it hard to fix some stuff.
Find it here.
Unfortunately, at the moment, it is only available in C#.NET. You can still use the shapes and canvas in a VB project, but you'll have to translate the example project to VB.NET (only a few lines of code though).
The example is not as good as the screenshot above (yet) because it wasn't a priority. It does have a ComboBox to select the available shapes and a PropertyGrid to set the properties of the selected shape, just like in Visual Studio.