Nested timers? Trouble with logic on multiple simultaneous timer ticks - vb.net

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

Related

Moving picturebox according to the score of the dice

I am new to visual basic and am needing some help. I am creating a board game where you have to roll a dice and depending on the side it lands on, the picture box moves accordingly. I have labels put together in a square shape making up a look alike grid that is 5 rows and 10 columns. So far, I have the part for when the player clicks the button "Rouler" they get a randomized side of the dice. I would like for each time the dice is rolled it moves along the grid accordingly to the number the dice has picked.
Public Class frm1
Dim Rand As New Random
Dim Dé(5) As Image
Private Sub btnRouler_Click(sender As Object, e As EventArgs) Handles btnRouler.Click
Dim n As Integer
n = Rand.Next(4)
PictureBoxDé.Image = Dé(n)
End Sub
Private Sub frm1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dé(0) = My.Resources._11
Dé(1) = My.Resources._2
Dé(2) = My.Resources._3
Dé(3) = My.Resources._4
Dé(4) = My.Resources._5
Dé(5) = My.Resources._6
I think you are saying you have 50 labels arranged in 10 columns and 5 rows. The De array contains the dice images.
This is one approach. Make a small class called Player. When you add a second player you will see the value of this.
You will need an array of the labels, indexes 0-49.
When you roll the die, you choose the appropriate image form the De array. Next, you clear the Player1.Token from the CurrentPosition Label. You also increase the players current position by n + 1. If he rolls a 1 (De(0)) you add one to n to move 1 space. Finally, you add a the Player1.Token to the label at the new position.
Public Class Player
Public Property Token As Image
Public Property CurrentPosition As Integer
Public Sub New(Pic As Image)
Token = Pic
CurrentPosition = 0
End Sub
End Class
Private Player1 As Player
Private Dé(5) As Image
Private LabelArray(49) As Label
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LabelArray = {Label1, Label2, Label3, Label4} 'etc.
Player1 = New Player(Image.FromFile("Dog.png"))
Dé(0) = My.Resources._11
Dé(1) = My.Resources._2
Dé(2) = My.Resources._3
Dé(3) = My.Resources._4
Dé(4) = My.Resources._5
Dé(5) = My.Resources._6
End Sub
Private Sub btnRouler_Click(sender As Object, e As EventArgs) Handles btnRouler.Click
Dim n As Integer
n = Rand.Next(6) 'Non negative integer, one less than the parameter - in this case 0 to 5
PictureBoxDé.Image = Dé(n)
Player1.CurrentPosition += n + 1
LabelArray(Player1.CurrentPosition).Image = Player1.Token
End Sub

DataGridView slow to refresh on screen

