Reputation: 16610
The following "for each" loop runs only once. Why?
The project contains 4 forms. Form1 contains 4 PictureBox's. I'm trying to iterate over a list of forms (in this example, to set each form's BG to the picturebox) :
Sub Butt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Butt.Click
Dim Forms As New List(Of Form)
Dim Pics As New Dictionary(Of Form, PictureBox)
Forms.Add(me)
Forms.Add(form2)
Forms.Add(form3)
Forms.Add(form4)
performs only first iteration:
For Each frm As Form In Forms
pics(frm) = Me.Controls("PictureBox" + CStr(i + 1))(0)
'BTW the next line behave the same:
' pics(frm) = CType(Me.Controls("PictureBox" + CStr(i + 1)), PictureBox)
'or this:
' pics(frm) = PictureBox1
'or if the pics is a dictionary(of string):
' pics(frm.name) = PictureBox1
Next
performs all 4 iterations :
For Each frm As Form In Forms
msgbox(frm.name)
Next
Why it wouldn't do all 4 iterations in the first loop?
Edit:
"i" is not the issue. Apparently the problem is with assigning the same control twice, as a value to the "pics" dictionary... still investigating.
Edit 2 - intermediate summary
For x As Integer = 0 To Forms.Count - 1
pics(Forms(x)) = DirectCast(PictureBox1, PictureBox)
Next
Your answers can be grouped into
(1.) is probably not the issue. The syntax dict(k) = v works everywhere else. I even tried dict.add(k,v) and it gave EXCATLY the same behavior, i.e. still error.
(2.) There's indeed something wrong in the IDE: The debugger skips the "Next" statement, and continues-as-usual the next instruction after the loop. (WTF) It didn't matter even after I set to catch all exceptions in the Alt+Ctrl+E screen. The exception is still not caught.
This is IMHO a serious bug, even if someone at redmond made it by design. The same "funny behavior" is probably inherited from the VB6 days, were sometimes code was "flying away" without any reason. (But at least there it was quitting the application).
p.s. I don't have any "try - catch" clauses, nor "on error do something stupid", just to be clear :)
(3.) It's probably it, though I don't understand, and it doesn't answer the question - "why the original code is doing weewooo during runtime".
See "working solution" above.
FRANKLY I DONT WANT TO UNDERSTAND IT ANYMORE BECAUSE IT DRIVES ME CRAZY, WHY, IN PYTHON, I JUST WRITE dict(k) = y AND IT NEVER BREAKS. I DONT UNDERSTAND HOW ITS POSSIBLE TO dict.contains(k,v). IT DOESNT MAKE ANY SENSE. AND THERE MUST BE SENSE, IT'S PROGRAMMING, not voodoo.
(4.) i don't have any way (or will) to prove it.
Thank you guys.
i'll probably never use VB again after this project. There are so many voodoo things to be dealt with, it doesn't worth the headaches.
Upvotes: 1
Views: 2391
Reputation: 336
I seem to remember running into a similar issue, the trick if memory serves is to remove the form objectively from the for each like so
Sub Butt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Butt.Click
Dim Forms As New List(Of Form)
Dim Pics As New Dictionary(Of Form, PictureBox)
Forms.Add(me)
Forms.Add(form2)
Forms.Add(form3)
Forms.Add(form4)
For x As Integer = 0 To Forms.Count - 1
If Not Pics.Contains(Forms(x), DirectCast(Me.Controls("PictureBox" + CStr(i + 1))(0), PictureBox)) Then
Pics.Add(Forms(x), DirectCast(Me.Controls("PictureBox" + CStr(i + 1))(0), PictureBox))
Else
Pics(Forms(x)) = DirectCast(Me.Controls("PictureBox" + CStr(i + 1))(0), PictureBox)
End If
Next
########################################################################################## UPDATE explore and expand on the above ##############################################################################
What follows is a more detailed explanation and break down of this problem and why it isn't actually a myth or an oddity in .NET
The .NET OO framework is incredibly large complex and powerful, to understand and remember it all is not humanly impossible, but there are a few concepts that need to be grasped in order to navigate your way through the maze.
Lets examine what was going wrong.
You declared and populated a List of forms List(Of Form) with each form in your project
Dim Forms as New List(Of Form)
You then declared a dictionary to hold a (key, value) collection of the shape (Form, PictureBox)
Dim Pics as New Dictionary(Of Form, PictureBox)
This means your "Key" is of type Form and your "Value" is of type PictureBox
Now, using the list of Forms you were iterating through each form in the list, saying that you want the "Value"(PictureBox) of the "Key"(Form) in the Dictionary*(Pics)* to be set to a PictureBox found on the current form (Where the code is running) with name "PictureBox(x)" where x is your incremental integer value of some kind. Strangely you are then asking for the first instance of something of that picturebox (thats the (0) part on the end)
For Each frm As Form In Forms
pics(frm) = Me.Controls("PictureBox" + CStr(i + 1))(0)
Next
Fundamentally the method invoked from pics(frm) only gets or sets the value of an existing item in the dictionary, since the dictionary is not loaded with anything yet, Pics is empty, at no point did you add any of the forms to the Dictionary and as such nothing would be set at all.
A more suitable approach to achieve the Key Value assignment would be as follows, however this is still overkill to simply set the background image of the forms
Sub BuildDictionary()
'1. Declare the array of forms and the dictionary of (Key,Values) of type (Form,PictureBox)
Dim Forms As New List(Of Form) 'list of forms that you want to assign BG
Dim Pics As New Dictionary(Of Form, PictureBox) 'declare your dictionary list ("Key","Value") = (Form,Picturebox)
With Forms 'fill the list
.Add(Me)
.Add(Form1)
.Add(Form2)
.Add(Form3)
End With
'2. Snag the picturebox we want to assign to the forms
Dim PictureBoxToAssign As PictureBox = DirectCast(Me.Controls("PictureBox" + CStr(i + 1)), PictureBox) 'grab your picturebox
'3 option 1 (prefered)
'#### Either Do this next
Pics.Clear() 'if your logic allows that you can clear the dictionary first
For x As Integer = 0 To Forms.Count - 1 'for each form in the array
Pics.Add(Forms(x), PictureBoxToAssign) 'add the form and picturebox to the dictionary
Next
'3 option 2 (only if you cannot clear the dictionary)
'#### Or Do this
'you cannot clear the dictionary first due to logic
For x As Integer = 0 To Forms.Count - 1 'for each form in the array
'check existance of the pair
If Pics.Contains(New Generic.KeyValuePair(Of Form, PictureBox)(Forms(x), PictureBoxToAssign)) Then
Pics(Forms(x)) = PictureBoxToAssign 'update the value
Else
Pics.Add(Forms(x), PictureBoxToAssign) 'add the new pair
End If
Next
End Sub
If the objective is merely to set the background image of each form to the image of a picturebox, then perhaps a faster more efficient route might be the following example
Sub SetBG()
Dim Forms As Form() = {Me, Form1, Form2, Form3} 'array of forms that you want to assign BG, occupies less memory than a list
Dim PictureBoxToAssign As PictureBox = DirectCast(Me.Controls("PictureBox" + CStr(i + 1)), PictureBox) 'grab your picturebox
For x As Integer = 0 To Forms.Count - 1 'for each form in the array
Forms(x).BackgroundImage = PictureBoxToAssign.Image 'assign the image to the background
Next
End Sub
The important thing to remember here is that when changing the object in any way, while iterating through the collection of objects within the array, while possible, must NOT be done using a "for each obj as type in array of object" approach, ie not like this
For Each obj as ObjectType in ArrayofObjects
Change the properties of obj = (BIG NO CAN DO)
Next
you MUST separate the loop from interacting directly with the object in collection, in this example by using the for each of an index like so
For x As Integer = 0 To Forms.Count - 1 'using an integer not the object itself
'referencing the object through the index of the collection Forms(x)
Forms(x).BackgroundImage = PictureBoxToAssign.Image
Next
I hope this answer brings light to the darkness
Upvotes: 2
Reputation: 13313
Because of this part :
pics(frm)
It is probably Nothing
. I'm guessing here because I don't have the excpetion thrown.
You never reach the Next
statement. This is why you only have one iteration.
Your error is being swallowed somewhere else. Try activating Common Language Runtime Exceptions's Thrown property in the Exceptions pannel. (ctrl + alt + e).
Maybe you should try replacing
pics(frm)
by
pics.Add(frm, Me.Controls("PictureBox" + CStr(i + 1))(0))
Upvotes: 3
Reputation: 415800
The code to assign to your Pics dictionary looks wrong to me. To add an new item to a dictionary, you cannot just assign to that index. You must use the .Add() method.
An exception should be thrown when you try to assign to the key that doesn't exist yet. This could explain one time through your loop. You enter the loop the first time, the exception is thrown, so your code never reaches the second iteration. If somewhere you're swallowing that exception, you'd never know.
Sub Butt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Butt.Click
Dim Forms As New List(Of Form)
Dim Pics As New Dictionary(Of Form, PictureBox)
Forms.Add(me)
Forms.Add(form2)
Forms.Add(form3)
Forms.Add(form4)
For Each frm As Form In Forms
pics.Add(frm, Me.Controls("PictureBox" + CStr(i + 1))(0))
Next
Upvotes: 0