Selecting variable by name - vb.net

I'm not sure of the appropriate search term so please close and point me to a duplicate if that's the case.
I have a structure with has multiple variables. I've created an array of the type of the structure. I've added data to a few indexes of the array. I now want to select the a specific variable based on the name of the variable.
He's an example of the structure:
structure struc
dim name as string
dim lvl as integer
dim capacity as integer
end structure
And the deceleration of the array:
dim vills(3) as struc

You should be using public fields, not variables Actually you have the terminology correct according to MSDN, the only thing you are missing is the access modifier i.e.
Public Structure Struc
Public Name As String
Public Lvl As Integer
Public Capacity As Integer
End Structure
At the moment your variables are private which means they aren't accessible from outside your Structure.
Looking at your comments it looks like you are trying to access the property by name dynamically rather than knowing it at compile time. There are a few ways of doing this, most of which involve some Reflection.
You should perhaps have a look at ExpandObject - it's effectively a key/value dictionary with the characteristics of a normal class-type object so you get the best of both worlds e.g.
Dim struct As Object = New ExpandoObject()
struct.name = "SomeValue"
struct.lvl = 3
struct.capacity = 100
Console.WriteLine(struct["name"])
Console.WriteLine(struct["lvl"])

As Tony Hopkinson mentioned, it is possible via Reflection:
Public Class Form1
Structure struc
Dim name As String
Dim lvl As Integer
Dim capacity As Integer
End Structure
Dim vills(3) As struc
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i As Integer = 0 To vills.Length - 1
vills(i).lvl = i * 10
Next
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim fi As Reflection.FieldInfo
Dim fieldName As String = "lvl"
For i As Integer = 0 To vills.Length - 1
fi = vills(i).GetType.GetField(fieldName, Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public)
If Not IsNothing(fi) Then
Dim value As Object = fi.GetValue(vills(i))
Debug.Print(i & ": " & value.ToString)
End If
Next
End Sub
End Class
Up to you to decide if it's worth it...

I really think that if this is an important and frequent requirement (find an element by key) then you should think twice and change your array to a Dictionary(Of string, struc) and use something like this
Dim vills = new Dictionary(of String, struc)
Dim s = new struc()
s.name="k1"
s.lvl=1
s.capacity=1
z.Add(s.name, s)
....
struc c = vills("k1")
if(c IsNot Nothing) Then
Console.WriteLine(c.lvl.ToString())
End If
but if you still want to use an array you could search your struc array by name using Linq
Structure struc
Public Dim name as string
Public Dim lvl as integer
Public Dim capacity as integer
End Structure
Sub Main
Dim vills(3) as struc
....
Dim c as struc = wills.Where(Function(x) (x.name="keyName")).Single()
if(c.name IsNot Nothing) then
Console.WriteLine("Found")
End If
End Sub

Related

Confused As To Why This Function Does Not Call?

I'm trying to call this function into a procedure and am confused on why this is not working, I've looked up how to format a function call and don't understand my mistakes.
Structure Stock
Dim category As String
Dim price As Integer
Dim size As String
Dim sku As String
Dim color As String
End Structure
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim stockArray() As String =
IO.File.ReadAllLines("stockInventory.txt")
End Sub
Private Sub btnNewSave_Click(sender As Object, e As EventArgs) Handles btnNewSave.Click
Dim answer
answer = check(sku, stockArray)
End Sub
Function check(sku, stockArray) As Boolean
Dim flag As Boolean
Dim numVar = -1
numVar = Array.IndexOf(stockArray, txtSKU.Text)
If numVar = -1 Then
flag = False
End If
If numVar <> -1 Then
flag = True
End If
Return flag
End Function
It gives me an error saying,
sku is not declared. It may be inaccessible due to its protection
level
I have a structure where sku is defined as being a string. Do I have to declare it again in this sub for it to work?
You can fine-tune this code a bit:
Function check(ByVal stockArray as String()) As Boolean
Return Array.IndexOf(stockArray, txtSKU.Text) >= 0
End Function
It'll return if the element is in the Array.
If your file is very very large and you are doing many searches, I recommend storing in a HashSet instead of an array to get O(1) searching, instead of the O(n) you are getting.
I notice your stock array, has to be declared at the class level, not the method level, to be accessible by other methods.

Get OPCItem from ClientHandles in DataChange Event - OPCAutomation.dll

