Pass a variable in as a member name to an Object? - vb.net

Not sure if this is even possible but I need to be able to pass a variable's value in as an object's member name.
Basically I'm using a wdsl that has a number of objects where some of them could contain a collection, I need to make data grids to show the data in the collection which is straightforward enough but at the moment I have to make code for each object/collection that defines how many effective columns and their names and types.
This works fine albeit a bit long winded but it will also break if the wdsl changes and the objects collection content changes (names, types etc.)
What I need is to be able to pass a object name to a sub which will work out if the object contains a collection (PropertyType will contain []), read its name and pass that name down to a loop which will go through at the correct level to retrieve the "column" names and data types.
I have got almost all of this working until I want to pass the collection name into a loop as an object member name as it obviously doesn't evaluate the string value of CollName in the below example, it will just error saying CollName isn't a member of the object which of course it isn't but the variables actual value would be.
Sub IterateObject(objName)
Dim CollName = ""
For Each m As System.Reflection.PropertyInfo In objName.GetType().GetProperties()
If m.CanRead Then
If InStr(m.PropertyType.ToString, "[]") <> 0 Then
CollName = m.Name
End If
End If
Next
For Each p As System.Reflection.PropertyInfo In objName.CollName(2).GetType().GetProperties()
If p.CanRead Then
If p.Name <> "ExtensionData" Then
MsgBox(p.Name & " - " & (p.PropertyType.ToString))
End If
End If
Next
End Sub
Is there a way of doing effectively objName.(value of CollName)(2).GetType().GetProperties()

This seems to have fixed it using CallByName to allow me to make a new object out of just the bit I need
Sub IterateObject(objName)
Dim CollName = ""
For Each m As System.Reflection.PropertyInfo In objName.GetType().GetProperties()
If m.CanRead Then
If InStr(m.PropertyType.ToString, "[]") <> 0 Then
CollName = m.Name
End If
End If
Next
Dim CollObj
CollObj = CallByName(objName, CollName, CallType.Get)
For Each p As System.Reflection.PropertyInfo In CollObj(0).GetType().GetProperties()
If p.CanRead Then
If p.Name <> "ExtensionData" Then
MsgBox(p.Name & " - " & (p.PropertyType.ToString))
End If
End If
Next
End Sub

Related

Using user defined types in an array

