Dynamically change pen color & paint when clicked - vb.net

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.

Related

How can I change one label out of many by right clicking one of them

I have some simple code. It changes the BorderStyle property of a Label by right-clicking. Nothing fancy, but still. However, I have twenty labels. Is there a simpler way of doing this instead of "copy-paste" this code 20 times?
Private Sub Label1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Label1.MouseDown
If e.Button = MouseButtons.Right Then
If Label1.BorderStyle = BorderStyle.None Then
Label1.BorderStyle = BorderStyle.FixedSingle
Else
Label1.BorderStyle = BorderStyle.None
End If
End If
End Sub
Private Sub Label2_MouseDown...
...
End Sub
You could either create a custom control which inherits from Label and has the behaviour you want, or you could write a handler which works out which control it is responding to from the sender parameter.
The latter, presented first here, is simpler for a one-off, but the former would be more re-usable, and you wouldn't have to maintain the list of Labels for the AddHandler.
Sub Label_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
Dim lbl = DirectCast(sender, Label)
If e.Button = MouseButtons.Right Then
If lbl.BorderStyle = BorderStyle.None Then
lbl.BorderStyle = BorderStyle.FixedSingle
Else
lbl.BorderStyle = BorderStyle.None
End If
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each l In {Label1, Label2}
AddHandler l.MouseDown, AddressOf Label_MouseDown
Next
End Sub
The AddHandler line connects the MouseDown event of each of the Labels to the specified event handler. (You can add more than one event handler to an event, if needed.)
For a control (your very own custom one) derived from an existing control (a System.Windows.Forms.Label in this case), let's call it BorderedControl, you can follow the instructions at How to: Inherit from Existing Windows Forms Controls (it's too close to plagiarism to copy it to here), and then your code for the control might look like:
Public Class BorderedLabel
Inherits Label
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
If e.Button = MouseButtons.Right Then
If Me.BorderStyle = BorderStyle.None Then
Me.BorderStyle = BorderStyle.FixedSingle
Else
Me.BorderStyle = BorderStyle.None
End If
End If
MyBase.OnMouseDown(e)
End Sub
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
End Class
After you have built your project after adding that code, you will find a new control, named "BorderedLabel", in the ToolBox. You can drag that onto the form "design surface" and it will behave just like an ordinary Label except that it will have your BorderStyle-changing code incorporated automatically.

PictureBox2.Image = PictureBox1.Image seems to be linking the boxes instead of copying the image over?

The Problem
I have a simple form with 2 PictureBoxes
I allow the user to draw on PictureBox1
When I click a Button on the form I want to capture the image in PictureBox1 and store it in PictureBox2
The issue is that if I add the line:
PictureBox2.Image = PictureBox1.Image
Any updates to PictureBox1 are immediately reflected in PictureBox2 ?!?
I just want to capture the image in PictureBox1 at that moment in time so that I can use it to 'Undo'
Tech
It's a Windows Forms App in Visual Basic, .Net 4.7.2 using Visual Studio 2019 Preview
Code
Public Class Form1
Dim drawMouseDown = False ' Set initial mouse state to not clicked
Dim drawMyBrush As New Pen(Brushes.White, 20) 'Set up the Brush
Public drawCanvas As New Bitmap(245, 352) 'Set up Bitmap Canvas
Private Sub btn_Color_Yellow_Click(sender As Object, e As EventArgs) Handles btn_Color_Yellow.Click
drawMyBrush.Brush = Brushes.Yellow
drawMyBrush.Width = 20
End Sub
Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
drawMouseDown = True
End Sub
Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
drawMouseDown = False
End Sub
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
Dim g As Graphics = Graphics.FromImage(drawCanvas)
Static coord As New Point
If drawMouseDown Then
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
drawMyBrush.StartCap = Drawing2D.LineCap.Round
drawMyBrush.EndCap = Drawing2D.LineCap.Round
g.DrawLine(drawMyBrush, coord.X, coord.Y, e.X, e.Y)
g.Dispose()
PictureBox1.Image = drawCanvas
Me.Refresh()
End If
coord = e.Location
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PictureBox2.Image = PictureBox1.Image 'Why does this not just update the PicBox2 image once?!? (or only when the Button is clicked)
End Sub
End Class
Expectation
When Button1 is clicked I expect PictureBox2 to contain the PictureBox1 image, when I continue to draw on PictureBox1 I do NOT expect it to keep updating PictureBox2 as the user is drawing on the other!
As you suspect, PictureBox2.Image = PictureBox1.Image makes the former a reference to the latter.
What you can do instead is clone the image:
PictureBox2.Image = DirectCast(PictureBox1.Image.Clone(), Image)
Because you are referencing the Image property of PictureBox2 to PictureBox1.Image. So when they both point to the same reference, any changes to either of the PictureBox's image property, will affect the other's.
In order to avoid his, make a new instance of Bitmap object based on the PictureBox1.Image and set it to PictureBox2.Image:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PictureBox2.Image = New Bitmap(PictureBox1.Image)
End Sub

