I have what I think is a basic problem that has me scratching my head.
I want to be able to draw a rectangle on my form while constraining it to a given ratio. Similar to how Photoshop's crop tool works.
I can scale images correctly using a ratio, but I am having trouble applying the formula to a 'live' drawn rectangle.
Here is the basic working code to draw said rectangle.
Public Class Form2
Dim mRect As Rectangle
Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
mRect = New Rectangle(e.X, e.Y, 0, 0)
Me.Invalidate()
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
If e.Button = Windows.Forms.MouseButtons.Left Then
mRect = New Rectangle(mRect.Left, mRect.Top, e.X - mRect.Left, e.Y - mRect.Top)
Me.Invalidate()
End If
End sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
Using pen As New Pen(Color.Red, 3)
e.Graphics.DrawRectangle(pen, mRect)
End Using
End class
The above code works fine to draw a freeform rectangle. I'm just not sure where or how to apply the formula to ensure the drawn rectangle always adheres to a given ratio such as 1.5
Any help would be hugely appreciated. Thanks
Try this ;
Dim mRect As Rectangle
Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
mRect = New Rectangle(e.X, e.Y, 0, 0)
Me.Invalidate()
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
If e.Button = Windows.Forms.MouseButtons.Left Then
mRect = New Rectangle(mRect.Left, mRect.Top, e.X - mRect.Left, e.Y - mRect.Top)
'Replace 1.5 with the scale you want to use
Dim hgt As Integer = Convert.ToInt32(mRect.Height/1.5)
Dim wdth As Integer = Convert.ToInt32(mRect.Width/1.5)
mRect.Size = New Size(wdth*1.5, hgt*1.5)
Me.Invalidate()
End If
End sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
Using pen As New Pen(Color.Red, 3)
e.Graphics.DrawRectangle(pen, mRect)
End Using
End class
Related
I am currently solving a bug that will remove the created rectangle on the PictureBox. The problem is that when I click an Item on the PictureBox and Resize the windows form, the rectangle does not move on with the item selected. This is the code creating the rectangle:
Private Sub paintRectangle(pictBox As System.Windows.Forms.PictureBox, pic As Image)
If pic Is Nothing Then Exit Sub
pictBox.Image = pic
If m_rect_x = -1 And m_rect_y = -1 Then
Return
End If
Dim graphic As System.Drawing.Graphics
Dim redselpen As System.Drawing.Pen
Dim yNegative As Integer = 3
redselpen = New System.Drawing.Pen(Color.Blue)
redselpen.DashStyle = Drawing2D.DashStyle.DashDot
If pictBox.Image IsNot Nothing Then
graphic = System.Drawing.Graphics.FromImage(pictBox.Image)
graphic.DrawRectangle(redselpen, m_rect_x, m_rect_y - yNegative, SystemConfig.iRectWidth, SystemConfig.iRectHeight + 2)
pictBox.Image = pictBox.Image
End If
End Sub
After Resizing the Form, I want to remove the create a rectangle on the PictureBox.
I tried this solution but the Rectangle is still in the PictureBox.
How to remove all the drawn rectangles on the picture box? (Not on the image)
But it does not work, the rectangle is still in the picturebox.
Here's a simple example showing the Paint() event of a PictureBox being used to draw a rectangle that can be moved and turned on/off:
Public Class Form1
Private yNegative As Integer = 3
Private pt As New Nullable(Of Point)
Private drawRectangle As Boolean = False
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
If drawRectangle AndAlso pt.HasValue Then
Using redselpen As New System.Drawing.Pen(Color.Blue)
redselpen.DashStyle = Drawing2D.DashStyle.DashDot
e.Graphics.DrawRectangle(redselpen, pt.Value.X, pt.Value.Y - yNegative, SystemConfig.iRectWidth, SystemConfig.iRectHeight + 2)
End Using
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
pt = New Point(25, 25)
drawRectangle = True
PictureBox1.Invalidate()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
drawRectangle = Not drawRectangle ' toggle the rectangle on/off
PictureBox1.Invalidate()
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
pt = New Point(150, 25)
drawRectangle = True
PictureBox1.Invalidate()
End Sub
End Class
So, I tried to do draw a rectangle by dragging my mouse in a form, and I was successful, but when I try to do the same way in a picturebox no rectangle is created.
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
If fGMouseIsDown And Not PictureBox1.Image Is Nothing Then
rect.Width = e.X - rect.X
rect.Height = e.Y - rect.Y
Invalidate()
End If
End Sub
Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
fGMouseIsDown = True
rect.Location = e.Location
rect.Width = 0
rect.Height = 0
Invalidate()
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
e.Graphics.DrawRectangle(Pens.Blue, rect)
End Sub
Per #HansPassant: The Invalidate() call in your PictureBox1_MouseDown() method invalidates the form, when you instead want to invalidate the picture box.
That call should instead be:
PictureBox1.Invalidate()
Additionally, ensure you drag the right way; this will only work when you go from top-left to bottom-right.
The next code lets you draw Rectangles in the Form with mouse clics.
Why not, or how can be draw over a PictureBox?
Public Class Form1
Dim SelectRect As Rectangle = New Rectangle()
Dim ps As Point = New Point()
Dim pe As Point = New Point()
This catch the first click, starting point or corner of the rectangle
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
SelectRect.Width = 0
SelectRect.Height = 0
SelectRect.X = e.X
SelectRect.Y = e.Y
ps.X = e.X
ps.Y = e.Y
pe = ps
End Sub
This part determine the width and height of the rectangle:
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
If (e.Button = MouseButtons.Left) Then
ControlPaint.DrawReversibleFrame(Me.RectangleToScreen(SelectRect), Color.Black, FrameStyle.Dashed)
SelectRect.Width = e.X - SelectRect.X
SelectRect.Height = e.Y - SelectRect.Y
ControlPaint.DrawReversibleFrame(Me.RectangleToScreen(SelectRect), Color.Black, FrameStyle.Dashed)
End If
End Sub
This part determine the last coordinate, the second corner of the rectangle:
Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
Dim g As Graphics = Me.CreateGraphics()
Dim p As Pen = New Pen(Color.Blue, 2)
ControlPaint.DrawReversibleFrame(Me.RectangleToScreen(SelectRect), Color.Black, FrameStyle.Dashed)
g.DrawRectangle(p, SelectRect)
g.Dispose()
End Sub
End Class
Your code uses a control (a Form in this case) mouse events to enable the drawing of rectangular shapes, with the help of guidelines, provided by Control.DrawReversibleFrame().
You just have to define the same events of a different, drawable, control - like a PictureBox - and repeat, more or less, the same procedure (after a cleanup).
As many have stated, here and before, use the Graphics object that
the Paint event kindly offers, so that your drawing will persist.
The Graphics object you get from Control.CreateGraphics() is not
persistent, and it can be erase/clipped when you don't want to.
Use it only if that is really what you have planned to do for the
reasons you know.
I've adden an event handler that checks if Control Key is pressed.
If Control is pressed, you add a rectangle, if not, only one rectangle is drawn.
I've also included, as an example, a line of code that fills the rectangle. I think it's interesting, because you have to control the size of the invalidated Region.
Comment out these lines of code to draw just the frame:
SelectRect.Inflate(CInt(-_pen.Width / 2), CInt(-_pen.Width / 2))
e.Graphics.FillRectangle(_brush, SelectRect)
Dim SelectRect As Rectangle = New Rectangle()
Dim _pen As Pen = New Pen(Color.Green, 4)
Dim _brush As SolidBrush = New SolidBrush(Color.Orange)
Dim _ControlPressed As Boolean = False
Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown
_ControlPressed = (e.Modifiers And Keys.Control) = Keys.Control
End Sub
Private Sub Form1_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
_ControlPressed = (e.Modifiers And Keys.Control) = Keys.Control
End Sub
Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
SelectRect.Location = e.Location
SelectRect.Size = New Size(0, 0)
End Sub
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
If (e.Button = MouseButtons.Left) Then
ControlPaint.DrawReversibleFrame(PictureBox1.RectangleToScreen(SelectRect), PictureBox1.BackColor, FrameStyle.Dashed)
SelectRect.Width = e.X - SelectRect.X
SelectRect.Height = e.Y - SelectRect.Y
ControlPaint.DrawReversibleFrame(PictureBox1.RectangleToScreen(SelectRect), PictureBox1.BackColor, FrameStyle.Dashed)
End If
End Sub
Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
If (e.Y < SelectRect.Y) Then
SelectRect.Location = If(SelectRect.Location.X > e.X,
New Point(e.X, e.Y), New Point(SelectRect.X, e.Y))
SelectRect.Size = New Size(Math.Abs(SelectRect.Width), Math.Abs(SelectRect.Height))
Else
If SelectRect.Location.X > SelectRect.Right Then
SelectRect.Location = New Point(e.X, SelectRect.Y)
SelectRect.Size = New Size(Math.Abs(SelectRect.Width), Math.Abs(SelectRect.Height))
End If
End If
If _ControlPressed Then
Dim _InflatedRect As Rectangle = New Rectangle(SelectRect.Location, SelectRect.Size)
_InflatedRect.Inflate(CInt(_pen.Width / 2), CInt(_pen.Width / 2))
PictureBox1.Invalidate(_InflatedRect)
Else
PictureBox1.Invalidate()
End If
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
'Draw the outer rectangle with the color of _pen
e.Graphics.DrawRectangle(_pen, SelectRect)
'Fill the rectangle with the color of _brush
'It's half Pen.Width smaller so it doesn't erase the contour
SelectRect.Inflate(CInt(-_pen.Width / 2), CInt(-_pen.Width / 2))
e.Graphics.FillRectangle(_brush, SelectRect)
End Sub
I tried to recreate a small thing I tried ages ago. It's literally just a simple paint program. The code is basically:
Public Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
X = Control.MousePosition.X
Y = Control.MousePosition.Y
Mdown = True
End Sub
Private Sub Form1_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
Dim g As Graphics = Me.CreateGraphics
Dim NX As Integer = Control.MousePosition.X
Dim NY As Integer = Control.MousePosition.Y
If Mdown = True Then
g.DrawLine(System.Drawing.Pens.Red, X, Y, NX, NY)
X = NX
Y = NY
End If
End Sub
Private Sub Form1_MouseUp(sender As Object, e As MouseEventArgs) Handles Me.MouseUp
Mdown = False
End Sub
It works fine, the line draws from the main point to the next as the mouse moves. However, the accuracy of the drawn line is questionable. When drawing in the regular window size (586, 634) on my second monitor (Running at 1280x720) the line very closely follows the mouse tip (but isn't exact). But when the window is on my main (1920x1080) screen, the line is WAY off. Is there a specific reason for this, because I thought calling Control.MousePosition.X/Y got the mouse's position in relation to the window's size not the screen size? (Or something else)
I'm usually able to figure these things out on my own, but this just seems wrong in general. Any ideas?
From MSDN:
The MousePosition property returns a Point that represents the mouse
cursor position at the time the property was referenced. The coordinates indicate the position on the screen, not relative to the control, and are
returned regardless of whether the cursor is positioned over the control. The
coordinates of the upper-left corner of the screen are 0,0.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.mouseposition%28v=vs.110%29.aspx
You are getting the position of the mouse relative to the screen instead of to the control that raised the mouse event.
For the latter you should use the MouseEventArgs variable e, and specifically its Location property.
That way you get the position relative to your form instead of the screen.
E.g.
Public Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
X = e.X 'Equal to X = e.Location.X
Y = e.Y 'Equal to Y = e.Location.Y
Mdown = True
End Sub
So it is not a problem with an inaccuracy of the drawn line, but of the coordinates to provide to the DrawLine method. In your code you can notice that the offset shifts with the position of your form on the screen.
When you draw with CreateGraphics(), the drawing is temporary (minimize/restore the app and see what happens). To make the drawing persistent, store the info in a GraphicsPath and render it in the Graphics supplied to you via e.Graphics in the Paint() event of the Form:
Public Class Form1
Private pt1 As Point
Private curGP As Drawing2D.GraphicsPath
Private GPs As New List(Of Drawing2D.GraphicsPath)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.DoubleBuffered = True
End Sub
Public Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
If e.Button = MouseButtons.Left Then
curGP = New Drawing2D.GraphicsPath
GPs.Add(curGP)
pt1 = New Point(e.X, e.Y)
End If
End Sub
Private Sub Form1_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
If e.Button = MouseButtons.Left Then
Dim pt2 As New Point(e.X, e.Y)
curGP.AddLine(pt1, pt2)
pt1 = pt2
Me.Invalidate()
End If
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
For Each GP As Drawing2D.GraphicsPath In GPs
e.Graphics.DrawPath(Pens.Red, GP)
Next
End Sub
End Class
The Program
I'm playing around and learning about graphics using visual basic (coming from C++). I've made a program and I want to do two things: paint when the left mouse button is pressed, and stop when released, and also I want to be able to change the pen color using a colordialog. After hours of frustration, I've yet to combat these two problems.
The Code (Snippet)
Private obj As Graphics
Dim rect As Rectangle
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
obj = RichTextBox1.CreateGraphics
End Sub
Private Sub Form1_FormClosed(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles MyBase.FormClosed
obj.Dispose()
End Sub
Private Sub RichTextBox1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles RichTextBox1.MouseMove
With rect
.X = e.X
.Y = e.Y
.Width = TrackBar1.Value
.Height = TrackBar1.Value
End With
If ToolStripButton1.Checked = True Then
obj.DrawEllipse(Pens.Black, rect)
ElseIf ToolStripButton2.Checked = True Then
obj.DrawRectangle(Pens.Black, rect)
End If
ToolStripStatusLabel2.Text = (e.X & ", " & e.Y)
End Sub
Past Attempts (and frustrations)
My idea originally was to do this:
Dim myPen = New Pen(ButtonWithDC1.BackColor)
But doing so gave me an error message. I looked at Microsoft's documentation, but it wasn't useful for what I'm trying to do. I can create a pen just fine, but I'd like for the user to be able to change the color while the app is currently running.
GUI Layout
I don't have an attempt at my other problem (drawing while pressing the mouse down, not just by moving the mouse -- like a normal paint program), I don't even have a starting point for that solution. Thanks to everyone in advance.
Place a button (Button1) and picturebox (PictureBox1) on a form, also add a colordialog (ColorDialog1).
This code will allow you to draw on the picture box and choose the color using a color you select from the colordialog. The MouseDown event writes a flag that the mouse is down, and stores the last location. The MouseUp does similar. The MouseMove actually draws. Use a line and the last location.
Public Class Form1
Private myColor As Color = Color.Black
Private mouseIsDown As Boolean = False
Private previousLocation As System.Nullable(Of System.Drawing.Point) = Nothing
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myColor = If(ColorDialog1.ShowDialog() = Windows.Forms.DialogResult.OK, ColorDialog1.Color, myColor)
End Sub
Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
mouseIsDown = True
previousLocation = e.Location
End Sub
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
If mouseIsDown Then
If previousLocation IsNot Nothing Then
Using g As Graphics = Graphics.FromImage(PictureBox1.Image)
g.DrawLine(New Pen(myColor), previousLocation.Value, e.Location)
End Using
PictureBox1.Invalidate()
End If
previousLocation = e.Location
End If
End Sub
Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
mouseIsDown = False
previousLocation = Nothing
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.PictureBox1.Image = New Bitmap(PictureBox1.Width, PictureBox1.Height)
End Sub
End Class
Your question is a bit more involved than you think, and SO is not keen on multiple questions per post (not in your nest interest either - someone might know A and not B so wont bother answering).
To draw on Mousemove when the mouse is down, you need to track when the mouse is down (question A):
Private _mouseDown As Boolean
Private _mouseLoc As Point
Private _mouseNewLoc As Point
sub Ctl_MouseDown(sender...
' ToDo: add logic to check which button....
_mouseDown = True
_mouseLoc = New Point(e.X, e.Y)
End Sub
sub Ctl_MouseUp(sender...
_mouseDown = False
End Sub
Then mousemove can be used to capture the current location
Sub Ctl_MouseMove(sender....
If _mouseDn Then
_mouseNewLoc = New Point(e.X, e.Y)
Ctl.invalidate ' call to paint
End If
End Sub
' selected color from dialog or whereever
Private myColor As Color
Sub Ctl_Paint(sender....
If _mouseDn Then
' Pen ctor is overloaded...(Question B)
Using p As New Pen(myColor)
e.Graphics.DrawLine(p, _mouseLoc, _mouseNewLoc)
' plus more....
End Using
End If
This only addresses the questions posed; the bigger issue you have is tracking what has already been drawn. This will draw a line only while the mouse is down, but for a polygon or shape, you have to add code to redraw those parts. Either a List of the points which make up a polygon, or maybe save what you have to a bitmap and add to it. Thats a bit outside the scope of the question and depends on app factors. You also need a Drawing start/stop or way to signal when to stop adding lines or ovals or whatever (ovals are sort of simple: one at a time, lines as part of a shape will take some work).
Either way all painting has to take place in the Paint event (or OnPaint) if you want to see the shape/drawing/image develop.