Is it possible to get the OPCItem from a Group using the ClientHandle value?
Pseudo example:
Dim Server As OPCServer
Dim Groups As OPCGroups
Dim WithEvents Group1 as OPCGroup
Dim ItemGroup as OPCItems
Dim Item as OPCItem
Private Sub Form1_Load(sender as Object, e as EventArgs) Handles MyBase.Load
Server.Connect("MyServer")
Group1 = New OPCGroup()
Group1.Name = "Group1"
Group1.IsSubscribed = True
Group1.OPCItems.Add("Item1", 1)
Server.OPCGroups.Add(Group1)
End Sub
Private Sub Group1_DataChange(TransactionID As Integer, NumItems As Integer, ByRef ClientHandles As Array, ByRef ItemValues As Array, ByRef Qualities As Array, ByRef TimeStamps As Array) Handles Group1.DataChange
Dim Browser as OPCBrowser = Server.CreateBrowser()
Browser.ShowBranches()
Browser.DataType = vbInteger
Browser.ShowLeafs()
Dim qualityValue as Integer
For q As Integer = 1 To Qualities.Length
qualityValue = Qualities.GetValue(n)
If qualityValue = 192 Then
'HERE is where I want to get the OPCItem by using the ClientHandles
'I can use the ClientHandle to get the Value, but I'd also like to get the ItemID to do validation against.
Dim itemClientHandle as Integer = Convert.ToInt32(ClientHandles.GetValue(n))
Dim itemValueByClientHandle as String = ItemValues.GetValue(n).ToString()
End If
Next
'It is possible to use the OPCBrowser to get the Item Names but how do I correlate the two?
Dim itemNames as New List(Of String)
For n As Integer = 1 To OPCBrowser.Count
itemNames.Add(OPCBrowser.Item(i))
Next
' Do More Stuff
End Sub
There is a request to enhance an existing client application to be paused when a value is sent from the client to the OPC Server and wait for the bit tag to switch as in indication the process has finished. This part is built; however, there are multiple items (tags) in the group and there needs to be a way of identifying the correct items bit value was changed.

Listbox Sorting Issue

I am having trouble sorting a listbox the way I am expecting it to work even using the sorted = true property. Assuming i have files named as such, when i sort by "Name" in a Windows Folder view (outside of vb) it sorts like so:
1180741
1179715
1162371
1141511
1131750
1117362
1104199
1082698
1062921
1043875
991514
970621
963154
952954
948067
917669
904315
899902
892398
882024
This is how i need it to be sorted in my list. However, with the sorted = true property, vb.net decides to sort like this:
1043875
1062921
1082698
1104199
1117362
1131750
1141511
1162371
1179715
1180741
882024
892398
899902
904315
917669
948067
952954
963154
970621
991514
I cannot understand why windows sorts the list correctly but VB does not. Any help would be most appreciated.
*This example assumes that each object in the listbox is a string representation of an integer, if in the case that it is not, the item will be skipped, you can modify this to handle such a case in another way if you want....
Example:
Option Strict On
Option Explicit On
Option Infer Off
Public Class Form1
Dim r As New Random(Now.Millisecond)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'We simulated a populated listbox
For I As Integer = 0 To 1000
Dim n As Integer = r.Next(0, Integer.MaxValue)
ListBox1.Items.Add(n.ToString)
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SortListBoxNumbers(ListBox1)
End Sub
Sub SortListBoxNumbers(listbox As ListBox)
Dim numbers As New List(Of Integer)
For Each item As Object In listbox.Items
Dim n As Integer = 0
If item.GetType = GetType(String) Then
If Integer.TryParse(DirectCast(item, String), n) Then
numbers.Add(n)
End If
End If
Next
listbox.Items.Clear()
numbers = SortNumbers(numbers)
For Each n As Integer In numbers
listbox.Items.Add(n.ToString)
Next
End Sub
Function SortNumbers(numbers As List(Of Integer)) As List(Of Integer)
Dim result As New List(Of Integer)
Dim count As Integer = numbers.Count
Do
Dim highN As Integer = Integer.MinValue
For Each n As Integer In numbers
If n > highN Then highN = n
Next
numbers.Remove(highN)
result.Insert(result.Count, highN)
Loop Until result.Count = count
result.Reverse()
Return result
End Function
End Class

I want my structure of vehicle makes to show in a listbox