It appears that it is not possible to create an empty and thus dynamic array of a user defined type.
I've tried several options provided by various 'help' websites including yours. I've spent the better part of a day researching dynamic arrays and user defined types.
Option Base 1
Public Type RegisteredItem
ItemName as String
ItemState as ItemStatus ' an enum
End type
' note the 's'
' also tried (-1), but that must be very old stuff?
' also tried (16)
Dim RegisteredItems () as RegisteredItem
Public Function RegisterItem(aName as String, aState as ItemStatus) as integer
' now when RegisteredItems is used in code, instead of getting array properties, a choice of ItemName or ItemState is all that pops up.
Debug.Print UBound(RegisteredItems) returns an error 13, type mismatch
basically, no array properties or methods seem to be available.
When I use RegisteredItems., only ItemName and ItemState appear. It's as if there is no table.
as described in the code section. Would like to be able to use Count or UBound = LBound to see if the table is empty; would like to search the table if it's not empty and redim it to add new items when necessary, but currently, I must be missing something.
About me: I got out of the computer business in 1989. At that time when the dinos ran amuck, I knew 40 assembler languages and about 40 programming languages. I met Bill Gates and Steve Jobs when they can to view the XEROX PARC systems where I was working at the time. You can fill it in from there.
You must either dimension the array when declared:
Dim RegisteredItems(4) As RegisteredItem
or, as will be the case for you, before using it:
Dim RegisteredItems() As RegisteredItem
' ...
ReDim RegisteredItems(7) As RegisteredItem
Addendum
This is what your module initially could look like:
Option Compare Database
Option Explicit
Option Base 1
Public Enum ItemStatus
Active
Passive
End Enum
Public Type RegisteredItem
ItemName As String
ItemState As ItemStatus
End Type
Public RegisteredItems() As RegisteredItem
' Add item.
'
Public Function RegisterItem(aName As String, aState As ItemStatus) As Integer
Dim Item As RegisteredItem
Dim ItemCount As Integer
Item.ItemName = aName
Item.ItemState = aState
On Error Resume Next
ItemCount = UBound(RegisteredItems)
On Error GoTo 0
ItemCount = ItemCount + 1
ReDim Preserve RegisteredItems(ItemCount)
RegisteredItems(ItemCount) = Item
RegisterItem = ItemCount
End Function
' List items.
'
Public Sub ListRegisteredItems()
Dim Index As Integer
On Error Resume Next
For Index = LBound(RegisteredItems) To UBound(RegisteredItems)
Debug.Print Index, RegisteredItems(Index).ItemName, RegisteredItems(Index).ItemState
Next
End Sub
Ok, a few things.
Checking for a empty array() requires error trapping
(this is unfortunate)
So, with this code:
Option Compare Database
Option Explicit
Option Base 1
Enum ItemStatus
One = 1
Two = 2
End Enum
Public Type RegisteredItem
ItemName As String
ItemState As ItemStatus ' an enum
End Type
Sub Test22()
Dim reg() As RegisteredItem
Dim intRows As Integer
On Error Resume Next
intRows = UBound(reg)
If Err.Number <> 0 Then
MsgBox "no rows in arrary"
Else
MsgBox "There are " & intRows & " in the array"
End If
On Error GoTo 0 ' turn error system back on
' add a row to the arrary
intRows = intRows + 1
ReDim Preserve reg(intRows)
reg(intRows).ItemName = "Hello"
reg(intRows).ItemState = One
End Sub
Note how we have to test/check for the un-defined (empty) array.
Also, note that intel-sense will work for the enum. However, you don't go .ItemState.SomeEnum, but in fact assign the enum with = sign.
So this screen cap shows how the intel-sense will look for the enum of the array:
However you could consider creating a class module (a custom class).
Arrays go back to the "old" FORTRAN days, or say BASIC that was so often included with a PC in the DOS days.
However, collections are great, since you don't have to re-dim, there is a built in "count" method. And they are dynamic.
So, you could create a class module like this:
Option Compare Database
Option Explicit
' Register class
Public ItemName As String
Public Status As ItemStatus
So, above was created as a class module, and saved as RegisterClass
Now, our code becomes far more simple:
Sub Test22()
Dim reg As New RegisterClass
Dim myCol As New Collection
reg.ItemName = "Hello"
reg.Status = One
Debug.Print "count in collection = " & myCol.Count
myCol.Add reg
Debug.Print "count in collection = " & myCol.Count
'display one value
Debug.Print myCol.Item(1).ItemName
' with above you don't get intel sense
' but, you can go:
Dim myValue As RegisterClass
For Each myValue In myCol
Debug.Print myValue.ItemName
Next
End Sub

How to parameterise a object member in VB.NET for WSDL

I have some code that populates a SOAP request in VB.NET. I get the data from a SQL query and run through the objects members to populate each one. I'm trying to find a quick(ish) way to not provide the member when it is empty rather than checking each value before applying it. As the SQL data comes in from a pipe delimited file in the first place we never get NULL just empty cells which get sent in the SOAP request as "".
Is there a way to define an objects member using a variable rather than its literal name?
UpLB_request_Items.Spare8 = Convert.ToString(SourceDataSet.tables(0).Rows(i)(colSPARE8))
UpLB_request_Items.Spare9 = Convert.ToString(SourceDataSet.tables(0).Rows(i)(colSPARE9))
UpLB_request_Items.Spare10 = Convert.ToString(SourceDataSet.tables(0).Rows(i)(colSPARE10))
Call IterateObject(UpLB_request_Items)
So this populates each member with the value from the SQL data (SourceDataSet), then I could send the completed object down to a Sub to check each value before I actually send it to the webservice
Sub IterateObject(objName)
Dim CollName = ""
For Each m As System.Reflection.PropertyInfo In objName.GetType().GetProperties()
If m.CanRead Then
If m.PropertyType.Name = "String" Then
CollName = m.Name
Dim CollVal = CallByName(objName, CollName, CallType.Get)
If CollVal = "" Then
objName.CollName = Nothing 'this is the tricky bit
End If
End If
End If
Next
End Sub
The bit where it obviously breaks is objName.CollName = Nothing as it will then say that the object doesn't have a member called CollName and I'm not sure if there is a way to force the code to evaluate CollName to its value (e.g. SPARE8 rather than the string "CollName")
I went with this in the end
UpLB_request_Items.Spare4 = Strings.Replace(Convert.ToString(SourceDataSet.Tables(0).Rows(i)(colSPARE4)), "", Nothing)

