Variable variables? - vb.net

I was just wondering if it was possible in Visual Basic 2010 Express to cycle through, say, a series of RectangleShapes, but use a For...Next loop and check for a specific property of each name of the rectangles. For example:
Private Sub RectangleIntersect(ByVal rect As Microsoft.VisualBasic.PowerPacks.RectangleShape)
For c = 1 To 31
RectangleShape('and then add the c value to the name).Location
Next
End Sub
Help is much appreciated. Thank you for your time!

Assuming you are using the same ShapeContainer (ShapeContainer1 by default) for all of your RectangleShapes:
Private Sub RectangleIntersect(ByVal rect As Microsoft.VisualBasic.PowerPacks.RectangleShape)
For Each otherRect As PowerPacks.RectangleShape In ShapeContainer1.Shapes.OfType(Of PowerPacks.RectangleShape)()
If Not (otherRect Is rect) Then
If otherRect.Bounds.IntersectsWith(rect.Bounds) Then
Debug.Print(otherRect.Name & " intersects with " & rect.Name)
End If
End If
Next
End Sub
To get them "in order" as you requested, you could do:
Private Sub RectangleIntersect(ByVal rect As Microsoft.VisualBasic.PowerPacks.RectangleShape)
For c As Integer = 1 To 31
Dim c2 As Integer = c
Dim R As RectangleShape = ShapeContainer1.Shapes.OfType(Of PowerPacks.RectangleShape).FirstOrDefault(Function(x)
Return x.Name = "RectangleShape" & c2
End Function)
If Not IsNothing(R) AndAlso Not (R Is rect) Then
If R.Bounds.IntersectsWith(rect.Bounds) Then
Debug.Print(R.Name & " intersects with " & rect.Name)
End If
End If
Next
End Sub

By the code block you've provided, I assume you have a bunch of RectangleShape objects somewhere in the parent class that are named RectangleClassN, where is N is 1 to 31 for example.
What you need as the parameter to your method is some kind of collection of RectangleShapes, rather than what you have defined as a single RectangleShape. Then you will be able to iterate over them.
Private Sub RectangleIntersect(ByVal rectangles As IEnumerable(Of RectangleShape))
For Each rs As RectangleShape in rectangles
If rs.Location = "" Then
'something
End If
Next
End Sub
When you call the method, you'll need to pass in some kind of collection containing multiple objects. See the contrived example below.
Dim myRectangles As New List(Of RectangleShape)()
myRectangles.Add(RectangleShape1)
myRectangles.Add(RectangleShape2)
RectangleIntersect(myRectangles)
Since gathering each individual object would be agonizing, you could do something like loop through all the controls or objects that belong to some container and check their type. If their type is RectangleObject, add them to the collection. Or you could even perform this code inside your RectangleIntersect method.
For Each rs As RectangleShape In parentContainer.Controls ' or whatever this would be. Children? it depends on what the container is
...
Next

Related

Removing items from a List(Of ) results in System.InvalidOperationException

It happens so often to me that I need to remove items from a List(Of) if a certain condition is met.
I prefer to do it this way:
For Each nClass As SomeClass In MyList
If (Something) Then
MyList.Remove(nClass)
End If
Next
However, when I remove an item, the collection is changed, and the For Next Statement can't proceed, and a System.InvalidOperationException is thrown.
I wonder if there's any general way to do this properly without writing big workarounds.
Can anybody tell how this should be done correctly?
I'm attaching a test code to see the error:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim nList As New List(Of SomeClass)
For i As Integer = 0 To 5
Dim nNewItem As New SomeClass
nNewItem.Int = i
nNewItem.Text = "My text " + i.ToString
nList.Add(nNewItem)
Next
For Each nItem As SomeClass In nList
If nItem.Int > 1 And nItem.Int < 5 Then
nList.Remove(nItem)
End If
Next
End Sub
End Class
Public Class SomeClass
Private _iInt As Integer = 0
Private _sText As String = String.Empty
Public Property Int() As Integer
Get
Return _iInt
End Get
Set(value As Integer)
_iInt = value
End Set
End Property
Public Property Text() As String
Get
Return _sText
End Get
Set(value As String)
_sText = value
End Set
End Property
End Class
As Plutonix said, you need to work backwards through your loop -
Change
For Each nItem As SomeClass In nList
If nItem.Int > 1 And nItem.Int < 5 Then
nList.Remove(nItem)
End If
Next
to
For I As Integer = nList.Count to 0 Step -1
If nlist(I).Int > 1 And nList(I).Int < 5 Then
nList.RemoveAt(I)
End If
Next
The reason being that in a For Each..Next loop, the number of items in the object is recorded at the start of the loop and does not change. Even if you add or remove items in the object.
For example. Say you start off with a list of 10 letters.
A
B
C
D
E
F
G
H
I
J
So. At the start of your For Each loop, the number of items is recorded (10). As the loop iterates through the list, let's say you remove "E".
For the purpose of this explanation is doesn't matter too much which one. Anyway, you'll end up with all the subsequent items being moved back one. So what was at index position 5 will be at position 4 and so on to what was at index 10 is now at index 9 . The loop carries on and when it tries to access the item at index 10, it doesn't exist because it's outside the bounds of the list that now has 9 items.
If instead you use a `For..Next' loop stepping backwards, and start at the list item and head backwards, when you get to say, "E" and want to remove it, and then step backwards again and again until you get to the first item,the first item is still at the same index position, so no problem.

VBA Object module must Implement ~?

I have created two classes, one being an interface for the other. Each time I try to instantiate Transition_Model I get:
Compile error: Object Module needs to implement '~' for interface'~'
To my understanding Implementing class is supposed to have a copy of all public subs, function, & properties. So I don't understant what is the problem here?
Have seen similar questions come up but either they refer to actual Sub or they include other complications making answer too complicated for me to understand.
Also note I tried changing Subs of Transition_Model to Private and add 'IModel_' in front of sub names(Just like top answer in second question I linked) but I still receive the same error.
IModel
Option Explicit
Public Enum Model_Types
Transition
Dummy
End Enum
Property Get M_Type() As Model_Types
End Property
Sub Run(Collat As Collateral)
End Sub
Sub Set_Params(key As String, value As Variant)
End Sub
Transition_Model
Option Explicit
Implements IModel
Private Transitions As Collection
Private Loan_States As Integer
Private Sub Class_Initialize()
Set Transitions = New Collection
End Sub
Public Property Get M_Type() As Model_Types
M_Type = Transition
End Property
Public Sub Run(Collat As Collateral)
Dim A_Transition As Transition
Dim New_Balance() As Double
Dim Row As Integer
For Row = 1 To UBound(Collat.Curr_Balance)
For Each A_Transition In Transitions
If A_Transition.Begining = i Then
New_Balance = New_Balance + Collat.Curr_Balance(Row) * A_Transition.Probability
End If
Next A_Transition
Next
End Sub
Public Sub Set_Params(key As String, value As Double)
Dim Split_key(1 To 2) As String
Dim New_Transition As Transition
Split_key = Split(key, "->")
Set New_Transition = New Transition
With New_Transition
.Begining = Split_key(1)
.Ending = Split_key(2)
.Probability = value
End With
Transitions.Add New_Transition, key
End Sub
Lastly the Sub I am using to test my class
Sub Transition_Model()
Dim Tested_Class As New Transition_Model
Dim Collat As New Collateral
'Test is the model type is correct
Debug.Assert Tested_Class.M_Type = Transition
'Test if Model without transition indeed does not affect balances of its collateral
Collat.Curr_Balance(1) = 0.5
Collat.Curr_Balance(2) = 0.5
Tested_Class.Run (Collat)
Debug.Assert ( _
Collat.Curr_Balance(1) = 0.5 And _
Collat.Curr_Balance(2) = 0.5)
End Sub
Actaully Per the second question I linked has the correct answer which I missed.
All subs need to start with 'IModel_' and rest ot the name has to match the name in IModel.
AND
This is the part i missed, you cannot use underscore in the Sub name.

Adding a variable to a listBox in VB.net Windows Form

I am making a dvd database system in windows form and trying to display the dvd's entered by a user. Then display the Title, Director and Genre in 3 separate listBoxes.
When the user enters the information through 3 separate text boxes, the information is stored in a structure I made called TDvd. This means I can call for example dvd.Title or dvd.Director. I also use the variable index to add this information to an array I made called Dvd(100) (just a random number I used to test).
Here is the code I currently have for adding the items to the ListBox:
For i = 1 To noOfAddedDvds
lstTitle.Items.Add(dvd(i).Title)
lstDirector.Items.Add(dvd(i).Director)
lstGenre.Items.Add(dvd(i).Genre)
Next
The variable NoOfDvdsAdded is just a way of keeping track of the number of dvd's the user has already entered.
I run this and enter the Title, Director and Genre, but when I try and display this information across the 3 listboxes, I get the error:
An unhandled exception of type 'System.ArgumentNullException' occurred in System.Windows.Forms.dll
Public Class Form1
Structure TDvd
Dim Title As String
Dim Director As String
Dim Genre As String
End Structure
Dim dvd(100) As TDvd
Dim index As Integer = 0
Dim noOfAddedDvds As Integer
Private Sub btnAddToDatabase_Click(sender As Object, e As EventArgs) Handles btnAddToDatabase.Click
If txtDirector.Text <> "" Or txtGenre.Text <> "" Or txtTitle.Text <> "" Then
txtTitle.Text = dvd(index).Title
txtDirector.Text = dvd(index).Director
txtGenre.Text = dvd(index).Genre
index += 1
noOfAddedDvds += 1
End If
End Sub
Private Sub btnDisplayDatabase_Click(sender As Object, e As EventArgs) Handles btnDisplayDatabase.Click
Dim i As Integer
For i = 0 To noOfAddedDvds
MessageBox.Show(index & ", " & i)
lstTitle.Items.Add(dvd(i).Title)
lstDirector.Items.Add(dvd(i).Director)
lstGenre.Items.Add(dvd(i).Genre)
MessageBox.Show(index & ", " & i)
Next
End Sub
End Class
According to the documentation, an ArgumentNullException is thrown by the Add() method if the argument passed to it is null. (Or Nothing in VB.) So one of these is Nothing at runtime:
dvd(i).Title
dvd(i).Director
dvd(i).Genre
You'll have to debug to determine which. It would seem that the error is because you're starting your iteration at 1 instead of 0, I would think it should be:
For i = 0 To noOfAddedDvds - 1
So when you get to the index of noOfAddedDvds in your collection, that element will be an uninitialized struct with Nothing strings.
You'll definitely want to fix the iteration (indexes start at 0). Additionally, you may also benefit from initializing the String properties in your struct to String.Empty internally. Depends on whether you want similar errors to manifest as an exception or as an empty record. Sometimes the latter makes the problem more obvious since at runtime you'd see that your output started on the second record.
Just a few pointers...
The Items collection on the ListBox is actually 0 indexed, by which I mean that instead of going "1,2,3", it actually goes (0,1,2).
That's what your problem is.
Hint - think about perhaps using a List instead of an array as well... (for dvd)
Your thing cries out for being rewritten in OO form:
Friend DVDGenres
Undefined
Comedy
Action
Adventure
Sci-Fi
End Enum
Friend Class DVD
Public Property Title As String
Public Property Director As String
Public Property Genre As DVDGenres
Public Sub New
Title = ""
Director = ""
Genre = DVDGenres.Undefined
' other stuff too
End Sub
Public Overrides Function ToString As String
Return Title
End Sub
End Class
Now something to store them in. Arrays went out with Rubik's Cubes, so a List:
Private myDVDs As New List(of DVD)
A list and a class can do what arrays and structures can without the headaches. Add a DVD:
Dim d As New DVD
d.Name = TextBoxName.Text
d.Director = TextBoxDir.Text
d.Genre = comboboxGenre.SelectedItem
' add to the container:
myDVDs.Add(d)
Display all the DVDs in a ListBox to pick from:
AllDVDsLB.DataSource = myDVDs
AllDVDsLB.DisplayMember = "Title"
This will set your list as the datasource for the listbox. Whatever is in the List is automatically displayed without copying data into the Items collection. Then, say from selectedindex changed event, display the selected item details to some labels:
Label1.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Title
Label2.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Director
Label3.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Genre.ToString
Iterate to do something like what is in the Question:
For Each d As DVD in myDVDs ' CANT run out of data
lstTitle.Items.Add(d.Title)
lstDirector.Items.Add(d.Director)
lstGenre.Items.Add(d.Genre.ToString)
Next
Or iterate and reference with an Int32:
For n As Integer = 0 To myDVDs.Count - 1
lstTitle.Items.Add(myDVDs(n).Title)
' etc
Next n
HTH

How to shuffle a list on a random order?

How can I change the order of data in a list on a random order (Shuffle). easiest method with the least coding effort without definition of new functions or sub please.
I usually tag the items with random data and sort that. You can implement the shuffle directly, but that's more work - especially proving the algorithm actually shuffles randomly...
Well, I just made this code snippet here for a future reference, if you want to use a list just replace all instances of "Stack" with "List" and make sure to change ".Push" to ".Add" and it should work fine. To be honest I'm surprised a shuffle function isn't built in.
Dim Deck As New Stack
Sub Main()
For i As Integer = 1 To 10
Deck.Push("Card #" & i)
Next
Do
Console.Clear()
For i As Integer = 0 To Deck.Count - 1
Console.WriteLine(Deck(i))
Next
Console.ReadKey(True)
Shuffle()
Loop
End Sub
Private Sub Shuffle()
Dim NewDeck As New Stack
Dim i As Integer
Dim s As String 'Change type depending on what is in your stack.
Dim r As New Random
Do
i = r.Next(0, Deck.Count)
s = Deck(i)
'Stops you getting several of one item and then none of others, etc.
If Not NewDeck.Contains(Deck(i)) Then
NewDeck.Push(s)
End If
Loop Until NewDeck.Count = Deck.Count
Deck = NewDeck
End Sub

VB.NET Iterating Form Labels

I have several label boxes on my design form that all share the naming convention lbl_#.text where # ranges from 1 to 60. I want to make a loop that iterates through each lbl_#.text adding some incremental value, let's say multiples of 2 for this question's theoretical purpose.
Something such that the end result would amount to the following:
lbl_1.text = "2"
lbl_2.text = "4"
lbl_3.text = "6"
...
lbl_60.text = "120"
I'm not sure how to access each of these labels through the coding side, I only know how to explicitly mention each label and assign a value :/
There are a few options here.
In this situation the labels will often have a common container, such as panel or groupbox control. In that case:
Dim formLabels = myContainerControl.Controls.OfType(Of Label)()
For Each formLabel As Label In formLabels
'...
Next formLabel
Of course, this mixes logical groups with visual groupings. Those two things don't always align well, so you can also...
Add them all to a Label array (or List(Of Label) or any other enumerable):
Dim formLabels(60) As Label = {lbl_1, lbl_2, lbl_3 .... }
For Each formLabel As Label in formLabels
'...
Next formLabel
But sometimes that's more trouble than it's worth, even if you use a loop to create the collection, and so you can also
Use the .Name property (in conjunction with a naming convention to identify your desired controls):
Dim formLabels = Controls.Where(Function(c) c.Name.StartsWith("lbl_"))
For Each formLabel As Label In formLabels
'...
Next formLabel
Some combination of the above (for example, code in the form load event to create a list based on the name property).
Notice the actual For Each loop is exactly the same in all of those options. No matter what you do, get to the point where you can write a single expression to identify the label control, and then run a simple loop over the expression result.
This points to a final strategy: think in terms of binding to a data source. With a data source, your labels are created as part of a DataGridView, FlowLayoutPanel, or similar control. Then you can iterate the rows in the grid or panel.
Use the Controls collection:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim i As Integer
For i = 1 To 3
Dim myLabel As Label = CType(Me.Controls("lbl_" & i), Label)
myLabel.Text = ...whatever value you want to put here
Next
End Sub
End Class
If you don't know how many labels there are, one option is to use a Do Loop.
Dim lblTarget As Label = Nothing
Dim intCursor As Integer = 1
Dim bolFirstIteration As Boolean = True
Do Until lblTarget Is Nothing AndAlso Not bolFirstIteration
If bolFirstIteration Then
bolFirstIteration = False
End If
lblTarget = CType(Me.Controls("lbl_" & intCursor.ToString()), Label)
If Not lblTarget Is Nothing Then
lblTarget.Text = (intCursor * 2).ToString()
End If
intCursor += 1
Loop