Working with too many PictureBoxes, any way to use a For? - vb.net

Warning : Im completely new to VB.net and only know the most basic form of programming, maybe even less
Visual Basic 2010 Express
I have declared an Array Equipa(x,y) as integer.
I have 30 PictureBoxes ( PictureBox1 to PictureBox30)
In my mind, I assigned each PictureBox to a X and Y in the Array.
At load form, I want to change the image of PictureBox5 to PictureBox25.
For this I copy pasted 20 times PictureBox(x).Image = My.Resources.GreyHexagon
I would like to have a Loop that can do this without me copy pasting so much.
During the program, clicking on PictureBox5, for example, changes it to a different image, depending on the value, of the Array declared.
If Equipa(0, 0) = 0 Then
PictureBox4.Image = My.Resources.GreyHexagon
ElseIf Equipa(0, 0) = 1 Then
PictureBox4.Image = My.Resources.BlueHexagon
Else
PictureBox4.Image = My.Resources.RedHexagon
End If
The problem, again, is I have to repeat this code, for every Array position, since each PictureBox is assigned to each position.
So what I need is a Loop that can go through each PictureBox, I'm not asking you to make that code, I just don't know how to go through each PictureBox, for example PictureBox5 to PictureBox25.
PS . The following code changes EVERY PictureBox to the Image I wan't. But I do not understand any of this code, therefor can't change it to only go through PictureBox5 to PictureBox25.
Me.SuspendLayout()
For Each box As PictureBox In Me.Controls.OfType(Of PictureBox)()
box.Image = My.Resources.RedHexagon2
Next box
Me.ResumeLayout()

What I tried once before: In the declarations,
Dim pic(29) As PictureBox
And then in Public Sub New, I wrote picarray() and then made a Private Sub picarray. In this sub I dimmed each picture to the array
pic(0) = picturebox1
pic(1) = picturebox2
pic(2) = picturebox3
...
pic(29) = picturebox30
In my case, I wanted to move all 30 at once, and I had a timer, so I did:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _Timer1.Tick
For i = 0 To 29
pic(i).Left = pic(i).Left + 3
Next
End Sub
To move them all to the right at once. I used the same way to move them back if the last one on the left hits the form's edge.

Me.SuspendLayout()
For Each box As PictureBox In Me.Controls.OfType(Of PictureBox)()
box.Image = SomeNewPicture
Next box
Me.ResumeLayout()
If you want to be able to look at specific images, you can still just say things like PictureBox4.Image = SomeNewImage. But if you really want to access them by index, first define this variable as a member of the form class:
Private PictureBoxes() As PictureBox
And then do this in your form's Load event:
PictureBoxes = Me.Controls.OfType(Of PictureBox)().ToArray()
Now you can reference each PictureBox by index:
PictureBoxes(4).Image = SomeNewPicture
PictureBoxes(10).Image = SomeOtherNewPicture
You could also do something like this:
Me.SuspendLayout()
For i As Integer = 5 to 25
PicturesBoxes(i) = My.Resources.RedHexagon2
Next i
Me.ResumeLayout()
Finally, to address the If/Then code in your edited question, I'd do something like this:
'Note this Array shorthand requires a more recent version of Visual Studio
' I used it for brevity in the response. You should be able to convert it on your own
Dim Images() As Image = {My.Resources.GreyHexagon, My.Resources.BlueHexagon, My.Resources.RedHexagon}
Dim ImageIndex As Integer = Equipa(0, 0)
If ImageIndex < 0 OrElse ImageIndex > 1 Then ImageIndex = 2
PictureBox4.Image = Images(ImageIndex)

Related

Can I use variables to control which PictureBox I am using?