Variable variables?

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

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

Checking if a value is a member of a list

I have to check a piece of user input against a list of items; if the input is in the list of items, then direct the flow one way. If not, direct the flow to another.
This list is NOT visible on the worksheet itself; it has to be obfuscated under code.
I have thought of two strategies to do this:
Declare as an enum and check if input is part of this enum, although I'm not sure on the syntax for this - do I need to initialise the enum every time I want to use it?
Declare as an array and check if input is part of this array.
I was wondering for VBA which is better in terms of efficiency and readability?
You can run a simple array test as below where you add the words to a single list:
Sub Main1()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test("dog") 'True
Debug.Print "horse", Test("horse") 'False
End Sub
Function Test(strIn As String) As Boolean
Test = Not (IsError(Application.Match(strIn, arrList, 0)))
End Function
Or if you wanted to do a more detailed search and return a list of sub-string matches for further work then use Filter. This code would return the following via vFilter if looking up dog
dog, dogfish
In this particular case the code then checks for an exact match for dog.
Sub Main2()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test1("dog")
Debug.Print "horse", Test1("horse")
End Sub
Function Test1(strIn As String) As Boolean
Dim vFilter
Dim lngCnt As Long
vFilter = Filter(arrList, strIn, True)
For lngCnt = 0 To UBound(vFilter)
If vFilter(lngCnt) = strIn Then
Test1 = True
Exit For
End If
Next
End Function
Unlike in .NET languages VBA does not expose Enum as text. It strictly is a number and there is no .ToString() method that would expose the name of the Enum. It's possible to create your own ToString() method and return a String representation of an enum. It's also possible to enumerate an Enum type. Although all is achievable I wouldn't recommend doing it this way as things are overcomplicated for such a single task.
How about you create a Dictionary collection of the items and simply use Exist method and some sort of error handling (or simple if/else statements) to check whether whatever user inputs in the input box exists in your list.
For instance:
Sub Main()
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
myList.Add "item1", 1
myList.Add "item2", 2
myList.Add "item3", 3
Dim userInput As String
userInput = InputBox("Type something:")
If myList.Exists(userInput) Then
MsgBox userInput & " exists in the list"
Else
MsgBox userInput & " does not exist in the list"
End If
End Sub
Note: If you add references to Microsoft Scripting Runtime library you then will be able to use the intelli-sense with the myList object as it would have been early bound replacing
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
with
Dim myList as Dictionary
Set myList = new Dictionary
It's up to you which way you want to go about this and what is more convenient. Note that you don't need to add references if you go with the Late Binding while references are required if you want Early Binding with the intelli-sense.
Just for the sake of readers to be able to visualize the version using Enum let me demonstrate how this mechanism could possibly work
Enum EList
item1
item2
item3
[_Min] = item1
[_Max] = item3
End Enum
Function ToString(eItem As EList) As String
Select Case eItem
Case EList.item1
ToString = "item1"
Case EList.item2
ToString = "item2"
Case EList.item3
ToString = "item3"
End Select
End Function
Function Exists(userInput As String) As Boolean
Dim i As EList
For i = EList.[_Min] To EList.[_Max]
If userInput = ToString(i) Then
Exists = True
Exit Function
End If
Next
Exists = False
End Function
Sub Main()
Dim userInput As String
userInput = InputBox("type something:")
MsgBox Exists(userInput)
End Sub
First you declare your List as Enum. I have added only 3 items for the example to be as simple as possible. [_Min] and [_Max] indicate the minimum value and maximum value of enum (it's possible to tweak this but again, let's keep it simple for now). You declare them both to be able to iterate over your EList.
ToString() method returns a String representation of Enum. Any VBA developer realizes at some point that it's too bad VBA is missing this as a built in feature. Anyway, you've got your own implementation now.
Exists takes whatever userInput stores and while iterating over the Enum EList matches against a String representation of your Enum. It's an overkill because you need to call many methods and loop over the enum to be able to achieve what a simple Dictionary's Exists method does in one go. This is mainly why I wouldn't recommend using Enums for your specific problem.
Then in the end you have the Main sub which simply gathers the input from the user and calls the Exists method. It shows a Message Box with either true or false which indicates if the String exists as an Enum type.
Just use the Select Case with a list:
Select Case entry
Case item1,item2, ite3,item4 ' add up to limit for Case, add more Case if limit exceeded
do stuff for being in the list
Case Else
do stuff for not being in list
End Select