Berry Tsakala
Berry Tsakala

Reputation: 16610

vb.net "For Each" iterates only once over a list

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

working solution (but not an answer)

For x As Integer = 0 To Forms.Count - 1
    pics(Forms(x)) = DirectCast(PictureBox1, PictureBox)
Next

Your answers can be grouped into

1. dictionary syntax: dict.add(k, v) vs. dict(k) = v

2. exceotion monster eating my exception as suggested by some.

3. casting voodoo needed, as suggested by @Andre Pageot

4. VB don't like to add the same picturebox twice to the dictionary.

(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

Answers (3)

Andre Pageot
Andre Pageot

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

phadaphunk
phadaphunk

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

Joel Coehoorn
Joel Coehoorn

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

Related Questions