Trying to assign pictures to PictureBoxes in VB - vb.net

I am trying to create a simple game, and first it needs to randomly load 16 PictureBoxes with images. I am not sure where the problem lies in this.
Public Class Form1
Private picArrows() As PictureBox = {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
Private Sub btnNew_Click(sender As Object, e As EventArgs) Handles btnNew.Click
'starts a new game
'declares RNG
Dim randGen As New Random
'uses RNG to determine arrow placement
For intPicBox As Integer = 0 To 15
Select Case randGen.Next(1, 5)
Case 1
picArrows(intPicBox).Image = My.Resources.Up
Case 2
picArrows(intPicBox).Image = My.Resources.Right
Case 3
picArrows(intPicBox).Image = My.Resources.Down
Case 4
picArrows(intPicBox).Image = My.Resources.Left
End Select
Next
End Sub
End Class
I get a NullReferenceException error on the line after Case X. Anyone know what I'm doing wrong?

I get a NullReferenceException error on the line after Case X
You cannot initialize your array like this:
Public Class Form1
Private picArrows() As PictureBox = {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
The Form has not been initialized yet, so it and all the controls on it have not been created yet. As a result, all those control references are going to be Nothing, leaving you with an array full of Nothings. The result is a NullReferenceException because Nothing does not have an Image property.
You can declare the array there, but you can only initialize it after the form's constructor runs (Sub New). Form Load is a good place:
Public Class Form1
Private picArrows As PictureBox()
' for best results you should use the same RNG over and over too:
Private randGen As New Random()
...
Private Sub Form_Load(....
picArrows = New PictureBox() {pic11, pic12, pic13, pic14,
pic21, pic22, pic23, pic24,
pic31, pic32, pic33, pic34,
pic41, pic42, pic43, pic44}
See also NullReference Exception in Visual Basic

Slightly different arrangement without the companion array:
Private Sub btnNew_Click(sender As Object, e As EventArgs) Handles btnNew.Click
With New Random
For col = 1 To 4
For row = 1 To 4
CType(Controls(String.Format("pic{0}{1}", col, row)), PictureBox).Image = {My.Resources.Up, My.Resources.Right, My.Resources.Down, My.Resources.Left}(.Next(0, 4))
Next
Next
End With
End Sub

Related

How to Remove a control from another Sub than the one where the control was initially created in VB .NET

I have written some code to create a PictureBox every time the code runs, which works fine.
Public Sub BtnHit_Click(sender As Object, e As EventArgs) Handles BtnHit.Click
Dim PicBoxNewCard As New PictureBox
PicBoxNewCard.Width = 114
PicBoxNewCard.Height = 166
PicBoxNewCard.SizeMode = PictureBoxSizeMode.Zoom
DrawCard(PicBoxNewCard)
Me.Controls.Add(PicBoxNewCard)
PicBoxNewCard.Location = New Point((257 + (57 * DrawnCardCounter)), 349)
But I want to be able to delete these created PictureBoxes by pressing a button, which would be in a different sub to the one that creates the Boxes.
I have googled around and have found references to creating Classes, Panels etc and have not had any success. I have found the exact code that I need to make it work, (Me.Controls.Remove(PicBoxNewCard)) but it only seems to work when executed in the same Sub.
Set the Name or Tag property and use that later when searching for the PictureBox to remove.
PicBoxNewCard.Name = $"Card{DrawnCardCounter}"
Private Sub RemoveButton_Click(sender As Object, e As EventArgs) Handles RemoveButton.Click
RemoveCardPicBox(10)
End Sub
Private Sub RemoveCardPicBox(CardNumber As Integer)
Dim delpicBox As PictureBox = Me.Controls.OfType(Of PictureBox).Where(Function(x) x.Name = $"Card{CardNumber}").FirstOrDefault
If Not delpicBox Is Nothing Then
Me.Controls.Remove(delpicBox)
End If
End Sub

How to add infinite components when a button is clicked

I have a social media WinForm. I have a function that basically makes a new picture box when a button is clicked
Public Sub NewPost()
picture as new picturebox
picture.Width = 208
picture.Height = 264
picture.Image = Form2.PictureBox1.Image
picture.Location = New Point(258, 60)
End Sub
The thing is it only generates 1 new picture box because I have to make a new variable each time I want to add a picturebox, and eachtime I have to have a new name. I know my question Is a bit confusing but help would be nice thanks
If you want to trap events for your dynamic PictureBoxes, then you'll have to abandon the WithEvents model and move to using AddHandler.
Here's a quick example where the name of the PictureBox is displayed when it is clicked. Note that I am not setting a Location since they are being added to a FlowLayoutPanel which takes care of the placement for you:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
NewPost()
End Sub
Public Sub NewPost()
Dim picture As New PictureBox
picture.Width = 208
picture.Height = 264
picture.BorderStyle = BorderStyle.FixedSingle
' ...etc...
Dim index As Integer = FlowLayoutPanel1.Controls.Count + 1
picture.Name = "pb" & index
AddHandler picture.Click, AddressOf picture_Click
FlowLayoutPanel1.Controls.Add(picture)
End Sub
Private Sub picture_Click(sender As Object, e As EventArgs)
Dim pb As PictureBox = DirectCast(sender, PictureBox)
Debug.Print(pb.Name)
End Sub
End Class
because I have to make a new variable each time
Not necessarily. You just want to keep a reference to the object. That reference doesn't need to be its own variable, it can just as easily be an element in a list. For example, suppose on your form you have a list of PictureBox objects as a class-level member:
Dim pictureBoxes As New List(Of PictureBox)()
Then in your method you can just add to that list:
Public Sub NewPost()
Dim pictureBox As New PictureBox
pictureBox.Width = 208
pictureBox.Height = 264
pictureBox.Image = Form2.PictureBox1.Image
pictureBox.Location = New Point(258, 60)
Me.pictureBoxes.Add(pictureBox)
End Sub
In this case the pictureBox variable is local to the NewPost method and gets re-created each time. But pictureBoxes is a class-level member and keeps track of the growing list of PictureBox objects that you're creating.
You can use a for while loop to create n number of objects
You can use the existing ControlCollection
Public Function NewPost() As String
Dim picture As New PictureBox
'your code
picture.Name = "Pb" & Form2.Controls.OfType(Of PictureBox).Count
Form2.Controls.Add(picture)
Return picture.Name
End Function
then you can retrive it
DirectCast(Form2.Controls(NewPost), PictureBox).Image = Form2.PictureBox1.Image
'OR
DirectCast(Form2.Controls("Pb12"), PictureBox).Image = Form2.PictureBox1.Image

Sum won't increment in visual basics 2013

I'm having this codes at all forms so I could have a sum of score in my quiz. But when the score form shows up in the end, it shows 0 and is more likely not incrementing when i have a wright or wrong answer. I'm sorry its my first time to make in VB basics. Wish someone could help.
Public Class Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles NextButton1.Click
If Option2.Checked Then
Score.ScoreRight.Text = Score.ScoreRight.Text + 1
Dim Form2 As New Form3
Form3.Show()
Me.Hide()
Else
Score.ScoreWrong.Text = Score.ScoreWrong.Text + 1
Dim Form2 As New Form3
Form3.Show()
Me.Hide()
End If
End Sub
End Class
I'm assuming that Score is a form and that Score.Scoreright and score.scorewrong are textboxes in that form
try this maybe
Public class Score
Dim withevents Form2instance as new Form2
Dim withevents Form3instance as new Form3
Dim rightAnsweres as integer = 0
Dim wronganswers as integer = 0
Public sub Updateresults()
Scoreright.text = rightansweres
ScoreWrong.text = wronganswers
End Sub
Public sub Form2Instance_QuestionAnswered()Handles Form2Instance.QuestionAnswered
if Form2Instance.AnsweredCorrectly = true then
rightansweres = rightansweres+1
else
wronganswers = wronganswers + 1
end if
Form2instance.Hide
UpdateResults()
Form3instance.show
end sub
end class
Now in your form2 and form3 classes you need an event and you need to raise the event when the question is answered.
Public Class Form2
Public event QuestionAnswered()
Property AnsweredCorrectly as integer = False
Sub RunThisAfterYouHaveIndicatedWhetherOrNotTheAnswerWasCorrect()
RaiseEvent QuestionAnswered()
End Sub
end class
Now to explain
You are trying to call a routine inside of a general class type and expecting the results to be updated inside of an active instance of that class. Or so it seems.
You have to have some kind of reference to the instance of the class that you're trying to update.
In this example, The class you're trying to update has a reference to the forms that it needs information from. It receives the information by waiting for the form to raise an event and then handles it's business.
Is Option2 a checkbox? If so, i think you need to use the .checkstatus property of the checkbox instead of .checked
If Option2.checkedstate = checkedstate.checked then

New Multi-threading not working

I'm trying to create a thread so when I click a button it creates a new PictureBox from a class, this is how far I've got but nothing comes up on the screen at all.
Form1 code:
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
pgClass.thread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private trd As Thread
Public Sub thread()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = False
trd.Start()
End Sub
Private Sub NewUIThread()
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
Application.DoEvents()
Threading.Thread.Sleep(100)
Next
counter += 1
End Sub
End Class
I have posted something similar before on here but it was different, the pictureBoxes were showing on the screen but I was trying to get them to move at the same time but they wouldn't move, they only moved one at a time. The question that I asked before was this Multi threading classes not working correctly
I made a few assumptions for this answer so it may not work for you out of the box but I think it will put you on the right track without using any Thread.Sleep calls because I personally don't like building intentional slows to my apps but that's a personal preference really.
So For my example I just used a bunch of textboxes because I didn't have any pictures handy to fiddle with. But basically to get it so that the user can still interact with the program while the moving is happening I used a background worker thread that is started by the user and once its started it moves the textboxes down the form until the user tells it to stop or it hits an arbitrary boundary that I made up. So in theory the start would be the space bar in your app and my stop would be adding another control to the collection. For your stuff you will want to lock the collection before you add anything and while you are updating the positions but that is up to your discretion.
So the meat and potatoes:
in the designer of the form I had three buttons, btnGo, btnStop and btnReset. The code below handles the click event on those buttons so you will need to create those before this will work.
Public Class Move_Test
'Flag to tell the program whether to continue or to stop the textboxes where they are at that moment.
Private blnStop As Boolean = False
'Worker to do all the calculations in the background
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker
'Controls to be moved.
Private lstTextBoxes As List(Of TextBox)
'Dictionary to hold the y positions of the textboxes.
Private dtnPositions As Dictionary(Of Integer, Integer)
Public Sub New()
' Default code. Must be present for VB.NET forms when overwriting the default constructor.
InitializeComponent()
' Here I instantiate all the pieces. The background worker to do the adjustments to the position collection, the list of textboxes to be placed and moved around the form
' and the dictionary of positions to be used by the background worker thread and UI thread to move the textboxes(because in VB.NET you can not adjust controls created on the UI thread from a background thread.
bgWorker = New System.ComponentModel.BackgroundWorker()
Me.lstTextBoxes = New List(Of TextBox)
Me.dtnPositions = New Dictionary(Of Integer, Integer)
For i As Integer = 0 To 10
Dim t As New TextBox()
t.Name = "txt" & i
t.Text = "Textbox " & i
'I used the tag to hold the ID of the textbox that coorelated to the correct position in the dictionary,
' technically you could use the same position for all of them for this example but if you want to make the things move at different speeds
' you will need to keep track of each individually and this would allow you to do it.
t.Tag = i
dtnPositions.Add(i, 10)
'Dynamically position the controls on the form, I used 9 textboxes so i spaced them evenly across the form(divide by 10 to account for the width of the 9th text box).
t.Location = New System.Drawing.Point(((Me.Size.Width / 10) * i) + 10, dtnPositions(i))
Me.lstTextBoxes.Add(t)
Next
'This just adds the controls to the form dynamically
For Each r In Me.lstTextBoxes
Me.Controls.Add(r)
Next
End Sub
Private Sub Move_Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'Don't need to do anything here. Placeholder
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Try
If Not bgWorker.IsBusy Then
'User starts the movement.
bgWorker.RunWorkerAsync()
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
Try
'Reset the positions and everything else on the form for the next time through
' I don't set the blnStop value to true in here because it looked cooler to keep reseting the textboxes
' and have them jump to the top of the form and keep scrolling on their own...
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, 10)
Next
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) = 10
Next
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub bgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'This is where we do all the work.
' For this test app all its doing is scrolling through each value in the dictionary and incrementing the value
' You could make the dictionary hold a custom class and have them throttle themselves using variables on the class(or maybe travel at an angle?)
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) += 1
Next
Catch ex As Exception
blnStop = True
End Try
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Try
'Once the background worker is done updating the positions this function scrolls through the textboxes and assigns them their new positions.
' We have to do it in this event because we don't have access to the textboxes on the backgroun thread.
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, dtnPositions(CInt(r.Tag)))
Next
'use linq to find any textboxes whose position is beyond the threshhold that signifies they are down far enough.
' I chose the number 100 arbitrarily but it could really be anything.
Dim temp = From r In Me.lstTextBoxes Where r.Location.Y > (Me.Size.Height - 100)
'If we found any textboxes beyond our threshold then we set the top boolean
If temp IsNot Nothing AndAlso temp.Count > 0 Then
Me.blnStop = True
End If
'If we don't want to stop yet we fire off the background worker again and let the code go otherwise we set the stop boolean to false without firing the background worker
' so we will be all set to reset and go again if the user clicks those buttons.
If Not Me.blnStop Then
bgWorker.RunWorkerAsync()
Else
Me.blnStop = False
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Try
'The user clicked the stop button so we set the boolean and let the bgWorker_RunWorkerCompleted handle the rest.
Me.blnStop = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
End Class
Theres a lot of code there but a lot of it is comments and I tried to be as clear as possible so they are probably a little long winded. But you should be able to plop that code on a new form and it would work without any changes. I had the form size quite large (1166 x 633). So I think that's when it works best but any size should work(smaller forms will just be more cluttered).
Let me know if this doesn't work for your application.
This is a problem that is well suited to async/await. Await allows you to pause your code to handle other events for a specific period of time..
Private Async Function Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) As Task Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
await pgClass.NewUIThread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private Async Function NewUIThread() As Task
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
await Task.Delay(100) 'The await state machine pauses your code here in a similar way to application.doevents() until the sleep has completed.
Next
counter += 1
End Sub
End Class