I'm not sure what to change in my code to make the listbox show each vehicle make.
Public Class Form1
Structure Vehicle
Dim Make As String
Dim Model As String
Dim Doors As Integer
Dim Hp As Integer
Dim VIN As String
End Structure
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Dim Vehicles(9) As Vehicle
Vehicles(0).Make = "Chevrolet"
Vehicles(1).Make = "Dodge"
Vehicles(2).Make = "Nissan"
Vehicles(3).Make = "Mitsubishi"
Made a for loop to show each make
For i = 0 To 4 Step 1
ls.Items.Add(Vehicles(i).Make.ToString)
Next
End Sub
You're only supplying data to 4 instances (0-3):
Vehicles(0).Make = "Chevrolet"
Vehicles(1).Make = "Dodge"
Vehicles(2).Make = "Nissan"
Vehicles(3).Make = "Mitsubishi"
But you're trying to retrieve data from 5 instances (0-4):
For i = 0 To 4 Step 1
String is by default Nothing unless you assign it a value. So your 5th instance is an empty instance of Vehicle. Thus...
This will fail because Vehicles(4).Make is Nothing, and you can't call a method (ToString) on Nothing:
ls.Items.Add(Vehicles(i).Make.ToString)
And this will fail because Vehicles(4).Make is Nothing, and you can't add an empty or null string to a listbox:
ls.Items.Add(Vehicles(i).Make)
You need to either:
Adjust your loop to only cover the data you have (For i = 0 To 3 Step 1), or
Add another data element (Vehicles(4).Make = "Something"), or
Add null checking to your code (If Vehicles(i).Make Is Not Nothing)
Why don't you try using Class instead of Structure, and using list instead of array. Since it seems that yourself also don't how long will the data be, so in this case list is a good choice for your and you can just consider it as a dynamic array in c/c++. Following is my way to interpret
Public Class Form1
Class Vehicle
Dim Make As String
Dim Model As String
Dim Doors As Integer
Dim Hp As Integer
Dim VIN As String
Public Sub New(Dim s As String)
Me.Model=s
End Sub
End Class
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Dim Vehicles As New List(Of Vehicle)
Vehicles.Add(New Vehicle("Chevrolet"))
Vehicles.Add(New Vehicle("Dodge"))
Vehicles.Add(New Vehicle("Nissan"))
Vehicles.Add(New Vehicle("Mitsubishi"))
And when you loop it,
Dim car as Vehicle
For each car in Vehicles
ls.Items.Add(car.Make)
Next
Hope it helps.

Resizing an array at runtime in VB.NET

In my Windows Forms application at runtime I will be resizing an array each time I add an element. So first I must resize to the size + 1, and then add a member to this index. How do I do this?
You could use the ReDim statement, but this really isn't your best option. If your array will be changing sizes often, especially as it sounds like you're just appending, you should probably use a generic List(Of T) or similar collection type.
You can use it just like you use an array, with the addition that adding an item to the end is as easy as MyList.Add(item)
To use a generic list, add Imports System.Collections.Generics to the top of the file. Then, you would declare a new integer list like this:
Dim MyList As New List(Of Integer)()
or a string list like this:
Dim MyList As New List(Of String)()
You should get the idea.
The suggested ReDim's need the Preserve keyword for this scenario.
ReDim Preserve MyArray(n)
Using a generic list is (as suggested) the best idea. If you however want to change the size of an Array, you can use Array.Resize(ByRef arr, newSize).
ReDim is not a good (pretty bad) idea (VB specific legacy, extremely slow).
I would prefer some type of collection class, but if you WANT to use an array do it like this:
Dim arr() As Integer
Dim cnt As Integer = 0
Dim ix As Integer
For ix = 1 To 1000
cnt = cnt + 1
ReDim arr(cnt)
arr(cnt - 1) = ix
Next
You can also make your own collection class. A good programming exercise for new programmers.
Public Class MyList
Private Items() As String
Private No As Integer = 0
Public Sub Add(ByVal NewItem As String)
''Create a temporary new string array
Dim CopyString(No) As String
''Copy values from Global Variable Items() to new CopyString array
For i As Integer = 0 To No - 1
CopyString(i) = Items(i)
Next
''Add new value - NewItem - to CopyString
CopyString(No) = NewItem
''Increment No to No + 1
No += 1
''Copy CopyString to Items
Items = CopyString
'Discard CopyString
CopyString = Nothing
End Sub
Public Sub Show(ByVal index As Integer)
MsgBox(Items(index))
End Sub
End Class
''Now create a form with a TextBox name - txt, Button1 and Button2
Public Class Form1
''Declare txts as a new MyList Class
Private txts As New MyList
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
''Add text to txts which is a MyList Class
txts.Add(txt.Text)
txt.Text = ""
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
''Display value at a specific index
txts.Show(Convert.ToInt16(txt.Text))
txt.Text = ""
End Sub
End Class
Use the ReDim command to specify the new size.
ReDim MyArray(MyArray.Length + 1)
As Joel says, use a list.
Dim MyList As New List(Of String)
Don't forget to change Of String to be Of whichever datatype you're using.
This work for me
Dim Table1 As New DataTable
' Define columns
Table1.Columns.Add("Column1", GetType(System.String))
Table1.Columns.Add("Column2", GetType(System.Int32))
Table1.Columns.Add("Column3", GetType(System.Int32))
' Add a row of data
Table1.Rows.Add("Item1", 44, 99)
Table1.Rows.Add("Item2", 42, 3)
Table1.Rows.Add("Item3", 42, 3)
Table1.Rows.Add("Item4", 42, 3)
Dim arr(-1) As String
For Each dr As DataRow In Table1.Rows
ReDim Preserve arr(arr.Length)
arr(arr.Length - 1) = dr("Column1")
Next