Is there a way that I can use a variable to control which PictureBox I am using in Visual Basic?
I.e.:
CurrentNumber = 1
PictureBox(CurrentNumber).backcolour = backcolour
You can use the Me.Controls(String) indexer. It lets you specify the name (as a string) of the control you want to access, thus you can dynamically access a picture box by concatenating the string "PictureBox" with a number.
Dim TargetPictureBox As PictureBox = TryCast(Me.Controls("PictureBox" & CurrentNumber), PictureBox)
'Verifying that the control exists and that it was indeed a PictureBox.
If TargetPictureBox IsNot Nothing Then
TargetPictureBox.BackColor = Color.Red
End If
Alternatively, to save processing power by avoiding looping through the entire control collection every time you can call the OfType() extension on Me.Controls, storing the result in an array sorted by the controls' names. That way it'd only have to iterate the control collection once.
'Class level - outside any methods (subs or functions).
Dim PictureBoxes As PictureBox() = Nothing
'Doesn't necessarily have to be done in a button, it's just an example.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If PictureBoxes Is Nothing Then
PictureBoxes = Me.Controls.OfType(Of PictureBox).OrderBy(Function(p As PictureBox) p.Name).ToArray()
End If
'NOTE: CurrentNumber - 1 is necessary when using an array!
PictureBoxes(CurrentNumber - 1).BackColor = Color.Red
End Sub
NOTE: This solution will only work properly if all your picture boxes are named "PictureBox1", "PictureBox2", etc. If you suddenly skip a number ("PictureBox3", "PictureBox5", "PictureBox6") then PictureBoxes(CurrentNumber - 1) for CurrentNumber = 5 would return PictureBox6 rather than PictureBox5.
What you really should do is create a PictureBox() and use that to reference your picture boxes via an index.
The best way to build your array is to create a method that builds the array from the references created by the designer. This lets you continue to use the designer to create your controls and it makes your code check for deleted controls at design-time. Using Me.Controls(...) suffers from run-time errors if controls you are looking for have been deleted.
Here's the code you need:
Private _PictureBoxes As PictureBox() = Nothing
Sub AssignPictureBoxesArray
_PictureBoxes = {PictureBox1, PictureBox2, PictureBox3}
End Sub
Then you access them like this:
Sub SomeMethod
Dim CurrentNumber = 1
Dim PictureBox = _PictureBoxes(CurrentNumber - 1)
PictureBox.BackColor = System.Drawing.Color.Red
End Sub

On button click fill next picture box