DrawLine Accuracy?

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

Graphics.DrawRectangle not working in control events

First of all thank you for taking the time out of your busy schedule to assist me.
I am developing a project (Win Application) with a Form and 3 textboxes (TextBox1, TextBox2 and TextBox3).
I need draw a rectangle around the textbox when focused this.
The code is:
Private Sub TextBox123_Enter(sender As Object, e As System.EventArgs) Handles TextBox1.Enter, TextBox2.Enter, TextBox3.Enter
Using g As Graphics = Me.CreateGraphics
Dim r As Rectangle = sender.Bounds
r.Inflate(4, 4)
g.DrawRectangle(Pens.Blue, r)
End Using
End Sub
The problem is the following:
The first time the textbox1 gains focus rectangle is not drawn.
The first time the textbox2 gains focus rectangle is not drawn.
Why not the rectangle is drawn when the first two events enter are fired?
Drawing with CreateGraphics is almost always not the correct approach. If you notice also, when you move from one box to another, the old rectangle is not being erased. You need to use the Form_Paint event to get it to work right. Or...perhaps simpler would be to create a UserControls which is 1-2 pixels larger than a child TextBox and set the backcolor of the UserControl canvas, draw your rectangle when the control gets the focus.
For form paint:
Public Class Form1
Private HotControl As Control
If you are only going to do TextBoxes, you can declare it As TextBox. This way it allows you to do the same for other control types. Set/clear the tracker:
Private Sub TextBox3_Enter(sender As Object, e As EventArgs) Handles TextBox3.Enter,
TextBox2.Enter, TextBox1.Enter
HotControl = CType(sender, TextBox)
Me.Invalidate()
End Sub
Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave,
TextBox2.Leave, TextBox3.Leave
HotControl = Nothing
Me.Invalidate()
End Sub
The Me.Invalidate tells the form to redraw itself, which happens in Paint:
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
If HotControl IsNot Nothing Then
Dim r As Rectangle = HotControl.Bounds
r.Inflate(4, 4)
e.Graphics.DrawRectangle(Pens.Blue, r)
End If
End Sub
You should also turn on Option Strict.
Try this in the click event handler
Private Sub TextBox1_Click(sender As Object, e As EventArgs) Handles TextBox1.Click
Using g As Graphics = Me.CreateGraphics()
Dim rectangle As New Rectangle(TextBox1.Location.X - 1, _
TextBox1.Location.Y - 1, _
TextBox1.Size.Width + 1, _
TextBox1.Size.Height + 1)
g.DrawRectangle(Pens.Blue, rectangle)
End Using
End Sub

Picturebox / Panel tearing when drag vb.net

I'm trying to make VB.net program and UI is like normal desktop. I have images in picture
boxes as desktop shortcuts. So user can drag them around in form. Btw im not skilled just started
to do things like this.
I can move picture boxes with mouse events its fine But pictures in pictureboxes are tearing when dragging. i have a big background picture, i guess this is the problem but i need help to get rid of tearing. heres code thx.
i also tryed as a panel and get same result.
Edit : pictures about dragging is png , 50x50 , transparent BG.
Public Class testform
Dim drag As Boolean
Dim mousex, mousey As Integer
Private Sub testform_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.BackgroundImage = My.Resources.worldmap 'approx 1.03mb Picture size
End Sub
Private Sub dragbox_MouseDown(sender As Object, e As MouseEventArgs) Handles dragbox.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
drag = True
mousex = Windows.Forms.Cursor.Position.X - dragbox.Left
mousey = Windows.Forms.Cursor.Position.Y - dragbox.Top
Else
End If
End Sub
Private Sub dragbox_MouseMove(sender As Object, e As MouseEventArgs) Handles dragbox.MouseMove
If drag = True Then
dragbox.Left = Windows.Forms.Cursor.Position.X - mousex
dragbox.Top = Windows.Forms.Cursor.Position.Y - mousey
End If
End Sub
Private Sub dragbox_MouseLeave(sender As Object, e As EventArgs) Handles dragbox.MouseLeave
drag = False
End Sub
End Class
You need to make the picture form double buffered. That means that it will no longer tear as it will first completely make sure it's all in place before showing the next frame.