I have code that makes a list of 11 circle objects and draws them to a picture box in a VB windows form. They are supposed to move across it and when they reach the end, restart at the beginning.
The circles get populated correctly, and move as they are supposed to, but after two cycles once they reach the end they disappear and don't reset to the beginning. Here is the code i am currently using.
Public Class frmContent
Private chocolatemark As New List(Of circlemark)
Public Sub frmContent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i As Integer = 0 To 10
chocolatemark.Add(New circlemark(Rnd() * 630 + 1, Rnd() * 220 + 140))
Next
End Sub
Private Sub rndGen_Tick(sender As Object, e As EventArgs) Handles rndGen.Tick
picturebox.Invalidate()
End Sub
Private Sub picturebox_Paint(sender As Object, e As PaintEventArgs) Handles picturebox.Paint
For i As Integer = 0 To 10
chocolatemark(i).update()
chocolatemark(i).draw(e)
Next
End Sub
End Class
Public Class circlemark
Private pos As Point = New Point(0, 0)
Sub New(ByVal x As Double, ByVal y As Double)
pos.X = x
pos.Y = y
End Sub
Public Sub draw(ByRef e As PaintEventArgs)
e.Graphics.DrawEllipse(Pens.Red, pos.X, pos.Y, 5, 5)
End Sub
Public Sub update()
If pos.X < 640 Then
pos.X += globalvalue.speed
End If
If pos.X > 640 Then
pos.X = 0
End If
End Sub
End Class
Public Class globalvalue
Public Shared speed As Integer = 5
End Class
Does anyone know why this might be happening?
If pos.X < 640 Then
pos.X += globalvalue.speed
End If
If pos.X > 640 Then
pos.X = 0
End If
You created a black hole for the objects, one they can never escape from. Once the value reaches 640, they'll get stuck forever. Can't get higher, can't get back to 0. It should of course be >= 640.
This problem became hard to diagnose (although the debugger can easily show you) because you used a "magic number". 640 isn't actually the size of the picture box. So you couldn't see them being stuck. Never use magic numbers. PictureBox.ClientSize.Width is the correct value to use, minus the object size. Don't hard-code that either, turns in a fleck of dust on a "retina" display.
Related
So, I am making a game for my programming class as part of my final project. I'm just in the planning and experimenting stage at the moment and I decided to get a headstart on graphics and collisions. I first made my program just by experimenting with the Graphics class VB has to offer, instead of using PictureBoxes. Alongside that, I added keyboard input to move an Image around. When I decided to add collision detection through the intersectsWith() method of the Image class, things became weird.
Basically, in my code, the "Player" entity has three different images - which change depending on which way they are facing, which is in turn determined by what key the user presses. Without any collision detection code, the movement and image changing works fine and the image moves about. However, as soon as I add collision detection the player does not move at all, only the way they face changes. This happens even if the player's Image is nowhere near the image I want to test for intersection (a dollar sign). Here's my entire code:
Public Class Form1
Enum DirectionFacing
FORWARDS
BACKWARD
LEFT
RIGHT
End Enum
' Player X position.
Dim pX As Integer = 100
' Player Y position.
Dim pY As Integer = 100
' The direction the player is facing - by default, backward.
Dim dir As DirectionFacing = DirectionFacing.BACKWARD
' The image of the player.
Dim pI As Image = My.Resources.MainCharacter_Forward
' Another image designed to test for collision detection.
Dim dI As Image = My.Resources.DollarSign
Private Sub Form1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
If (e.KeyCode = Keys.W) Then
' If they press W, move forward.
dir = DirectionFacing.FORWARDS
pI = My.Resources.MainCharacter_Forward
movePlayer(DirectionFacing.FORWARDS, 10)
ElseIf (e.KeyCode = Keys.S) Then
' If they press S, move backward.
dir = DirectionFacing.BACKWARD
pI = My.Resources.MainCharacter_Behind
movePlayer(DirectionFacing.BACKWARD, 10)
ElseIf (e.KeyCode = Keys.A) Then
' If they press A, move to the left.
pI = My.Resources.MainCharacter_Side
dir = DirectionFacing.LEFT
movePlayer(DirectionFacing.LEFT, 10)
ElseIf (e.KeyCode = Keys.D) Then
' If they press D, move to the right. To make the player face rightward,
' the image can be flipped.
Dim flipped As Image = My.Resources.MainCharacter_Side
flipped.RotateFlip(RotateFlipType.RotateNoneFlipX)
pI = flipped
dir = DirectionFacing.LEFT
movePlayer(DirectionFacing.RIGHT, 10)
End If
End Sub
' Moves the player by a certain amount AND checks for collisions.
Private Sub movePlayer(dir As DirectionFacing, amount As Integer)
If (dI.GetBounds(GraphicsUnit.Pixel).IntersectsWith(pI.GetBounds(GraphicsUnit.Pixel))) Then
Return
End If
If (dir = DirectionFacing.FORWARDS) Then
pY -= 10
ElseIf (dir = DirectionFacing.BACKWARD) Then
pY += 10
ElseIf (dir = DirectionFacing.LEFT) Then
pX -= 10
ElseIf (dir = DirectionFacing.RIGHT) Then
pX += 10
End If
End Sub
Private Sub draw(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim g As Graphics = e.Graphics()
g.DrawImage(dI, 400, 350)
g.DrawImage(pI, pX, pY)
Me.Invalidate()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.DoubleBuffered = True
End Sub
End Class
Basically, every time I press a key and want the image to move, the image doesn't move at all (even when the Player is nowhere close to the dollar sign), but the direction they are facing still changes. How can I keep the player moving and still stop the player from colliding with another image?
Well, the
If (dI.GetBounds(GraphicsUnit.Pixel).IntersectsWith(pI.GetBounds(GraphicsUnit.Pixel)))
will always return False since the GetBounds method does not return the current location of each rectangle. So they will never intersect, and your drawing scene remains the same.
So let's try to solve this problem.
Enum DirectionFacing
FORWARDS
BACKWARD
LEFT
RIGHT
End Enum
' The image of the player.
Dim pI As New Bitmap(My.Resources.MainCharacter_Forward)
' Another image designed to test for collision detection.
Dim dI As New Bitmap(My.Resources.DollarSign)
'The rectangle of the player's image.
Dim pIrect As New Rectangle(100, 100, pI.Width, pI.Height)
'The static rectangle of the collision's image.
Dim dIrect As New Rectangle(400, 350, dI.Width, dI.Height)
Now the IntersectWith function should work in the movePlayer method:
Private Sub movePlayer(dir As DirectionFacing, amount As Integer)
Dim px = pIrect.X
Dim py = pIrect.Y
Select Case dir
Case DirectionFacing.FORWARDS
py -= amount
Case DirectionFacing.BACKWARD
py += amount
Case DirectionFacing.LEFT
px -= amount
Case DirectionFacing.RIGHT
px += amount
End Select
If Not New Rectangle(px, py, pI.Width, pI.Height).IntersectsWith(dIrect) Then
pIrect = New Rectangle(px, py, pI.Width, pI.Height)
Invalidate()
End If
End Sub
Note that, both px and py variables are now locals because we already have pIrect which includes the currect x and y. We replaced the If statement with Select Case as a better approach I believe. We created a new rectangle to check any possible collision, if not, then we update our pIrect and refresh the drawing.
Besides moving the image through the W S A D keys, you also can make use of the ← ↑ → ↓ keys. To intercept them in the KeyDown event, just override the IsInputKey function as follow:
Protected Overrides Function IsInputKey(keyData As Keys) As Boolean
Select Case keyData And Keys.KeyCode
Case Keys.Left, Keys.Up, Keys.Right, Keys.Down
Return True
Case Else
Return MyBase.IsInputKey(keyData)
End Select
End Function
Thus, the KeyDown event:
Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown
Select Case e.KeyCode
Case Keys.W, Keys.Up
pI?.Dispose()
pI = New Bitmap(My.Resources.MainCharacter_Forward)
movePlayer(DirectionFacing.FORWARDS, 10)
Case Keys.S, Keys.Down
pI?.Dispose()
pI = New Bitmap(My.Resources.MainCharacter_Behind)
movePlayer(DirectionFacing.BACKWARD, 10)
Case Keys.A, Keys.Left
pI?.Dispose()
pI = New Bitmap(My.Resources.MainCharacter_Side)
movePlayer(DirectionFacing.LEFT, 10)
Case Keys.D, Keys.Right
pI?.Dispose()
pI = New Bitmap(My.Resources.MainCharacter_Side)
pI.RotateFlip(RotateFlipType.RotateNoneFlipX)
movePlayer(DirectionFacing.RIGHT, 10)
End Select
End Sub
Again, we replaced the If Then Else statement with Select Case. If you are not supposed to do that, I believe it will be easy for you to revert and use If e.KeyCode = Keys.W OrElse e.KeyCode = Keys.Up Then ....
The Paint routine:
Private Sub draw(sender As Object, e As PaintEventArgs) Handles Me.Paint
Dim g As Graphics = e.Graphics()
g.DrawImage(dI, dIrect)
g.DrawImage(pI, pIrect)
End Sub
Finally, don't forget to clean up:
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
pI?.Dispose()
dI?.Dispose()
End Sub
Good luck
I'm coding Conway's game of life. My grid is entirely in a picture box but when I load the form the back button in the upper right is completely white. Refreshing the form fixes the button but makes it incredibly laggy. Every other button shows up fine, just the back button is broken. How can I fix this?
Option Strict On
Public Class frmGame
' Declaring public variables
Public bmp As Bitmap
Public G As Graphics
Public WithEvents speed As Timer
Public grid(50, 40) As Boolean
Public input(50, 40) As Boolean
Public intGens As Integer = 0
Public change As Boolean = False
Public P As New Pen(Color.Black)
Private Sub picGrid_Paint(sender As Object, e As PaintEventArgs) Handles picGrid.Paint
' Loads bitmap, graphics, etc and prepares to begin simulation
' Creates graphics device
bmp = New Bitmap(600, 480)[enter image description here][1]
picGrid.Image = bmp
G = Graphics.FromImage(bmp)
' Defining variables for grid
Dim x As Integer = 0
Dim y As Integer = 0
' Draws grid
For y = 0 To 480
For x = 0 To 600
G.DrawRectangle(pen:=P, x:=x, y:=y, width:=12, height:=12)
x += 12
Next
x = 0
y += 12
Next
End Sub
Private Sub btnBack_Click(sender As Object, e As EventArgs) Handles btnBack.Click
' Hides rules form, shows main menu form
frmMainMenu.Show()
Me.Hide()
End Sub
Private Sub frmGame_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
' Frees memory when form is closed
G.Dispose()
bmp.Dispose()
speed.Dispose()
End Sub
End Class
I'm not exactly sure do I understand your problem, nor I can recreate it with code provided.
But I can recommend some thing's that can help you.
First of all do not use Control.Refresh() while working with drawing, use instead Control.Invalidate() & Control.Update(), that may fix lag issue.
Do you relay need 2 forms (that i understand is the root of problem)?
Instead of hiding form, hide PictureBox (.Visible = False) and show other controls you need at that moment.
Below is code for a simple voting system I am coding.
Public Class Form1
Dim winner As String
Dim maxVotes As Integer
Dim votes() As String
Dim index As String
Dim candidates As String
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
If Not isValidInput(txtNewCandidate.Text) Then
Exit Sub
End If
lstCandidates.Items.Add(txtNewCandidate.Text)
txtNewCandidate.Clear()
txtNewCandidate.Focus()
ReDim Preserve votes(index)
index += 1
End Sub
Private Function isValidInput(ByRef firstName As String) As Boolean
If IsNumeric(txtNewCandidate.Text) Or txtNewCandidate.Text = "" Then
MsgBox("Please input a valid candidate name.")
txtNewCandidate.Focus()
Return False
Else
Return True
End If
End Function
Private Sub btnTally_Click(sender As Object, e As EventArgs) Handles btnTally.Click
lstTallies.Visible = True
lblTally.Visible = True
For i = 0 To lstCandidates.Items.Count - 1
lstTallies.Items.Add(lstCandidates.Items(i).ToString & " - " & votes(i))
Next
End Sub
Private Sub lstCandidates_DoubleClick(sender As Object, e As EventArgs) Handles lstCandidates.DoubleClick
If lstCandidates.SelectedIndex = -1 Then
MsgBox("Select a candidate by double-clicking")
End If
votes(lstCandidates.SelectedIndex) += 1
MsgBox("Vote Tallied")
End Sub
Private Sub pbxWinner_Click(sender As Object, e As EventArgs) Handles pbxWinner.Click
End Sub
End Class
The voter must double click on their choice of candidate in the first list box. The user then tallies the votes by clicking on a button and a second list box will appear with the votes per candidate.
Now I need to display the winner (or winners, if there is a tie) in a picture box, pbxWinner. I am not sure how to accomplish this. Any clues?
Here is what i am trying to do, though the code below doesn't work.
Private Function candidateWinner(ByRef winner As String) As Boolean
For i As Integer = 0 To lstCandidates.SelectedIndex - 1
If votes(i) > maxVotes Then
maxVotes += 1
End If
Next
g = pbxWinner.CreateGraphics
g.TranslateTransform(10.0F, 0.0F)
g.DrawString(winner, New Font("Arial", 7, FontStyle.Regular), Brushes.DarkBlue, New PointF(0, 0))
Return True
End Function
Your code is actually working fine for an initial paint, but when the picture box image doesn't have its own bitmap set, a number of events can repaint its graphics behind the scenes(even as simple as minimizing/mazimizing the form, and a whole bunch of other ones), so in effect your text seems to never appear at all or disappear almost instantly when in reality it's probable getting repainted. To fix this, use a bitmap for the graphics object's reference, paint the bitmap's graphics, and then assign the bitmap to the picturebox's image property. This will make the image persistent...give this code a try in your candidateWinner function after the for loop:
Dim bmp As New Bitmap(pbxWinner.Width, pbxWinner.Height)
Dim g As Graphics = Graphics.FromImage(bmp)
g.TranslateTransform(10.0F, 0.0F)
g.DrawString(winner, New Font("arial", 7, FontStyle.Regular), Brushes.DarkBlue, 0, 0)
pbxWinner.Image = bmp
...If you still aren't seeing text, make sure the winner string has the correct value set, I tested this code and it showed my test string correctly
Edit for Comment:
That's because of the logic you're using to calculate the winner...you are just checking to see if the currently selected candidate's vote count is higher than maxVotes and then incrementing the max by 1. If you wanted to stick with that sort of logic for picking the winner, you would want to iterate through ALL of the candidates(not just those from index 0 to the currently selected one), and if their vote count is higher than the max, then set the max EQUAL to their vote count. Then the next candidate in the loop will have their count checked against the previous max. However, tracking the winner could be done a lot easier if you just use a dictionary since you are allowing candidates to be added, and you must change your "winner" logic to actually check who has the most votes out of everyone entered. A bare bones example of that would look like this:
Dim dctTally As Dictionary(Of String, Integer)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dctTally = New Dictionary(Of String, Integer)
End Sub
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
dctTally.Add(txtNewCandidate.Text, 0)
lstCandidates.Items.Add(txtNewCandidate.Text)
End Sub
Private Sub lstCandidates_DoubleClick(sender As Object, e As EventArgs) Handles lstCandidates.DoubleClick
dctTally(lstCandidates.text) += 1
End Sub
Private Sub pbxWinner_Click(sender As Object, e As EventArgs) Handles pbxWinner.Click
Dim winner = dctTally.Aggregate(Function(l, r) If(l.Value > r.Value, l, r)).Key
Dim bmp As New Bitmap(pbxWinner.Width, pbxWinner.Height)
Dim g As Graphics = Graphics.FromImage(bmp)
g.TranslateTransform(10.0F, 0.0F)
g.DrawString(winner, New Font("arial", 7, FontStyle.Regular), Brushes.DarkBlue, 0, 0)
pbxWinner.Image = bmp
End Sub
This way, the program allows as many names as you want to be added to the candidates list, and will add a vote count to their name each time their name is double-clicked on. Then, when your winner pixturebox is clicked, it will find the dictionary with the highest vote count and display their name in the winner-box.
You can try this to draw the winners:
Private Sub candidateWinner()
Dim y As Single = 0
maxVotes = votes.Select(Function(x) Convert.ToInt32(x)).Max()
For i = 0 To UBound(votes)
If votes(i) = maxVotes.ToString() Then
g = pbxWinner.CreateGraphics
g.TranslateTransform(10.0F, 0.0F)
g.DrawString(lstCandidates.Items(i).ToString(), New Font("Arial", 7, FontStyle.Regular), Brushes.DarkBlue, New PointF(0, y))
y += 10
g.Dispose()
End If
Next
End Sub
I'm trying to create a simple visual example and the first step is having a column of boxes (panels) move across the screen. So far I've accomplished that, but I'm also attempting to have each panel blink a few times, individually, while moving. The effect should be a type of 'round robin' loop where the first panel blinks a few times, then the second, then the third, etc, etc and repeat.
I'm quite new to VB and so far I've only been able to successfully make either only one panel blink or all of the panels blink, not each one individually. Here's my code so far:
Public Class Form1
Public ticks As Integer
Public p(4) As Panel
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
p(0) = Panel1
p(1) = Panel2
p(2) = Panel3
p(3) = Panel4
p(4) = Panel5
ticks = 0
End Sub
Private Sub tmr1_Tick(sender As Object, e As System.EventArgs) Handles tmr1.Tick
Dim i As Integer
If ticks = 1 Then
For i = 0 To 4
Dim randomValue = Rnd()
p(i).Top = 50 + 75 * i
p(i).Left = randomValue * 120
Next
ElseIf ticks > 30 Then
ticks = 0
Else
For i = 0 To 4
p(i).Left += 20
Next
End If
ticks += 1
End Sub
Private Sub tmr2_Tick(sender As System.Object, e As System.EventArgs) Handles tmr2.Tick
Dim i As Integer
For i = 0 To 4 'all of the panels blink at the same time..
If p(i).Visible = False Then
p(i).Visible = True
ElseIf p(i).Visible = True Then
p(i).Visible = False
End If
Next
End Sub
End Class
As of right now, all of the panels blink while moving across the screen in random locations, I'm assuming this is because the for loop responsible for the blinking is nested within the ticking timer, so for each tick it runs through the loop fully.
I'm a little stumped on what should be some very simple logic, but please bear with me as I am a novice.
Thank you for any and all help!
If I understand what you want, this would do it. They all blink now because they are all in the loop that occurs with each tick, this example changes each one by it's index in the array, and the index variable must be class level to retain it's value between ticks.
Private index As Integer
Private Sub tmr2_Tick(sender As System.Object, e As System.EventArgs) Handles tmr2.Tick
p(index).Visible = Not p(index).Visible
If index = 4 Then
index = 0
Else
index += 1
End If
End Sub
I have tried to do this both with GDI+/Invalidate and by using a Line Shape Control. In both cases the memory spirals out of control. To demonstrate, create a windows form application with a timer object which is set to 100ms and enabled and use the following code:
Public Class Form1
Private Y As Integer
Private intDirection As Integer = 1
Private Sub timTest_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles timTest.Tick
Me.Invalidate()
End Sub
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim g As Graphics = Me.CreateGraphics
Dim myPen As New Pen(Color.Black)
myPen.Width = 1
g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
g.DrawLine(myPen, 10, 10, 200, Y)
Y += intDirection
If Y > Me.Height - 20 Then intDirection = -1
If Y < 0 Then intDirection = 1
g.Dispose()
myPen.Dispose()
End Sub
End Class
So the code above causes a memory leak as the line moves. I think the reason is that there are unmanaged bitmaps created behind the scenes to paint the form which are not being released because on the managed side it is just a pointer.
If I add the following code at the start of the paint function
Dim intAlloc As Integer = Me.Width * Me.Height * 16
GC.AddMemoryPressure(intAlloc)
and at the end of the function I call
GC.RemoveMemoryPressure(intAlloc)
The memory utilization grows a little and shrinks a little but never grows out of control. The AddMemoryPressure and RemoveMemoryPressure seems to alert the GC that it needs to run. Is there a better way to do this or is this correct? The code above is just a simplification for example purposes to get to the root of a problem I have in a larger component with several moving lines. Also is this the best way to calculate the proper value to place in AddMemoryPressure?