I have a program that regularly updates a few datagridviews with new data that is received via TCP. The problem I am having is that the screen refresh is quite slow. Bellow is a stripped back version of my code. This example takes 1.1s to update the screen each time the loop in StartButton_Click is iterated. How can I make this faster without reducing the amount of data that is shown?
I added a stopwatch to try and work out what lines of code were causing the biggest issue. From the tests, it seemed that the main issue was updating the datagridview cells with a new number.
I'm not sure how to make this faster as my program relies on the values being updated regularly. Is a datagridview not the right object for this application? Should i be using something else? Is there a way to get a datagridview to update faster?
Public Class Form1
Public DataTable1 As New DataTable
Private Sub Load_From(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
DataGridView1.DataSource = DataTable1
Me.Height = 700
Me.Width = 1000
DataGridView1.Location = New Point(10, 10)
DataGridView1.Width = Me.Width - 10
DataGridView1.Height = Me.Height - 10
For c As Integer = 0 To 20
DataTable1.Columns.Add("col" & c)
If DataTable1.Rows.Count = 0 Then
DataTable1.Rows.Add()
End If
DataGridView1.Columns(c).AutoSizeMode = DataGridViewAutoSizeColumnMode.None '0%
DataTable1.Rows(0).Item(c) = "col" & c
DataGridView1.Columns(c).Width = 40
'Header
DataGridView1.Rows(0).Cells(c).Style.Alignment = DataGridViewContentAlignment.MiddleCenter
DataGridView1.Rows(0).Cells(c).Style.WrapMode = DataGridViewTriState.True
DataGridView1.Rows(0).Cells(c).Style.Font = New Font("Verdana", 8, FontStyle.Bold)
'Data
DataGridView1.Columns(c).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
DataGridView1.Columns(c).DefaultCellStyle.WrapMode = DataGridViewTriState.False
DataGridView1.Columns(c).DefaultCellStyle.Font = New Font("Verdana", 8, FontStyle.Regular)
Next
For r As Integer = 1 To 25
DataTable1.Rows.Add()
Next
End Sub
Private Sub StartButton_Click(sender As Object, e As EventArgs) Handles StartButton.Click
Dim stpw As New Stopwatch
stpw.Reset()
stpw.Start()
For i As Integer = 0 To 10
Dim rand As New Random
Dim randnumber As Double = rand.Next(5, 15) / 10
UpdateDataTable(randnumber)
DataGridView1.Update()
Me.Text = i & "/100"
Next
stpw.Stop()
MsgBox(stpw.Elapsed.TotalMilliseconds)
End Sub
Private Sub UpdateDataTable(ByVal offset As Double)
For r As Integer = 1 To DataTable1.Rows.Count - 1 'loop through rows
For c As Integer = 0 To DataTable1.Columns.Count - 1 '89%
DataTable1.Rows(r).Item(c) = (r / c) * offset
Next
Next
End Sub
End Class
Edit:
I have to admit that I totally botched my original answer by erroneously believing that the call to DataGridView.Update was not needed to emulate the OP conditions. I am leaving my original text as it may be of use for someone in another situation.
A potential solution is to use a DoubleBuffered DataGridView. This can be accomplished by creating a class that inherits from DataGridView and enables DoubleBuffering.
Public Class BufferedDataGridView : Inherits DataGridView
Public Sub New()
MyBase.New()
Me.DoubleBuffered = True
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
e.Graphics.Clear(Me.BackgroundColor)
MyBase.OnPaint(e)
End Sub
End Class
Doing this yields a change in appearance in that the client area is black until something is drawn on it. To alleviate this, the class overrides the OnPaint method to draw a background.
In my testing this reduced the bench-march time from approximately 2600 ms to approximately 600 ms.
End Edit
In addition to the highly pertinent suggestions of #Visual Vincent in the comments regarding eliminating unnecessary updating, I would recommend that you use a BindingSource to encapsulate the DataTable and use that as the DataGridview.DataSource.
Private bs As New BindingSource
Private Sub Load_From(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
bs.DataSource = DataTable1
DataGridView1.DataSource = bs
This will allow you to temporary suspend change events raised through the DataTable that cause the DataGridView to repaint cells.
Private Sub UpdateDataTable(ByVal offset As Double)
' prevent each item change from raising an event that causes a redraw
bs.RaiseListChangedEvents = False
For r As Integer = 1 To DataTable1.Rows.Count - 1 'loop through rows
For c As Integer = 0 To DataTable1.Columns.Count - 1 '89%
DataTable1.Rows(r).Item(c) = (r / c) * offset
Next
Next
bs.RaiseListChangedEvents = True ' re-enable change events
bs.ResetBindings(False) ' Force bound controls to re-read list
End Sub
This way the will only repaint once to reflect all the changes to the underlying DataTable.

vb.net code reads lables wrong

Okay, so I am making a game, like cookie clicker, or in my case - http://www.silvergames.com/poop-clicker (don't ask...), so that when you click on the icon in the center, it changes the total value by adding 1. On the side, you have a picture which you click to increase the amount it generates automatically every second.
At the moment I have it like this:
The timer tics every second. If the total amount > the cost of upgrade then it shows the picture of the thing you click to upgrade.
When you click that picture -
The cost is taken away from the total amount.
It changes the amount of times you have used that upgrade by +1.
The automatic upgrades per second is changed by +1.
The Cost is increased by 10.
What is happening is that I click the icon in the middle say 5 times (very quickly) and it only comes up with a total of 3. That in itself is a problem, but the even worse problem is that it shows the picture to click, when i told it to only show when the total value was > 10 (the cost of the upgrade).
I am really confused, and any help will be much appreciated.
Thanks
SkySpear
PS. Here's the Code -
Public Class Form1
Private Sub picPoop_Click(sender As Object, e As EventArgs) Handles picPoop.Click
lblPoops.Text = lblPoops.Text + 1
End Sub
Private Sub picCursor_Click(sender As Object, e As EventArgs) Handles picCursor.Click
lblPoops.Text = lblPoops.Text - lblCursorCost.Text
lblCursorAmmount.Text = lblCursorAmmount.Text + 1
lblPoopsPerSec.Text = lblPoopsPerSec.Text + 1
lblCursorCost.Text = lblCursorCost.Text + 10
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
lblCursorAmmount.Text = 0
lblCursorCost.Text = 10
lblBabyAmmount.Text = 0
lblBabyCost.Text = 100
lblBowlAmmount.Text = 0
picCursor.Hide()
tmrSec.Start()
End Sub
Private Sub tmrSec_Tick(sender As Object, e As EventArgs) Handles tmrSec.Tick
If lblPoops.Text > lblCursorCost.Text Then picCursor.Show()
End Sub
End Class
Again, don't ask where this ridiculous idea came from, I can assure you it wasn't mine.
Your main problem with this is that in your Timer sub, you are comparing text to text. In this case, a value of "3" > "21", since text comparisons work on a char by char basis. When this happens, your pictureBox is being shown. As others suggested, you can use any of the string to numeric conversion functions in your timer event to make this work better.
A slightly better approach would be to declare some class level numeric variables that hold each individual value and displays them when needed. As an example
numPoops += 1
lblPoops.Text = numPoops
This will make sure that all math will work correctly.
You are dealing with the Value of the textboxes, not the Text in it.
You should enclose each textbox with VAL() to get its exact value as a number.
Private Sub picPoop_Click(sender As Object, e As EventArgs) Handles picPoop.Click
lblPoops.Text = VAL(lblPoops.Text) + 1
End Sub
Private Sub picCursor_Click(sender As Object, e As EventArgs) Handles picCursor.Click
lblPoops.Text = VAL(lblPoops.Text) - VAL(lblCursorCost.Text)
lblCursorAmmount.Text = VAL(lblCursorAmmount.Text) + 1
lblPoopsPerSec.Text = VAL(lblPoopsPerSec.Text) + 1
lblCursorCost.Text = VAL(lblCursorCost.Text) + 10
End Sub
Private Sub tmrSec_Tick(sender As Object, e As EventArgs) Handles tmrSec.Tick
If VAL(lblPoops.Text) > VAL(lblCursorCost.Text) Then picCursor.Show()
End Sub

Checkbox returned in Event Handler isn't one on form

I'm creating an array of checkboxes on a form dynamically; the code that creates the array looks like this:-
checkbox_array(count_of_checkboxes) = New CheckBox
if (count_of_checkboxes = 0) Then
checkbox_array(count_of_checkboxes).Top = specimen_checkbox.Top
checkbox_array(count_of_checkboxes).Left = specimen_checkbox.Left
else
checkbox_array(count_of_checkboxes).Top = checkbox_array(count_of_checkboxes - 1).Top + vertical_offset
checkbox_array(count_of_checkboxes).Left = checkbox_array(count_of_checkboxes - 1).Left + horizontal_offset
End If
my_panel.Controls.Add(checkbox_array(count_of_checkboxes))
AddHandler checkbox_array(count_of_checkboxes).MouseClick, cbxSpecimen_CheckedChanged
checkbox_array(count_of_checkboxes).Name = someValue
checkbox_array(count_of_checkboxes).Text = someValue
checkbox_array(count_of_checkboxes).Enabled = true
checkbox_array(count_of_checkboxes).Visible = true
checkbox_array(count_of_checkboxes).Show()
This works fine and dandy on one form. However, I am using the same code on a form which is derived from a base form, and running into a problem, in that the object returned in the sender parameter, although clearly a checkbox with a recognisable name, isn't any of the checkboxes in the array.
I verified this with:-
Private Sub cbxSpecimen_CheckedChanged( sender As System.Object, e As System.EventArgs) Handles cbxSpecimen.CheckedChanged
For i As Integer = 0 To checkbox_array.GetUpperBound(0) - 1
If checkbox_array(i).Equals(sender) Then
// set a breakpoint here
End If
Next i
End Sub
Can anyone shed any light on why this should work on a normal form, but not a derived-class form?
I verified this with:-
Private Sub cbxSpecimen_CheckedChanged( sender As System.Object, e As System.EventArgs)
Handles cbxSpecimen.CheckedChanged
For i As Integer = 0 To checkbox_array.GetUpperBound(0) - 1
If checkbox_array(i).Equals(sender) Then
// set a breakpoint here
End If
Next i
End Sub
Why checkbox_array.GetUpperBound(0) - 1? This will skip the last element in the array. Try:
For i As Integer = 0 To checkbox_array.GetUpperBound(0)
If checkbox_array(i).Equals(sender) Then
// set a breakpoint here
End If
Next i
Or
For i As Integer = 0 To checkbox_array.Length - 1
...
I have managed to get this to work by refilling the array of checkboxes inside the click event:-
Private Sub cbxSpecimen_CheckedChanged(sender As System.Object, e As System.EventArgs) Handles cbxSpecimen.CheckedChanged
For i As Integer = 0 To check_boxes.GetUpperBound(0)
If check_box_array(i).Name = CType(sender, CheckBox).Name And
Not check_box_array(i).Equals(sender) Then
check_box_array(i) = CType(sender, CheckBox)
End If
Next i
' do useful work
End Sub
After the check box on the form has been stuffed back into the array, it remains there (so the second invokation for the same checkbox doesn't insert into the array a second time).
This seems like a ghastly hack to me, but I'll go with it for the time being.

How to make a live score counter for Lucky 7 (vb game)

I have made a simple game, lucky 7 on visual basic using vb code. The score counter doesn't work properly, for example if I win the game once (get a 7 in one of the 3 slots), I get 10 points and the score label changes to 10. If I continue pressing the spin button and win again, the score label still stays on the number 10, and does not change to 20.
Here is the code for the spin button I wrote:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim rand = New Random
Dim slots = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Dim score = 0
For i = 0 To 2
slots(i) = rand.Next(10)
Next
Label1.Text = (slots(0).ToString)
Label2.Text = (slots(1).ToString)
Label3.Text = (slots(2).ToString)
If slots(0) = 7 Or slots(1) = 7 Or slots(2) = 7 Then
score = score + 10
Label4.Text = (score.ToString)
PictureBox1.Visible = True
Else
PictureBox1.Visible = False
End If
End Sub
Do I need to add a while loop or something similar to make the score change as many times as I win the game?
You need to move your variable declaration at class level.
At the moment, you create it when you click on your button. Therefore, each time you click, you delete the score variable and create it again.
Move the
Dim score = 0
line as follows:
'Assuming your Form is called Form1
Public Class Form1 Inherits Form
Dim score = 0
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'Your current code
End Sub
End Class
And your problem is solved.
You should probably read some documentation about scopes.
An extract about your little mistake :
If you declare a variable within a procedure, but outside of any If statement, the scope is until the End Sub or End Function. The lifetime of the variable is until the procedures ends.