I'm writing a little program in VB and I'm stuck at a point where I want to add a specific image on a button click to a picturebox. The tricky part for me is, that each time I click the button, I want the image (from same location, e.g "C:\Test.jpg") to appear in the next picture box.
I tried to use a variable in the picturebox name and increase it on each click but it kept giving errors (must have used it wrong, obviously).
So to make it more clear:
I click Button1
image from location "C:\Test.jpg" appears in PictureBox1
I click Button1 again
image from location "C:\Test.jpg" appears in PictureBox2
etc.
As you can imagine, I'm not an expert in VB.NET so if you good people have any suggestions, thank you in advance :D
Vahur
Here's another approach using Controls.Find():
Public Class Form1
Private counter As Integer = 0
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
counter = counter + 1
Dim matches() As Control = Me.Controls.Find("PictureBox" & counter, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is PictureBox Then
Dim pb As PictureBox = DirectCast(matches(0), PictureBox)
Using fs As New System.IO.FileStream("C:\Test.jpg", IO.FileMode.Open)
pb.Image = New Bitmap(Image.FromStream(fs))
End Using
End If
End Sub
End Class
Form level:
Private mPList As New List(of PictureBox)
Private pIndex as Integer = 0
Form Shown:
With mPlist
.Add(PictureBox1)
.Add(PictureBox2)
... continue as needed
Ens With
Button Click
' remove image from last picbox (????)
mPlist(pIndex).Image = Nothing
pIndex += 1
if pIndex > mPlist.Count-1 then pIndex = 0
mPlist(pIndex).Image.FromFile(filename)
The issue, I'm sure, is that you are trying to figure out how to increment a number and get a handle of the picture box to set the picture on. I am going to make a couple assumptions here, in that you are making a card game and you will have a set number of players. You have a couple options:
For the number, you can either set a PRIVATE variable in your form for holding the next number and on each button click, increment it by 1.
Private Player1NextImage as byte = 1
Private Player2NextImage as byte = 1
Private Player3NextImage as byte = 1
Private Player4NextImage as byte = 1
In the button click, for the specific player, you would want to get their picturebox that is next:
You can either keep the list of pictureboxes in a list, as show by #Plutonix, or you can just use a SELECT CASE statement and hardcode the pictureboxes.
Select Case Player1NextImage
Case 1
Player1Card1PictureBox.Image = ...
Case 2
Player1Card2PictureBox.Image = ...
Etc...
'Then increment the number
Player1NextImage += 1
There are multiple ways to do this. Let's say your variable is called Counter, and (assuming) that the parent of your pictureboxes is the main form / control, add this to your button click event:
Counter += 1
Dim Pic As PictureBox = CType(Me.Controls("PictureBox" & Counter.ToString), PictureBox)
If Pic Is Nothing Then
MsgBox("Could not find PictureBox")
Else
Pic.Image = Image.FromFile("C:\Test.jpg")
End If

Simple VB, handling many buttons

In my VB solution I have a lot of buttons arranged in a grid. When the program is loaded one of the buttons is randomly set to be the right one (You have to click that one to win). Can anybody point my in the right direction? Because there must be a better way than manually coding every single button, and creating an event handler for every single button.
You don't have to give me a working example, just an overall idea of how this is done.
Thanks.
If you want to arrange the buttons in a grid, either use the TableLayoutPanel or add the buttons directly to the form and calculate their positions. The TableLayoutPanel is useful if you want to arrange the buttons automatically when the form resizes, otherwise adding the buttons directly seems easier to me.
Add the buttons to an array defined at form level to make them easily accessible
Public Const NColumns As Integer = 5, NRows As Integer = 4
Private buttons As Button(,) = New Button(NColumns - 1, NRows - 1) {}
You can add the buttons easily in loops
For ix As Integer = 0 To NColumns - 1
For iy As Integer = 0 To NRows - 1
Dim btn = New Button()
btn.Text = String.Format("{0:d2}{1:d2}", ix, iy)
btn.Location = New Point(leftMargin + ix * xDistance,
topMargin + iy * yDistance)
btn.Size = New Size(buttonWidth, buttonHeight)
AddHandler btn.Click, Addressof Button_Clicked
buttons(ix, iy) = btn
Controls.Add(btn)
Next
Next
You can determine the winning button with a random generator. Define it as form member, not as local variable.
Private randomGenrator As System.Random = New System.Random()
Determine the coordinates
Dim xWins = randomGenrator.Next(NColumns) 'Returns a number between 0 and NColumns-1
Dim yWins = randomGenrator.Next(NRows)
The click handler looks like this
Private Sub Button Button_Clicked(sender As Object, e As EventArgs)
If sender = buttons(xWins, yWins) Then
'You win
Else
'You loose
End
End Sub
First, you said you want a grid of buttons, so you have to have a FlowLayoutPanel control in your form in order to let the buttons you want to add, to be arranged automatically.
Second, you have to make use of a for loop, r any kind of look, in order to add the buttons to be added to previously added 'FlowLayoutPanel'.
class Answers
Dim strAnswerText as string
Dim AnswerFlag as Boolean
End Class
Sub LoadForm(byval a_Answers as Answer())
Dim i as Integer = 0
Dim b as Button
For(i=0;i<NUM_OF_BUTTONS;i++)
b = New Button()
b.Text = "Choice -" & i & "- " & a_Answers(i).strAnswerText
b.Tag = a_Answers(i).AnswerFlag
'Supposing that the FlowLayoutPanel control name is fl
AddHandler b.Click, Addressof Clicked
fl.controls.Add(b)
End For
End Sub
Sub Button Clicked(sender as object, e as EventArgs)
if sender.Tag = True
'True answer
else
'Wrong answer
end if
End Sub

Retrieving data on dynamic controls

I am using dynamically created controls and need to retrieve information about the control at runtime.
If IsLoaded <> "free" Then
flow_display.Controls.Clear()
For x As Integer = 0 To populate.Count - 1
If populate(x).parentID = 2 Then
Dim NewPicBox As PictureBox = New PictureBox
NewPicBox.Size = New System.Drawing.Size(697, 50)
NewPicBox.ImageLocation = pw_imgLink & populate(x).imageID
AddHandler NewPicBox.Click, AddressOf catWindow
flow_display.Controls.Add(NewPicBox)
End If
Next
IsLoaded = "free"
End If
End Sub
Here I create the control when the user clicks on the appropriate label. Right now the catWindow sub is empty. I need to figure out which button is clicked and figure out its location on the populate list. I have tried a few things and from what I've read from other questions can't seem to find anything the helps. Thanks :)
For finding out which PictureBox is pressed, your catWindow Sub should look like this:
Public Sub catWindow(ByVal sender As Object, ByVal e As EventArgs)
Dim box As PictureBox = TryCast(sender, PictureBox)
If box Is Nothing Then Exit Sub
'Now "box" refers to the PictureBox that was pressed
'...
End Sub
If you want to find it's location in the populate list, you will need to iterate through the list until you find the matching box. You could also pre-empt a property on your PictureBox that isn't doing anything else and use it to store the index. Older forms tools used to have a .Tag property especially for this kind of thing. But really, the need to do this smells like a design flaw to me.
FWIW, I'd rewrite your original sample like this:
If IsLoaded <> "free" Then
flow_display.SuspendLayout()
flow_display.Controls.Clear()
For Each box As PictureBox In populate
.Where(Function(p) p.parentID = 2)
.Select(Function(p) New PictureBox() With {
.Size = New System.Drawing.Size(697, 50),
.ImageLocation pw_imgLink & p.imageID })
AddHandler box.Click, AddressOf catWindow
flow_display.Controls.Add(box)
Next box
flow_display.ResumeLayout()
IsLoaded = "free"
End If

