How do I clear a drawn line in vb.net? - vb.net

I am writing a simple test program that draws an axis/crosshair in a form. I have two text boxes, where I put in the x-center and y-center and draw the crosshair based on that. I want to be able to put in new coordinates, and move the crosshair to the new position, but when I do, the old drawing stays there. I want to erase the old drawing and then draw the new one.
My code is below:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim mypen As Pen
mypen = New Pen(Drawing.Color.Red, 1)
Dim mygraphics As Graphics = Me.CreateGraphics
Dim x_center = Integer.Parse(xPos.Text)
Dim y_center = Integer.Parse(yPos.Text)
mygraphics.DrawLine(mypen, x_center - 50, x_center, x_center + 50, x_center)
mygraphics.DrawLine(mypen, y_center, y_center - 50, y_center, y_center + 50)
End Sub
End Class

The Drawing on a Control surface is usually handled through the Control's Paint() event, using its PaintEventArgs class object.
To raise the Paint() event of a Control, call its Invalidate() method.
(Note that the Invalidate() method has a number of overloads, some of which allows to re-paint only a defined region of the surface.)
If a Graphics object is created elsewhere (as you're doing now), the drawings performed with this object will persist or will be erased when you don't want to (e.g. if a Control needs to repaint itself - and this happens quite often - the drawings will be erased).
Also, the Graphics object can't be stored. It will become an invalid object as soon as a Control has repainted its surface.
You could re-design you code in this way.
Create a shared Pen (you can redefined it at any moment if you need to, using its properties) so you don't have to create a new one every time you need to draw something.
Use a shared Point field to store the current center of the drawing.
Move the Graphics.DrawLine() to the Paint event of your Form.
Remember to Dispose() the Pen object when the Form closes (you can use it's Dispose() pre-defined method).
Public Class Form1
Private mypen As Pen = New Pen(Color.Red, 1)
Private Position As Point = New Point(-1, -1)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If (Integer.TryParse(xPos.Text, Position.X) = True) AndAlso
(Integer.TryParse(yPos.Text, Position.Y) = True) Then
Me.Invalidate()
End If
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
If Position.X > -1 Then
e.Graphics.DrawLine(mypen, Position.X - 50, Position.Y, Position.X + 50, Position.Y)
e.Graphics.DrawLine(mypen, Position.X, Position.Y - 50, Position.X, Position.Y + 50)
End If
End Sub
End Class
This is, however, not that much efficient, because you need to invalidate the entire Form.
For a full implementation, take a look a this Class (PasteBin - CrossHair).

Related

I draw image to form, but it is limited to top left corner of form

I have created a form and imported two square images saved as PNG files in resources. when I run the code below the black box which is drawn will only go about 200 pixels in the x coordinate and 150 pixels in the Y coordinate from where the image is drawn, after that the background remains white, and it seems I am unable to draw anything and anything I do draw stops around this point.
I have tried redrawing the image in a completely different location on the screen and It will not be visible if it is not within the region to the top left of the form, I have also tried drawing other images, but they also cease to exist when not in the top left of my form.
What I want is for the black box/other images to be drawn across the whole form, and not just in the top left corner, which something is preventing me from doing.
Public Class Form1
Dim gameGraphics As System.Drawing.Graphics = Me.CreateGraphics
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Draws black square which I have saved as resource
gameGraphics.DrawImage(My.Resources.black_Background, 0, 80, 1600, 600)
'Draws green square which I have saved as resource
gameGraphics.DrawImage(My.Resources.greenSquare, 2, 82, 40, 40)
End Sub
'makes the form fullscreen
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.FormBorderStyle = FormBorderStyle.None
Me.WindowState = FormWindowState.Maximized
End Sub
'closes form if quitbutton is clicked
Private Sub QuitButton_Click(sender As Object, e As EventArgs) Handles QuitButton.Click
Me.Close()
End Sub
End Class
Thanks for your time!
The Graphics Object cannot be stored. It's Erased/Updated constantly. You'll end up with an invalid one. It's really useless and, you could say, a mistake.
You can use a Graphics Object created with Control.CreateGraphics(), but you have to remember that it's not persistent; it will be erased when the Control you have painted it on needs to re-Paint() itself (e.g. you drag something over it, if it's a Form, when it's minimized etc.).
Those Properties, Me.FormBorderStyle = FormBorderStyle.None and Me.WindowState = FormWindowState.Maximized are better set in the designer. There's no reason to set them on a Form.Load() event. Their state is not even subject to a condition. In general, leave the Load event of a Form as lightweight as possible and avoid setting properties that can cause cascading events.
An example:
Define an object to store your images:
(The DrawBitmaps flag is used to let your Form know when to draw those Bitmaps).
Public Class MyBitmap
Public Property Image As Bitmap
Public Property Position As Point
Public Property Size As Size
End Class
Public MyBitmaps As List(Of MyBitmap)
Public DrawBitmaps As Boolean = False
Somewhere (even in Form.Load()), fill the list with you bitmaps:
(Here, the bitmap size is set to original size, but you can set it to whatever dimension you see fit).
MyBitmaps = New List(Of MyBitmap)
MyBitmaps.Add(New MyBitmap With {.Image = My.Resources.black_Background,
.Position = New Point(0, 80),
.Size = New Size(My.Resources.black_Background.Width,
My.Resources.black_Background.Height)})
MyBitmaps.Add(New MyBitmap With {.Image = My.Resources.greenSquare,
.Position = New Point(2, 82),
.Size = New Size(My.Resources.greenSquare.Width,
My.Resources.greenSquare.Height)})
The Paint() event e.Graphics of the Form performs all the painting:
(Note that it will not paint its surface unless the DrawBitmaps flag is set to True => It will not paint those Bitmaps when it's loading/showing. The other condition is a basic fail-safe.
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
If DrawBitmaps = True AndAlso MyBitmaps.Count > 0 Then
For Each _Item As MyBitmap In MyBitmaps
e.Graphics.DrawImage(_Item.Image, New Rectangle(_Item.Position, _Item.Size))
Next
End If
End Sub
When Button1 is clicked, the Form will draw your list of Bitmaps:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DrawBitmaps = True
Me.Invalidate()
End Sub
Somewhere in your code, add a new Bitmap and tell the Form to Invalidate only a region of the size of this new Bitmap:
MyBitmaps.Add(New MyBitmap With {.Image = My.Resources.[AnotherBitmap],
.Position = New Point(50, 50),
.Size = New Size(200, 200)})
Me.Invalidate(New Rectangle(MyBitmaps.Last().Position, MyBitmaps.Last().Size))
Remove a Bitmap from the list and repaint:
MyBitmaps.RemoveAt(0)
Me.Invalidate()

VB.net Graphics not drawing to screen

I am creating a hangman game and i cant get my dashes to draw on the screen.
Public Class Form1
Public lines() As String= IO.File.ReadAllLines("C:\Users\axfonath14\Desktop\words_alpha.txt")
Dim thisArray As String() = lines.ToArray
Public word As String = thisArray(CInt(Int((thisArray.Length * Rnd()) + 1)))
Public g As Graphics = Me.CreateGraphics()
Public dash As Image = My.Resources.dash
Public x As Integer = 1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.Text = word
For Each c As Char In word
x += 1
g.DrawImage(dash, 125 + (100 * x), 100)
Next
Label2.Text = x
End Sub
End Class
when i run this code it runs through the loop and changes the label to the amount of letters but it just wont draw anything.
Never, EVER call CreateGraphics. Lazy people do so without explanation in examples and tutorials but it is never appropriate in a real application. ALWAYS do your drawing in the Paint event handler of the control you want to draw on. In your case, get rid of that g field altogether, move your drawing code to the Paint event handler of the form and use the Graphics object that it provides:
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
For Each c As Char In word
x += 1
e.Graphics.DrawImage(dash, 125 + (100 * x), 100)
Next
End Sub
That way, your drawing gets restored each time the form gets erased and repainted, which can be often and will be after the Load event handler completes.
If you want to change what gets drawn then you need to store the appropriate data in fields that can then be accessed from the Paint event handler. To force a repaint, call Invalidate on the form or control whose Paint event you're handling.
You're grabbing the graphics context before the form has initialized. Move your initialization code to the actual constructor, after the InitializeComponent() method is called.

Custom rounded button with outline

I am tring to create a custom button width rounded corner and a white outline which follows its shape. On the OnPaint event I've added the following code.
Public Class RoundedButton
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
Dim grPath As GraphicsPath = New GraphicsPath(FillMode.Winding)
grPath.AddArc(0, 0, ClientSize.Height, ClientSize.Height, 90, 180)
grPath.AddLine(grPath.GetLastPoint, New Point(ClientSize.Width - grPath.GetLastPoint.X * 2, 0))
grPath.AddArc(New RectangleF(grPath.GetLastPoint, New Size(ClientSize.Height, ClientSize.Height)), 270, 180)
grPath.CloseFigure()
Me.Region = New Region(grPath)
Dim mypen As New Pen(Color.White, 2)
mypen.Alignment = PenAlignment.Inset
e.Graphics.DrawPath(mypen, grPath)
End Sub
End Class
If I try to use it in a form, it works only if Backcolor property is set to Transparent. If not I can't see the pen path.
I would like to change the backcolor without loose the rounded white border of the pen.
I would like to obtain something like this:
With all projects that you want custom buttons, it is usually easiest to curve a PictureBox with a picture of your button, then using your coding language (VB.NET, C#, C++) to add an on click function to make the action.
Here is a mini example using VB.NET:
Private Sub pictureBox1_Click(sender As Object, e As EventArgs) Handles pictureBox1.Click
Process.start("http://stackoverflow.com")
End Sub
Hope I helped,
Matt

How to use the Paint event more than once on a form?

Okay, so I am trying to make a program that each time you click (doesn't matter where) a random colored, and sized circle appears where you happened to click. however, the only way I can add a shape is via Paint event. here is the code I have now:
Private Sub Form1_Paint(ByVal Sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
Using Brush1 As New SolidBrush(Color.Orange)
e.Graphics.FillEllipse(Brush1, MousePosition.X, MousePosition.Y, 100, 100)
End Using
End Sub
I need to know a line of code that I can use in a mouse click event, that will re-run this sub. I know how to change the size, and make it random, I just don't know how to run this sub multiple times, more precisely; run this sub once after each mouse click. If someone can help, I would appreciate it!
Just as Plutonix explained, a refresh is handled by calling the Invalidate method.
The thing you need to remember is that whatever is painted on a surface is not persistent, so you need to redraw the whole screen every time. There are, of course, many ways in which this can be optimized for performance purposes, as this process can be extremely CPU intensive; specially, since GDI+ is not hardware accelerated.
So, what you need to do is:
Record every click (x, y position) and store it
Since the radius of each circle is random, determine the radius when the user clicks the form, then store it along with the x, y position of the click
Then, have the Paint event re-draw each stored sequence of clicks (with their respective radii) and re-draw each circle over and over.
Here's an implementation that will do the trick. Just paste this code inside any Form's class to test it:
Private Class Circle
Public ReadOnly Property Center As Point
Public ReadOnly Property Radius As Integer
Public Sub New(center As Point, radius As Integer)
Me.Center = center
Me.Radius = radius
End Sub
End Class
Private circles As New List(Of Circle)
Private radiusRandomizer As New Random()
Private Sub FormLoad(sender As Object, e As EventArgs) Handles MyBase.Load
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True) ' Not really necessary in this app...
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.SetStyle(ControlStyles.UserPaint, True)
End Sub
Private Sub FormMouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
circles.Add(New Circle(New Point(e.X, e.Y), radiusRandomizer.Next(10, 100)))
Me.Invalidate()
End Sub
Private Sub FormPaint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Dim g As Graphics = e.Graphics
g.Clear(Color.Black)
Using p As New Pen(Color.White)
For Each c In circles
g.DrawEllipse(p, c.Center.X - c.Radius \ 2, c.Center.Y - c.Radius \ 2, c.Radius, c.Radius)
Next
End Using
End Sub
Here's what you'll get after a few clicks on the form

VB.NET Custom Control (custom drawing) Refresh issue

I've created a simple solution with 2 projects. The 1st project (class library) contains a custom control called Container which draws itself with rounded corners. The 2nd project (windows forms) is a test application.
If I add a Container instance to main Form in the 2nd project it shows the rounded corners nicely. Also when I run the 2nd project I can see the Container.
However when I start moving the form (click and hold the title bar), especially when I move it very fast, all the drawing is messed up, drawn over and over again but not clearing it's surface first...
I can call Container1.Refresh() in the Form1.Move event, but I don't want to set this every time because this also means I have to call Container1.Refresh() in the Form1.Resize event and who knows which other event...
Is there an event in the Container (control) class itself where I should call Me.Refresh() or Me.Update() or Me.Invalidate() ?
For reference (Form1.vb)
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Form1_Move(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Move
Me.Container1.Refresh()
End Sub
End Class
for reference (Container.vb):
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Public Class Container : Inherits Control
Private _Gp As GraphicsPath
Private Sub Container_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim r As Rectangle = e.ClipRectangle
Dim gp As New GraphicsPath
Dim cs As Integer = 25 'CornerSize'
r.Inflate(-5, -5)
gp.AddArc(r.X, r.Y, cs, cs, 180, 90)
gp.AddArc(r.X + r.Width - cs, r.Y, cs, cs, 270, 90)
gp.AddArc(r.X + r.Width - cs, r.Y + r.Height - cs, cs, cs, 0, 90)
gp.AddArc(r.X, r.Y + r.Height - cs, cs, cs, 90, 90)
Dim t As Single = cs / 2 + r.Y
gp.AddLine(r.X, r.Y + r.Height - cs, r.X, t)
e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias
e.Graphics.DrawPath(Pens.Black, gp)
End Sub
End Class
This is your problem:
Dim r As Rectangle = e.ClipRectangle
Change it to:
Dim r As Rectangle = Me.ClientRectangle
It looks to me as though your Container class isn't painting its entire area - normally a control is responsible for painting its entire rectangle.
In order to have a control that doesn't do this - that has transparent areas (like your rounded corners) - you need to give your control the WS_EX_TRANSPARENT property. Note that this is a Windows API subject, not a .NET one, so you're heading in the direction of some minor voodoo.
While it's written in C#, the CodeProject article Making Transparent Controls with C# and .NET 3.5 does seem directly relevant to what you're trying to achieve.
To quote that article, you first need to override the constructor of your UserControl and configure the background:
public TranspControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
Then, you need to override the CreateParams() method to set the control style WS_EX_TRANSPARENT:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
It shouldn't be necessary to force a redraw here (under normal circumstances) since that redraw is automatically forced as soon as your control gets nudged.
However, what you need to do is clearing the background of your control before painting anything else: otherwise, your painting operation will mingle with previous painting processes. Just add an
e.Graphics.Clear(BackColor)
before your other drawing operations in the Paint event handler. Also, consider using the OnPaint method rather than the Paint event since you subclass the control and don't need to resort to the Paint event handler.
For the record, Refresh forces a synchronous redraw which is usually not desired. Rather, use Invalidate which enqueues the redraw request into the default window message queue.