I'm new to VB and I need help on drawing a rectangle on a picture box in runtime.
Code:
Public Sub plotPoles(ByVal x As PointF)
Dim canvas As New ShapeContainer
Dim pole As New RectangleShape
canvas.Parent = picInput
pole.Parent = canvas
pole.Size = New System.Drawing.Size(5, 5)
pole.BorderColor = Color.Red
pole.FillStyle = FillStyle.DiagonalCross
pole.Location = New System.Drawing.Point(x.X, y.Y)
End Sub
It seems that the shape control location function only accepts Point data type, however the coordinates on my picture box is of PointF data type. How do I draw by using PointF location?
I would suggest not using the RectangleShape and use GDI+ instead. Here is an example:
Code:
Public Sub plotPoles(ByVal x As PointF)
'Create a new instance of a bitmap, this is what we'll draw on
Dim b As Bitmap = New Bitmap(picInput.Width, picInput.Height)
'Create a new instance of a graphics object, this is how we'll draw
Using g As Graphics = Graphics.FromImage(b)
'Create a new instance of a red pen(by default the width is 1)
Using rPen As Pen = New Pen(Color.Red)
'Draw a rectangle with the PointF parameter and with a size of (5, 5)
g.DrawRectangle(rPen, x.X, x.Y, 5.0, 5.0)
End Using 'Dispose of pen
'Save the graphics
g.Save()
End Using 'Dispose of graphics
'Set the image of the picturebox to the bitmap
picInput.Image = b
End Sub
Edit - I'd also suggest renaming your X parameter to something a bit more meaningful, especially since X is a property of PointF. My suggestion would be to rename it to something like rectangleLocation or rectanglePoint.
Last edited by dday9; Sep 10th, 2014 at 01:02 PM.
Reason: Added comments
I would suggest not using the RectangleShape and use GDI+ instead. Here is an example:
Code:
Public Sub plotPoles(ByVal x As PointF)
'Create a new instance of a bitmap, this is what we'll draw on
Dim b As Bitmap = New Bitmap(picInput.Width, picInput.Height)
'Create a new instance of a graphics object, this is how we'll draw
Using g As Graphics = Graphics.FromImage(b)
'Create a new instance of a red pen(by default the width is 1)
Using rPen As Pen = New Pen(Color.Red)
'Draw a rectangle with the PointF parameter and with a size of (5, 5)
g.DrawRectangle(rPen, x.X, x.Y, 5.0, 5.0)
End Using 'Dispose of pen
'Save the graphics
g.Save()
End Using 'Dispose of graphics
'Set the image of the picturebox to the bitmap
picInput.Image = b
End Sub
Edit - I'd also suggest renaming your X parameter to something a bit more meaningful, especially since X is a property of PointF. My suggestion would be to rename it to something like rectangleLocation or rectanglePoint.
Hi dday9,
Thanks for your reply.
The reason I use shape control instead of the GDI+ is because I want to be able to drag and move the object after placing it (and the object follows the cursor when dragging). From what I understand, this will be difficult to be implemented with GDI+ right? I dont know how to do it with GDI+, that's why I went to the direction of shape control.
Maybe if you know can you give me a direction? Your help is much appreciated.
Yeah, I suppose that would be a bit more difficult because GDI+ shapes are not objects in windows form applications. Not impossible, just harder. If that's the case though, the location of the RectangleShape will always be a Point and not a PointF.
Just change that line.
pole.Location = New System.Drawing.Point(x.X, y.Y)
If you have Option Strict on, if you hover the mouse over the error in the code and then click on the error dropdown that appears it will usually suggest how to fix the problem, e.g.
"Replace 'pf.x' with 'CInt(pf.x)'
and that is usually a hot link that will apply the fix for you if you click on it.
Just change that line.
pole.Location = New System.Drawing.Point(x.X, y.Y)
If you have Option Strict on, if you click on the dropdown on the error it will usually suggest how to fix the problem, e.g.
"Replace 'pf.x' with 'CInt(pf.x)'
and that is usually a hot link that will apply the fix for you if you click on it.
That's assuming that the OP doesn't need the higher precision that the PointF offers, but considering that the OP has the parameters as a PointF, I'm assuming that the OP needs that higher precision. Otherwise just pass a point as the parameter because that's essentially what you're doing whenever you convert the PointF to a Point.
If he is using a shape for the rectangle, it doesn't matter how precise the PointF is, the shape can only be drawn at Integral intervals.
The pointF comes into play when you're using matrix transforms, and I haven't used shape controls, but I would assume they are not subject to GDI+ transforms.
They aren't, that's why I suggested using GDI+ because otherwise you can't get the precision with a RectangleShape. That was point in post #4 and I was just reiterating it in post #6.
The reason I use shape control instead of the GDI+ is because I want to be able to drag and move the object after placing it (and the object follows the cursor when dragging). From what I understand, this will be difficult to be implemented with GDI+ right? I dont know how to do it with GDI+, that's why I went to the direction of shape control.
Maybe if you know can you give me a direction? Your help is much appreciated.
Thank you
How many rectangles do you want to drag and draw. It isn't really that hard to do in GDI+. If you keep an array of rectangles that you want to use, you can check if the mouse location when you click is in one of those rectangles, and as you move the mouse, you just update the rectangle's location relative to the mouse location and call Invalidate to redraw (cause the paint event).
I'll attach a project I wrote for someone as an example, but I don't see it already on this forum. It uses a rectangle to specify a portion of an image to be selected on one form and magnified on another form.
You can drag the rectangle around by clicking anywhere with the left mouse button and dragging.
You can resize the rectangle by clicking with the right mouse button in the rectangle and dragging.
For a more involved example, where I drag multiple rectangles that are drawn with card images in them, then you can check out this post, but if you're new to VB, you may not want to wade into that pool just yet. Besides, it is code that was ported from VB3 (written around 1995), so isn't object oriented in the least, but is not a bad example of one way of drawing and dragging a bunch of images around without dragging controls around that hold the images.
That's assuming that the OP doesn't need the higher precision that the PointF offers, but considering that the OP has the parameters as a PointF, I'm assuming that the OP needs that higher precision. Otherwise just pass a point as the parameter because that's essentially what you're doing whenever you convert the PointF to a Point.
Yes, I want the shape to be drawn at the precise location. I didn't know that the shape control is restricted to only Point parameter, that's why I posted this question hoping that there will be a solution, since shape controls will be easier to work with?(I suppose so)
Originally Posted by passel
How many rectangles do you want to drag and draw. It isn't really that hard to do in GDI+. If you keep an array of rectangles that you want to use, you can check if the mouse location when you click is in one of those rectangles, and as you move the mouse, you just update the rectangle's location relative to the mouse location and call Invalidate to redraw (cause the paint event).
I'll attach a project I wrote for someone as an example, but I don't see it already on this forum. It uses a rectangle to specify a portion of an image to be selected on one form and magnified on another form.
You can drag the rectangle around by clicking anywhere with the left mouse button and dragging.
You can resize the rectangle by clicking with the right mouse button in the rectangle and dragging.
For a more involved example, where I drag multiple rectangles that are drawn with card images in them, then you can check out this post, but if you're new to VB, you may not want to wade into that pool just yet. Besides, it is code that was ported from VB3 (written around 1995), so isn't object oriented in the least, but is not a bad example of one way of drawing and dragging a bunch of images around without dragging controls around that hold the images.
It will be restricted to about 6 rectangles. I've already did like what you said, keeping all the locations of rectangle in arraylist. Right now I have this code in my picturebox paint event to load up the X and Y axis when the program starts:
Code:
Private Sub picInput_Paint(sender As Object, e As PaintEventArgs) Handles picInput.Paint
Dim x, y As Single
With e.Graphics
.SmoothingMode = SmoothingMode.AntiAlias
.ScaleTransform(scaleRatio, scaleRatio)
.TranslateTransform(xTranslate, yTranslate)
.DrawString("Im", New Font("Ariel", 0.3), Brushes.Black, 0.3, -4)
.DrawString("Re", New Font("Ariel", 0.3), Brushes.Black, 5.5, -0.75)
' Draw axes.
Using p As New Pen(Color.Gray, 0)
.DrawLine(p, -6, 0, 6, 0)
For x = -5 To 5 Step 1
.DrawLine(p, x, -0.1F, x, 0.1F)
Next x
.DrawLine(p, 0, -4, 0, 4)
For y = -3 To 3 Step 1
.DrawLine(p, -0.1F, y, 0.1F, y)
Next y
'Labels ticks
For x = -5 To -1 Step 1
.DrawString(x.ToString, New Font("Ariel", 0.2), Brushes.Black, x - 0.15, 0.3)
Next x
For x = 1 To 5 Step 1
.DrawString(x.ToString, New Font("Ariel", 0.2), Brushes.Black, x - 0.1, 0.3)
Next x
For y = -3 To -1 Step 1
.DrawString(y.ToString, New Font("Ariel", 0.2), Brushes.Black, -0.4, -y - 0.13)
Next y
For y = 1 To 3 Step 1
.DrawString(y.ToString, New Font("Ariel", 0.2), Brushes.Black, -0.4, -y - 0.13)
Next y
End Using
End With
End Sub
So when the user starts plotting on the picturebox, all the locations will be saved in arraylist. If the user drags/moves a particular rectangle, I want the other rectangles to remain intact. Can I set a certain area (border) to Invalidate? Or should I Invalidate the picturebox and then redraw the other rectangles (using the locations stored in arraylist) on every MouseMove? I'm not sure whether that would cause the animation to appear laggish.
Anyway thanks for posting your example project, maybe the answer I'm looking for is in there, I haven't have time to look at it yet though. I will be busy in the next few days so I will come back again soon. Thanks for the help guys, you guys are really helpful for a newbie like me
Yes, I want the shape to be drawn at the precise location. I didn't know that the shape control is restricted to only Point parameter, that's why I posted this question hoping that there will be a solution, since shape controls will be easier to work with?(I suppose so)
Graphics.DrawRectangle and FillReectangle are a bit of an oddity because practically all the other graphics methods accept floating point arguments. There is a simple solution to this if you want to retain the accuracy. The Graphics.DrawRectangles method (with an s) does accept RectangleFs, so you could use it to draw a single RectangleF like this:
The curly braces turn the single RectangleF into a one-element array of RectangleFs, just to keep the syntax happy. Since you have six rectangles to draw you might find it convenient to use DrawRectangles for the collection anyway.
Originally Posted by zqkhor
So when the user starts plotting on the picturebox, all the locations will be saved in arraylist. If the user drags/moves a particular rectangle, I want the other rectangles to remain intact. Can I set a certain area (border) to Invalidate? Or should I Invalidate the picturebox and then redraw the other rectangles (using the locations stored in arraylist) on every MouseMove? I'm not sure whether that would cause the animation to appear laggish.
In MouseMove, it's best to use Invalidate(oldRectangle) to cause repainting of the position before the move, and then Invalidate(newRectangle) to cause repainting in the new position. This will be more efficient than repainting the whole control display each time. Sometimes the results are improved by using Rectangle.Inflate to enlarge the invalidated rectangles by a few pixels.
You could try adding Update after the Invalidate statements, which causes immediate repainting of the invalidated areas (rather like Refresh, which forces immediate repainting of the whole control) instead of putting them in a queue . But don't do this unless it really shows an improvement, because like Refresh, Update could itself cause lag when dragging an image. It forces repainting every time the MouseMove event fires, which can be more often than necessary to allow a smooth movement.
By the way, if you are concerned about performance you should avoid using the rather outdated ArrayList collection and use a generic collection like List(Of Rectangle). The generics are more efficient (because they don't require conversion from Object to a specific Type) and they have a much more useful range of methods.
BB
Last edited by boops boops; Sep 12th, 2014 at 03:10 PM.
All great advice from BB.
However, I had already added some code around your paint event to serve as a new example, and didn't implement any of those suggestions in the example as it stands.
I did in the "this post" link (last paragraph in post #9) of the card game, since there were a larger number of rectangles with images being drawn, invalidate an area around where the card was and where the card moved to, in the rapid animation portion of the code to restrict how much of the screen was redrawn.
But in this example since there are only a few rectangles, I didn't bother.
Also, as BB pointed out, you can't pass a RectangleF to DrawRectangle, but you can pass four singles defining X,Y,Width and Height, which is what I did.
You could pass the array to FillRectangles and DrawRectangles, but if you might want to have different color rectangles, then you will probably have to draw them in a loop so you can change brushes and pens.
I did change the paint code you provided since you are creating new Fonts many, many times, in your loops, and not disposing of any of them. Since you only use two fonts (at this point), my preference would be to create them once outside the paint event and reuse them, but in this example, just changed it to Create the font with Using, use it how ever many times necessary, and then dispose of it (End Using) within the paint event.
Just for the purpose of example, went ahead and added a button to define a 1x1 rectangle (rectangleF) at the origin of your graph, from a fixed array of seven rectangles.
You can drag the rectangle around with the left mouse button, and resize it by dragging on it with the right mouse button.
If you drag on the graph area (not on a rectangle), then the graph is moved around (left mouse), or resized (right mouse).
I used a function to convert mouse coordinates into graph coordinates, but you could also use a copy of the graphics matrix (or an inverse version of the matrix) used to do the drawing as another way to convert between the two scales, but that is usually more useful when you have an array of points to convert between the two coordinate systems.
I'm attaching the project, but also will post the modified code here in case someone wants a quick look at the code but not bother downloading the project.
Code:
Imports System.Drawing.Drawing2D
Public Class Form1
Private scaleRatio As Single = 1.0
Private xTranslate, yTranslate As Single
Private rects(6) As RectangleF
Private numOfRects As Integer
Dim fillBrush As New SolidBrush(Color.FromArgb(32, 0, 0, 0)) 'mostly transparent black
Dim activeRectangle As Integer
Private Sub picInput_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles picInput.MouseDown
Dim locationf As PointF = devToPicPoint(e.Location)
activeRectangle = -1
For i = rects.Count - 1 To 0 Step -1 'we draw in forward order (bottom to top), so check hit in reverse order (top to bottom)
If rects(i).Contains(locationf) Then
activeRectangle = i 'Found the topmost rectangle, so
Exit For 'exit the search
End If
Next
End Sub
Private Function devToPicPoint(pin As Point) As PointF
'Convert a point to a pointf, for instance, converting from Picturebox mouse coordinates, to Graph's scale coordinates
Dim x As Single = pin.X 'change incoming integer values to single
Dim y As Single = pin.Y
x /= scaleRatio 'adjust for the current scaling
y /= scaleRatio
x -= xTranslate 'offset by the current translation
y -= yTranslate
Return New PointF(x, y) 'return a PointF
End Function
Private Sub picInput_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles picInput.MouseMove
Static lastLocationF As PointF 'track the last point in graph coordinates (for moving and drawing)
Static lastELoc As Point 'track the last point in picturebox coordinates (for changing scale)
Dim locationf As PointF = devToPicPoint(e.Location) 'translate the mouse position into graph coordinates
If e.Button = Windows.Forms.MouseButtons.Left Then 'If dragging with left mouse button
If activeRectangle >= 0 Then 'If we clicked on a rectangle
rects(activeRectangle).X += (locationf.X - lastLocationF.X) ' move the rectangle
rects(activeRectangle).Y += (locationf.Y - lastLocationF.Y)
picInput.Invalidate()
lastLocationF = locationf
Else 'click is on the graph, not a rectangle
xTranslate = xTranslate + (locationf.X - lastLocationF.X) 'move the graph
yTranslate = yTranslate + (locationf.Y - lastLocationF.Y)
picInput.Invalidate()
End If
ElseIf e.Button = Windows.Forms.MouseButtons.Right Then 'If dragging with Right mouse Button
If activeRectangle >= 0 Then ' If dragging on a rectangle
'don't allow rectangle size smaller than 0.1
rects(activeRectangle).Width = Math.Max(rects(activeRectangle).Width + (locationf.X - lastLocationF.X), 0.1!)
rects(activeRectangle).Height = Math.Max(rects(activeRectangle).Height + (locationf.Y - lastLocationF.Y), 0.1!)
picInput.Invalidate()
lastLocationF = locationf
Else 'click is on the graph, not a rectangle
'let's change the scaling of the graph when dragging left and right
If lastLocationF <> locationf Then 'if the mouse has moved in X
Dim currentRatio As Single = scaleRatio ' Save this so we can "Zoom" around the translated center
scaleRatio *= CSng((e.X / lastELoc.X)) 'Multiple by the ratio (mouse position / last mouse position)
xTranslate *= (currentRatio / scaleRatio) 'adjust the translated center to
yTranslate *= (currentRatio / scaleRatio) 'maintain it at the same display location when we scale
picInput.Invalidate()
lastELoc = e.Location
End If
End If
Else
lastLocationF = locationf 'for moving object in the scale
lastELoc = e.Location 'for changing scale (so matches mouse movement)
End If
End Sub
Private Sub picInput_Paint(sender As Object, e As PaintEventArgs) Handles picInput.Paint
Dim x, y As Single
With e.Graphics
.SmoothingMode = SmoothingMode.AntiAlias
.ScaleTransform(scaleRatio, scaleRatio)
.TranslateTransform(xTranslate, yTranslate)
Using fnt As New Font("Ariel", 0.3)
.DrawString("Im", fnt, Brushes.Black, 0.3, -4)
.DrawString("Re", fnt, Brushes.Black, 5.5, -0.75)
End Using
' Draw axes.
Using p As New Pen(Color.Gray, 0)
.DrawLine(p, -6, 0, 6, 0)
For x = -5 To 5 Step 1
.DrawLine(p, x, -0.1F, x, 0.1F)
Next x
.DrawLine(p, 0, -4, 0, 4)
For y = -3 To 3 Step 1
.DrawLine(p, -0.1F, y, 0.1F, y)
Next y
'Labels ticks
Using fnt As New Font("Ariel", 0.2)
For x = -5 To -1 Step 1
.DrawString(x.ToString, fnt, Brushes.Black, x - 0.15!, 0.3!)
Next x
For x = 1 To 5 Step 1
.DrawString(x.ToString, fnt, Brushes.Black, x - 0.1!, 0.3!)
Next x
For y = -3 To -1 Step 1
.DrawString(y.ToString, fnt, Brushes.Black, -0.4!, -y - 0.13!)
Next y
For y = 1 To 3 Step 1
.DrawString(y.ToString, fnt, Brushes.Black, -0.4!, -y - 0.13!)
Next y
End Using
For i As Integer = 0 To numOfRects - 1
.FillRectangle(fillBrush, rects(i))
With rects(i)
e.Graphics.DrawRectangle(p, .X, .Y, .Width, .Height)
End With
Next
End Using
End With
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
scaleRatio = 32.0!
xTranslate = (picInput.ClientSize.Width / 2.0!) / scaleRatio
yTranslate = (picInput.ClientSize.Height / 2.0!) / scaleRatio
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If numOfRects < rects.Count Then
numOfRects += 1
rects(numOfRects - 1) = New RectangleF(0.0!, 0.0!, 1.0!, 1.0!)
picInput.Invalidate()
End If
End Sub
End Class
p.s. Looking back at the code, "New RectangleF(0.0!, 0.0!, 1.0!, 1.0!)", looks like I'm really excited about my numbers. I have Option Strict on so have to designate the literals as Singles, as opposed to the default Doubles. But using "!" is actually old school BASIC, and you can look less excited by doing a global search and replace to replace the "!" with "F", so the snippet should look like "New RectangleF(0.0F, 0.0F, 1.0F, 1.0F)", which would be more recognizable to others familiar with other .Net languages.
Last edited by passel; Sep 12th, 2014 at 08:23 PM.
Hey sorry for the late reply, I have been busy recently. Thank you for spending time and putting in effort to help me again, I really appreciate that!
Actually my project is suppose to plot diagonal crosses ("X") and circles ("O") on the graph (not rectangles, I tried to use rectangle cos there's this diagonal cross shape style in the shape controls,now I've switched back to graphics). I used your example as a reference and did some changes to my code, but the drag and move feature is not working completely (it is able to redraw at new position at MouseUp event, but it does not follow my mouse during MouseMove event).
Are you able to tell what is wrong with my code?
Code:
Imports System.Drawing.Drawing2D
Public Class Form1
Public picCursor, oldPos As PointF
Public scaleRatio As Integer = 35
Public xTranslate As Integer = 7, yTranslate As Integer = 5
Public selectedIndex As Integer
Public complex As Boolean = False, poleSelected As Boolean = False, zeroSelected As Boolean = False, above As Boolean = False, below As Boolean = False
Public polesArrayX, polesArrayY, zerosArrayX, zerosArrayY, polesStatus, zerosStatus As New ArrayList
Private Sub picInput_Paint(sender As Object, e As PaintEventArgs) Handles picInput.Paint
Dim x, y As Single
Dim i As Integer = 0, j As Integer = 0
With e.Graphics
.SmoothingMode = SmoothingMode.AntiAlias
.ScaleTransform(scaleRatio, scaleRatio)
.TranslateTransform(xTranslate, yTranslate)
' Draw axes.
Using p As New Pen(Color.Gray, 0)
.DrawLine(p, -6, 0, 6, 0)
For x = -5 To 5 Step 1
.DrawLine(p, x, -0.1F, x, 0.1F)
Next x
.DrawLine(p, 0, -4, 0, 4)
For y = -3 To 3 Step 1
.DrawLine(p, -0.1F, y, 0.1F, y)
Next y
End Using
'Labels ticks
Using fnt As New Font("Ariel", 0.2)
For x = -5 To -1 Step 1
.DrawString(x.ToString, fnt, Brushes.Black, x - 0.15!, 0.3!)
Next x
For x = 1 To 5 Step 1
.DrawString(x.ToString, fnt, Brushes.Black, x - 0.1!, 0.3!)
Next x
For y = -3 To -1 Step 1
.DrawString(y.ToString, fnt, Brushes.Black, -0.4!, -y - 0.13!)
Next y
For y = 1 To 3 Step 1
.DrawString(y.ToString, fnt, Brushes.Black, -0.4!, -y - 0.13!)
Next y
End Using
'Labels axes
Using fnt As New Font("Ariel", 0.3)
.DrawString("Im", fnt, Brushes.Black, 0.3, -4)
.DrawString("Re", fnt, Brushes.Black, 5.5, -0.75)
End Using
End With
updatePlots()
End Sub
Private Sub picInput_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picInput.MouseMove
'Converts Picturebox mouse position to Graph coordinates
picCursor.X = Math.Round(e.X / scaleRatio - xTranslate, 2, MidpointRounding.AwayFromZero)
picCursor.Y = -Math.Round(e.Y / scaleRatio - yTranslate, 2, MidpointRounding.AwayFromZero)
'Displays cursor coordinates
lblXValue.Text = picCursor.X.ToString()
lblYValue.Text = picCursor.Y.ToString()
If e.Button = Windows.Forms.MouseButtons.Left Then
Dim newPos As PointF = picCursor
If poleSelected = True Then 'Checks whether pole "X" or zero "O" is selected
polesArrayX.Item(selectedIndex) += (newPos.X - oldPos.X)
polesArrayY.Item(selectedIndex) += (newPos.Y - oldPos.Y)
picInput.Invalidate()
ElseIf zeroSelected = True Then
zerosArrayX.Item(selectedIndex) += (newPos.X - oldPos.X)
zerosArrayY.Item(selectedIndex) += (newPos.Y - oldPos.Y)
picInput.Invalidate()
End If
End If
End Sub
Private Sub picInput_MouseDown(sender As Object, e As MouseEventArgs) Handles picInput.MouseDown
oldPos = picCursor 'Captures the original mouse position
For i = polesArrayX.Count To 1 Step -1 'Performs poles hit test
If listPoles.Items.Contains(i & ". (" & picCursor.X & " , " & picCursor.Y & ")") Then
selectedIndex = i - 1
poleSelected = True
listPoles.SetSelected(selectedIndex, True) 'Highlights the selected poles on listbox
End If
Next
End Sub
Private Sub picInput_MouseUp(sender As Object, e As MouseEventArgs) Handles picInput.MouseUp
If poleSelected = True Then
polesArrayX.Item(selectedIndex) = picCursor.X
polesArrayY.Item(selectedIndex) = picCursor.Y
poleSelected = False
End If
picInput.Refresh()
updatePlots()
End Sub
Public Sub updatePlots()
Dim i As Integer = 0, j As Integer = 0
For Each pole As Decimal In polesArrayX
DrawPoles(toPoint(pole, polesArrayY.Item(i)))
i += 1
Next
For Each zero As Decimal In zerosArrayX
DrawZeros(toPoint(zero, zerosArrayY.Item(j)))
j += 1
Next
End Sub
Public Sub DrawPoles(ByVal point As PointF)
Dim p As New System.Drawing.Pen(Color.Red, 1)
Dim g As System.Drawing.Graphics
g = picInput.CreateGraphics
g.DrawLine(p, point.X - 5, point.Y + 5, point.X + 5, point.Y - 5)
g.DrawLine(p, point.X + 5, point.Y + 5, point.X - 5, point.Y - 5)
g.Dispose()
End Sub
'Converts Graph coordinates to Picturebox position (for drawing purpose)
Public Function toPoint(ByVal value As PointF) As PointF
value.X = (value.X + xTranslate) * scaleRatio
If complex = True Then
value.Y = (value.Y + yTranslate) * scaleRatio
Else
value.Y = (-value.Y + yTranslate) * scaleRatio
End If
Return value
End Function
Well these codes are only part of the whole program which contributes to the drag and move feature (to make things simple). I have attached the complete codes just in case you need it to see clearer. I tried to use Invalidate instead of Refresh during MouseUp event but it didn't redraw the plot at new location (not sure why), so I use Refresh instead.plot.zipplot.zip
I can't try out the project in full at the moment, because I'll need to get to a machine with vb.net 2012 on it, since I don't want to take the time to try to manually back the 2012 project into a 2010 project, but this line jumps out.
picCursor.Y = -Math.Round(e.Y / scaleRatio - yTranslate, 2, MidpointRounding.AwayFromZero)
I don't think you want the - sign on the front of that expression.
I know that your graph is showing that Y axis is positive in the Up direction, but your actually drawing coordinates are still Y positive going down.
I thought about mentioning that before but didn't because I assumed since you were able to draw the tick marks and axis values correctly by negating the Y coordinate you understood what was going on.
To draw something at a Y value of -2 in graph coordinates, you draw it at a Y value of 2 in GDI+ world coordinates.
So, picCursor.Y should be in the GDI+ world coordinates.
But, then further on where you display the coordinates
lblYValue.Text = picCursor.Y.ToString()
that is a place you would want to display in graph coordinates, so you would negate the Y there (not tested)
lblYValue.Text = (-picCursor.Y).ToString()
You could change the ScaleTransform to use a negative Y scaling to flip the Y coordinates, but the problem there is if you draw text, and other objects, they will be drawn inverted as well.
It is usually easiest to keep the drawing in the default +,+ scaling, and just invert the Y values of your data when you plot and display coordinate values.
After looking at the whole code and running on another machine, the negation at that point may not be a problem.
But you're not doing the drawing the way it should be done.
All the drawing should be done in the paint event, using the graphics object passed (e.graphics). Pictureboxes are doubled buffered by default, and that graphics object will draw in the backing buffer, which will get flushed to the screen at the end of the paint event.
You're creating a graphics object in your draw routine, and using that object will not draw in the backbuffer, but will draw directly to the screen area of the picturebox. When you finish the paint event, the flushing of the backbuffer to the screen will wipeout what you just drew, which is probably why you don't see the zeros and Xs when dragging in the picturebox.
It's also a pain the way you're doing your hit testing. Having to select the exact pixel to select the symbol is not very friendly, but I'm assuming you want to fix the bigger issues fist, then look at providing a larger hit test target.
Unfortuanately, I'm really backed up this week at work, so need to be focusing on that pretty much full time whenever I'm on the computer, so can play around with the code to try to make any suggested changes.
After looking at the whole code and running on another machine, the negation at that point may not be a problem.
But you're not doing the drawing the way it should be done.
All the drawing should be done in the paint event, using the graphics object passed (e.graphics). Pictureboxes are doubled buffered by default, and that graphics object will draw in the backing buffer, which will get flushed to the screen at the end of the paint event.
You're creating a graphics object in your draw routine, and using that object will not draw in the backbuffer, but will draw directly to the screen area of the picturebox. When you finish the paint event, the flushing of the backbuffer to the screen will wipeout what you just drew, which is probably why you don't see the zeros and Xs when dragging in the picturebox.
It's also a pain the way you're doing your hit testing. Having to select the exact pixel to select the symbol is not very friendly, but I'm assuming you want to fix the bigger issues fist, then look at providing a larger hit test target.
Unfortuanately, I'm really backed up this week at work, so need to be focusing on that pretty much full time whenever I'm on the computer, so can play around with the code to try to make any suggested changes.
Thank you for your suggestion once again.
I do understand that all drawings not in the paint event will be wiped out when I invalidate/refresh the picturebox. That's why I added the updatePlots() function at the end of the paint event, to draw all the plots in the paint event. But unfortunately it isn't working now, I will try to research more on the backbuffering of picturebox in the next few days.
Yes you're right, I will work on the hit testing issue that you've mentioned after getting this drag and move feature done.
It is important you draw at the right time, i.e. in the paint event, but it is also important where you draw, i.e. what graphics object you use.
You added calls to the drawing in the paint event, but you are not using the e.Graphics graphics object in those drawing routines, so you are drawing to a different context (i.e. using a graphics object you created), and that drawing will be wiped out.
It would be common to pass the Paint Event's graphics object to the drawing routine so that it can be used to draw in the same context.
Code:
'...
updatePlots(e.Graphics) 'pass the Paint event's graphics object to the drawing routines
'...
Public Sub updatePlots(g as Graphics)
Dim i As Integer = 0, j As Integer = 0
For Each pole As Decimal In polesArrayX
DrawPoles(g, toPoint(pole, polesArrayY.Item(i))) 'pass the graphics context on to the specific drawing routines
i += 1
Next
For Each zero As Decimal In zerosArrayX
DrawZeros(g, toPoint(zero, zerosArrayY.Item(j))) 'pass the graphics context on to the specific drawing routines
j += 1
Next
End Sub
Public Sub DrawPoles(ByVal g as Graphics, ByVal point As PointF)
Dim p As New System.Drawing.Pen(Color.Red, 0) 'Use 0 for size so the pen is always the minimum size, i.e. 1 pixel, regardless of scale.
' Dim g As System.Drawing.Graphics
' g = picInput.CreateGraphics
g.DrawLine(p, point.X - 5, point.Y + 5, point.X + 5, point.Y - 5) 'need to adjust these 5's to fit the scale of your graph, 5/35 i.e. .142857.
g.DrawLine(p, point.X + 5, point.Y + 5, point.X - 5, point.Y - 5)
' g.Dispose() 'Don't dispose the Paint event's graphics object
End Sub
'Converts Graph coordinates to Picturebox position (for drawing purpose)
Public Function toPoint(ByVal value As PointF) As PointF
value.X = (value.X + xTranslate) ' * scaleRatio 'Don't need to multiply by scaleRatio since you are drawing in the graph's coordinate system
If complex = True Then
value.Y = (value.Y + yTranslate) ' * scaleRatio
Else
value.Y = (-value.Y + yTranslate) ' * scaleRatio
End If
Return value
End Function
I didn't see the circle drawing in your code posted above, but I assume similar adjustment need to be done there.
The other calls you may have elsewhere to the draw routines need to be commented out, and instead your arrays updated and the paint event triggered again (by calling invalidate or refresh) to draw the updates.
I only modified the code visible above that you posted in post 13, so is not tested. I'm on the machine without vb 2012 again, so there may be errors in my post, but should give you the general idea of what needs to be done, your existing code.
It is important you draw at the right time, i.e. in the paint event, but it is also important where you draw, i.e. what graphics object you use.
You added calls to the drawing in the paint event, but you are not using the e.Graphics graphics object in those drawing routines, so you are drawing to a different context (i.e. using a graphics object you created), and that drawing will be wiped out.
It would be common to pass the Paint Event's graphics object to the drawing routine so that it can be used to draw in the same context.
Code:
'...
updatePlots(e.Graphics) 'pass the Paint event's graphics object to the drawing routines
'...
Public Sub updatePlots(g as Graphics)
Dim i As Integer = 0, j As Integer = 0
For Each pole As Decimal In polesArrayX
DrawPoles(g, toPoint(pole, polesArrayY.Item(i))) 'pass the graphics context on to the specific drawing routines
i += 1
Next
For Each zero As Decimal In zerosArrayX
DrawZeros(g, toPoint(zero, zerosArrayY.Item(j))) 'pass the graphics context on to the specific drawing routines
j += 1
Next
End Sub
Public Sub DrawPoles(ByVal g as Graphics, ByVal point As PointF)
Dim p As New System.Drawing.Pen(Color.Red, 0) 'Use 0 for size so the pen is always the minimum size, i.e. 1 pixel, regardless of scale.
' Dim g As System.Drawing.Graphics
' g = picInput.CreateGraphics
g.DrawLine(p, point.X - 5, point.Y + 5, point.X + 5, point.Y - 5) 'need to adjust these 5's to fit the scale of your graph, 5/35 i.e. .142857.
g.DrawLine(p, point.X + 5, point.Y + 5, point.X - 5, point.Y - 5)
' g.Dispose() 'Don't dispose the Paint event's graphics object
End Sub
'Converts Graph coordinates to Picturebox position (for drawing purpose)
Public Function toPoint(ByVal value As PointF) As PointF
value.X = (value.X + xTranslate) ' * scaleRatio 'Don't need to multiply by scaleRatio since you are drawing in the graph's coordinate system
If complex = True Then
value.Y = (value.Y + yTranslate) ' * scaleRatio
Else
value.Y = (-value.Y + yTranslate) ' * scaleRatio
End If
Return value
End Function
I didn't see the circle drawing in your code posted above, but I assume similar adjustment need to be done there.
The other calls you may have elsewhere to the draw routines need to be commented out, and instead your arrays updated and the paint event triggered again (by calling invalidate or refresh) to draw the updates.
I only modified the code visible above that you posted in post 13, so is not tested. I'm on the machine without vb 2012 again, so there may be errors in my post, but should give you the general idea of what needs to be done, your existing code.
Tested and works like a charm! Thank you so much, problem resolved