Removing a collection of controls

I have a form (Form1) and it has 30 controls.
When I hit a button, I want to remove those 30 buttons and put other controls on the form.
Now, my problem is that this is to slow.
I have this list with controls I want to delete and I run through them with a For Each.
Private Sub ClearControls()
'removing the controls from Me.Controls
For Each Control As Control In ListToDelete
Me.Controls.Remove(Control)
Next
ListToDelete = New List(Of Control)
End Sub
Now, if you watch the form, you see the controls getting deleted 1 by 1. This action takes about 0.4 seconds (timed with the build-in stopwatch) and that's too long.
Are there any solutions to delete the controls in a faster way or is it only possible to delete the controls 1 by 1?
Maybe an important fact is that everything is connected with a database.
The controls are created by a class I defined myself (TableDrawer) and it creates a rectangle or circle (depends on info from the database).
I add the selfmade controls to the form and when I want to delete them, it takes 0.4 seconds to get other controls on the form - also with information out of my database.
Hopefully this clears some things up and I hope you can help me out... It really has to go a bit faster (I hope to get 0.1s or lower)
Hiding the Panel first seems to make the controls disappear quicker than just clearing the Panel. See this code:
Option Strict On
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Panel1.Visible = False
If Not Panel1.Controls.OfType(Of Button).Any() Then
For x As Integer = 1 To 10
For y As Integer = 1 To 10
Dim btn As New Button()
btn.Size = New Size(45, 45)
btn.Location = New Point((x - 1) * 45, (y - 1) * 45)
btn.Text = (x * y).ToString()
Panel1.Controls.Add(btn)
btn.Visible = True
Next
Next
End If
Panel1.Visible = True
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Panel1.Visible = False
Panel1.Controls.Clear()
Panel1.Visible = True
End Sub
End Class
This code has 2 buttons, and a Panel. Button1 generates 100 buttons, places them on a Panel. Button2 hides the panel before removing them. Perhaps you can experiment with this idea.
It's not the deletion that tends to take the time - it's redrawing the form each time. Try surrounding your deletion code with calls to SuspendLayout and ResumeLayout
Private Sub ClearControls()
'removing the controls from Me.Controls
Me.SuspendLayout()
For Each Control As Control In ListToDelete
Me.Controls.Remove(Control)
Next
Me.ResumeLayout()
ListToDelete = New List(Of Control)
End Sub
Put the controls in a panel container control. Removing the panel container removes all child controls.
kindly never used remove and panel.removeat to remove any controls. It won't be able to delete last control in panel layout.Especially for panel.removeat will return out of index error once delete the last end control in panels. I'm also wondering need to know why this is problem appears?
Stored all the control name in string array, find those controls in panel and delete them, Try below code will help you delete all control in panel for one short. Try using with find and removeBykey function will make your task easier.
Dim ctrllist() as string
Dim counts = 0
For each control in Me.panel1.controls
redim Preserve ctrllist(0 to counts)
ctrllist(counts)=control.name
counts+=1
Next
For counts=Lbound(ctrllist) to Ubound(ctrlllist)
If me.panel1.controls.find(ctrllist(counts),True).Length>0 then
me.panel1.controls.removeBykey(ctrllist(counts))
End If
Next
Hope it will help.
Thanks user1884888! The technique helps me.
If I use Me.ScrollPanelControl.Controls.Clear() then the application goes unresponsive and there's no choice to terminate it from Task Manager but using this technique helps me.
This code is to help some one having the same problem.
While (True)
Dim count = Me.ScrollPanelControl.Controls.Count
If count <= 0 Then
Exit While
End If
Dim firstCtrl = CType(Me.ScrollPanelControl.Controls(0), MyControl)
If Not firstCtrl.IsMoving Then
If Me.ScrollPanelControl.Controls.Find(firstCtrl.Name, True).Length > 0 Then
Me.ScrollPanelControl.Controls.RemoveByKey(firstCtrl.Name)
End If
ElseIf count > 1 Then
firstCtrl = CType(Me.ScrollPanelControl.Controls(1), MyControl)
If Me.ScrollPanelControl.Controls.Find(firstCtrl.Name, True).Length > 0 Then
Me.ScrollPanelControl.Controls.RemoveByKey(firstCtrl.Name)
End If
Else
Exit While
End If
End While