I am displaying images using a timer and picturebox. The images seem to be adding on top of each other instead of displaying one, then removing that one, and loading another.
As the images are displayed, I would like to overlay text on the images.
Here is the code I am using to create images and text
Dim fname As String = (Actually an array in images(jpg) that display with timer)
Dim bm As New Bitmap(fname)
PicBox.Image = bm
findex -= 1 (index of image array)
Dim g As Graphics = PicBox.CreateGraphics
g.DrawImage(bm, 300, 10)
g.DrawString("Kishman Tukus", New Font("Arial", 24, FontStyle.Bold), Brushes.Green, 400, 100)
g.ResetTransform() '
g.Dispose()
I need the images to display in the picturebox one at a time using the timer and I need to overlay
text on the images too.
can someone help me stop the images from adding to the picturebox instead of displaying one at a time?
Or even better, don't use a PictureBox at all, just display images with text overlay?
In any case, i need to stop the memory bleed.
thank you
I think it's important how you load the images. Do you really load each image by filename each time you show it? That's misleading, as you mentioned there is an array. The distinction is you keep references to these images and you are modifying each one. Remember this is a reference type so the original items gets modified. So you end up with text repeatedly being written over itself. The irony is that if you did actually load the image each tick, then you wouldn't actually have this problem :)
I made something along the lines of what you have (I think), where we use Object.Clone to make a copy in memory of each bitmap which you can modify without modifying the original image.
Private images As New List(Of Bitmap)()
Dim findex As Integer = 0
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim bmCopy = DirectCast(images(findex).Clone(), Bitmap)
Using g As Graphics = Graphics.FromImage(bmCopy)
g.DrawString(Guid.NewGuid().ToString(), New Font("Arial", 24, FontStyle.Bold), Brushes.Green, 400, 100)
End Using
PicBox.Image = bmCopy
findex = (findex + 1) Mod images.Count()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
images.Add(My.Resources.Image1)
images.Add(My.Resources.Image2)
Timer1.Interval = 1000
Timer1.Enabled = True
End Sub
There is no memory leak. You can check the memory increase but the GC eventually clears it out.
I would expect to see something more like:
Dim bm As New Bitmap(fname)
Using g As Graphics = Graphics.FromImage(bm)
g.DrawString("Kishman Tukus", New Font("Arial", 24, FontStyle.Bold), Brushes.Green, 400, 100)
End Using
PicBox.Image = bm
Related
I'm trying to get familiar with graphics in vb.net and want to draw just a straight line going from the top left to the bottom right of my form Form1 without using the Form1_Paint method but by using Button1
However, the line is only drawn about a quarter of the way and stops as shown in the picture:
I think I need to increase the Clip of the Graphics Object z and I tried the following but it doesn't work. I also tried decreasing the Clip size and that worked as expected.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
z.Clip.MakeInfinite()
z.DrawLine(pen1, 0, 0, Me.Size.Width, Me.Size.Height)
End Sub
pen1 and z are initialized at the start of the program as follows:
Dim z As Graphics = CreateGraphics()
Dim pen1 = New Pen(Color.Black, 3)
I also tried using different set values for the x2 and y2 but nothing managed to be drawn outside of the box apart from using Form1_paint.
It has nothing to do with the clip, and everything to do with WHEN you called CreateGraphics(). At the point that the graphics was created, the form was not fully initialized and it is using the incorrect size.
When you use CreateGraphics() or create a Pen, you should explicitly Dispose() of them as soon as you're done. This is best accomplished via a Using block:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using z As Graphics = CreateGraphics()
Using pen1 = New Pen(Color.Black, 3)
z.DrawLine(pen1, 0, 0, Me.ClientSize.Width, Me.ClientSize.Height)
End Using
End Using
End Sub
In general, though, using CreateGraphics() in this manner is almost always the INCORRECT approach. The line drawn this way is TEMPORARY. Minimize the form and restore it and the line will be gone.
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).
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()
I am currently coding a small animation. The animation starts off with a small circle moving across the screen, then the user click buttons and other small images move across the screen (I feel like describing the contents of the images, the purpose of the program, etc. would be tangential and irrelevant).
The way I am currently coding the animation is like this:
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Circle1.Left -= 10
If Counter = 16 Then
Timer.Enabled = False
Counter = 0
End If
Counter += 1
End Sub
However, the problem is that I need to use the timer to help animate the movement of multiple images. Other than creating multiple timers, is there a way of using the ticking of the timer in multiple subroutines?
You can use a list to hold all the controls you want to animate. Iterate the list in the timer event and modify the position of the controls. Here's an example how to do it.
Public Class Form1
' this list holds all controls to animate
Private controlsToAnimate As New List(Of Control)
Private random As New Random
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
' this list holds all controls to remove
Dim controlsToRemove As New List(Of Control)
For i = 0 To controlsToAnimate.Count - 1
Dim control = controlsToAnimate(i)
If control.Left < 0 Then
' this control has reached the left edge, so put it in the list to remove
controlsToRemove.Add(control)
Else
' move the control to left
control.Left -= 10
End If
Next
' remove all controls that have reached the left edge
For Each control In controlsToRemove
controlsToAnimate.Remove(control)
control.Dispose()
Next
' for debug only, display the number of controls to animate
Me.Text = controlsToAnimate.Count()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' create a new picturebox
Dim newControl As New PictureBox
With newControl
' load an image for the picturebox
.Image = Image.FromFile("D:\Pictures\abc.jpg")
' size of the picturebox
.Size = New Size(100, 100)
' picturebox appears at the right edge of the form,
' the vertical position is randomize
.Location = New Point(Me.Width - newControl.Width, random.Next(Me.Height - newControl.Height))
' stretch the image to fit the picturebox
.SizeMode = PictureBoxSizeMode.StretchImage
End With
' add the newly created control to list of controls to animate
controlsToAnimate.Add(newControl)
' add the newly created control to the form
Me.Controls.Add(newControl)
End Sub
End Class
I am making a program that constantly sends the key "{PRTSC}" and then sets PictureBox1.BackgroundImage = My.Computer.Clipboard.GetImage.
At first it works fine but after a min or two the picturebox goes blank and no error is given.
My code is:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If Not My.Computer.Clipboard.ContainsImage Then
SendKeys.Send("{PRTSC}")
Else
PictureBox1.BackgroundImage = My.Computer.Clipboard.GetImage
My.Computer.Clipboard.Clear()
End If
End Sub
I have tried:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
'SendKeys.Send("{PRTSC}")
'If My.Computer.Clipboard.ContainsImage Then PictureBox1.BackgroundImage = My.Computer.Clipboard.GetImage
Dim bounds As New Rectangle
Dim screenshot As System.Drawing.Bitmap
Dim graph As Graphics
bounds = Screen.PrimaryScreen.Bounds
screenshot = New System.Drawing.Bitmap(bounds.Width, bounds.Height) ', System.Drawing.Imaging.PixelFormat.Format32bppRgb)
graph = Graphics.FromImage(screenshot)
graph.CopyFromScreen(0, 0, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)
PictureBox1.BackgroundImage = screenshot
graph.Dispose()
'screenshot.Save("d:\\dcap.jpg", Imaging.ImageFormat.Bmp)
End Sub
But attempting to dispose the screenshot yields an instant error. I don't know why.
{PRTSC} grabs the active window, when it has focus, and the screen otherwise.
It's a good idea to disable the timer at the beginning of the tick event, and start it at the end. This prevents re-entry, and, depending on the type of timer (there is the timer control, system.timers.timer, and system.threading.timer, each of which is a little different), you may be required to restart the timer each tick event.
It's normal to assign an image to a picturebox image instead of the backgroundimage. If something in the application is assigning a bitmap to picturebox1.image or blanking picturebox1.image, it will overwrite the screen shot in picturebox1.backgroundimage.