How can I respond to events raised by programmatically created UI elements?

I'm creating a board game for a piece of coursework. For the board, I'm using some nested For loops running through a 2D array to generate a "Space" object at each square.
The Space object contains a picturebox and some data about that space.
How can I handle events caused by clicking on the generated picturebox without having to hard-code it for each space?
I noticed this question seems to address this, but it's in C# and I couldn't translate it to VB.Net.
Edit:
This is how the board is generated
Dim board(23, 24) As Space
Private Sub GenerateBoard()
Dim spaceSize As New Size(30, 30)
Dim spaceLocation As New Point
Dim validity As Boolean
For Y = 0 To 24
For X = 0 To 23
spaceLocation.X = 6 + (31 * X)
spaceLocation.Y = 6 + (31 * Y)
If validSpaces(Y).Contains(X + 1) Then
validity = True
Else
validity = False
End If
board(X, Y) = New Space(validity, spaceSize, spaceLocation)
Me.Controls.Add(board(X, Y).imageBox)
board(X, Y).imageBox.BackColor = Color.Transparent
board(X, Y).imageBox.BringToFront()
Next
Next
End Sub
Space Class:
Public Class Space
Dim _active As Boolean
Dim _imageBox As PictureBox
Public Sub New(ByVal activeInput As Boolean, ByVal size As Size, ByVal location As Point)
_active = activeInput
_imageBox = New PictureBox
With _imageBox
.Size = size
.Location = location
.Visible = False
End With
End Sub
Property active As Boolean
Get
Return _active
End Get
Set(value As Boolean)
_active = value
End Set
End Property
Property imageBox As PictureBox
Get
Return _imageBox
End Get
Set(value As PictureBox)
_imageBox = value
End Set
End Property
Public Sub highlight()
With _imageBox
.Image = My.Resources.Highlighted_slab
.Visible = True
End With
End Sub
End Class
First all controls created by designer(textbox, label...) a generated by code too, but VisualStudio write this for you. If you open Designer file(yourForm.Designer.vb), then you can see all code how to generate a controls.
If you want a create event handler for your pictureBox , then:
//Initialize control
Private WithEvents _imageBox as PictureBox
Then create a event handler method:
Private Sub imageBox_Click(sender as Object, e as EventArgs)
//Your code
End Sub
Then in VB.NET you can assign a Event handler to the Event in two ways
first: In class constructor after you created a pictureBox( New PictureBox()) add
AddHandler Me._imageBox, AddressOf Me.imageBox_Click
second: On line we you created a event handler add next:
Private Sub imageBox_Click(sender as Object, e as EventArgs) Handles _imageBox.Click
//Your code
End Sub
And remember add your pictureBox to form controls YourForm.Controls.Add(spaceInstance.ImageBox)