I have created this class,
Interval.cls
public x as new collection
public y as new collection
public z as string
I wish to loop through the properties because I want the user to choose input x,y,z in the form so at my sub
sub test()
dim inter as new Intervals
inter.x.add "a"
inter.x.add "a"
inter.x.add "b"
inter.x.add "b"
inter.x.add "b"
userString1 = x
userString2 = a
' I want to make it dynamic so that whatever the user wants i can provide the results.
'i just want to make it possible to compare the userString to my properties
for each i in inter
'so i wish for i in this loop to be my properties x,y,z so i can make the if statement
if ( i = userString1) then
end if
next i
end sub
I know i can maybe make a tweek in the class to make it iterable, i don't know how to do it
any help is appreciated
'in class
Public Property Get Item(i As Integer) As Variant
Select Case ndx
Case 1: Item = Me.x
Case 2: Item = Me.y
Case 3: Item = Me.z
End Select
End Property
'in sub
Dim c as Collection
Dim s as String
For i = 1 to 3
if i < 3 then
set c = inter.Item(i)
'iterate through collection
else
s = inter.Item(i)
end if
next i
something like this is probably the easiest way to go, i didnt test it but hopefully it at least gets you started
Is this the sort of thing you are after?
'in class
Public x As New Collection
Public y As New Collection
Public z As String
Public Property Get Item(i As Integer) As Variant
Select Case i
Case 1: Item = Me.x
Case 2: Item = Me.y
Case 3: Item = Me.z
End Select
End Property
Sub try()
Dim userString2 As String
Dim userString1 As String
Dim inter As Interval
Dim i As Integer
Set inter = New Interval
inter.x.Add "a"
inter.x.Add "a"
inter.x.Add "b"
inter.x.Add "b"
inter.x.Add "b"
userString2 = "aabbb"
For i = 1 To inter.x.Count
userString1 = userString1 & inter.x.Item(i)
Next i
If userString1 = userString2 Then
'<do whatever>
End If
End Sub
OK how about forgetting the class and just using an array? The array of strings can be of any length and you can dynamically control it's size by the calling procedure.
Sub tryWithArray(ByRef StringArray() As String)
Dim userString2 As String
Dim i As Integer
userString2 = "b"
For i = 1 To UBound(StringArray())
If userString2 = StringArray(i) Then
'do something
End If
Next i
End Sub
I know this is an old post, but I wanted to share a solution I came up with when I had a similar issue.
VBA doesn't seem to offer any abstraction that allows you to refer to class members without naming them directly.
However, there is a way to loop through the members of a class in code. It’s kind of clunky, and it adds overhead, but makes it possible to address some or all of the members of your class from one or more loops without having to hard-code the property names every time. It might be useful if you have to step through the class from multiple places in your code.
Say you have a class with three properties (string A, integer B and double C), defined as usual with Property Let/Get statements, and private backing fields.
Now, add a function to your class definition that uses a named argument to return each of the values from the class itself.
Public Function MyValue (MyName As String) As Variant
Select Case MyName
Case “ValueA”
MyValue = Me.A
Case “ValueB”
MyValue = Me.B
Case “ValueC”
MyValue = Me.C
End Select
End Function
From your main code, you can now just pass in the string argument to get the desired value from the class. By creating a variant array of the desired name arguments in your code, you can loop through the array and retrieve the values from any instance of your class.
Dim VarList as Variant
Dim IntCounter as Integer
VarList = Array(“ValueB”, “ValueC”, “ValueA”)
For IntCounter = 0 to UBound(VarList)
Debug.Print MyClassInstance.MyValue(Cstr(VarList(IntCounter)))
Debug.Print MySecondClassInstance.MyValue(Cstr(VarList(IntCounter)))
Next IntCounter
This allows you to loop through as many or as few of the values from your class as you need, in any order, by just changing the order of the arguments in the array. (You would need to create a similar class function if you wanted to assign incoming values to the instance fields.)
As I said, it’s not perfect: it adds overhead, and unless all of the properties in your class happen to be the same type, it requires the function to return variant values, possibly forcing the caller to cast the returned values. But if you have to deal with numerous property values in multiple places in your code, it can be considerably less complicated than hard-coded direct assignments for each property name.
Related
User-Type is the kind of structure. I'm wondering if i can have sub delaration inside it. Something like:
Type myType
myParam1 As String
myParam2 As Long
...
Sub mySub(param)
'here I want some code for printing/showing params value
End Sub
End Type
I ask because i have problem with printing value of the myParam, when data are in an array of items of myType. After populating myArr with myType items, statment
print myArr(i).myParam1
gives me empty string.
In order to create an array of a declared type, be sure to use the Dim As New syntax.
Type myType
myParam1 As String
myParam2 As Long
End Type
Sub mySub
Dim myArr(2) As New myType
myArr(0).myParam1 = "A"
myArr(0).myParam2 = 1
myArr(1).myParam1 = "B"
myArr(1).myParam2 = 2
For i = 0 to Ubound(myArr) - 1
Print myArr(i).myParam1
Next
End Sub
As for adding subroutines in a Type statement, it is not in the documentation. On the other hand, that's an integral part of Python, one of the most popular LibreOffice scripting languages.
class myClass:
myAttr1 = ""
myAttr2 = 0
def myFunc(self, param):
self.myAttr2 = param
print(self.myAttr1)
myArr = [myClass(), myClass()]
myArr[0].myAttr1 = "A"
myArr[0].myAttr2 = 1
myArr[1].myAttr1 = "B"
myArr[1].myAttr2 = 2
for myObj in myArr:
myObj.myFunc(3)
Trying to loop through the results of a stored procedure using entity framework. I need to compare the list of communities to the value in a textbox so the user does not enter duplicate communities in the database, using a duplicate flag. I can retrieve my list of communities but I'm having difficulty looping through that list.
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each n As String In list.CommunityName
If n = txtCommunities.Text Then
duplicate = True
End If
Next n
While debugging I can hover over Accommodations.GetCommunities and see all of the values I need to loop through under the field "CommunityName" but when I step through the loop, the value for n shows up as a single character. Is there a way to turn this result set into a list so that I can loop through each value under "CommunityName"
I've also tried the below code and it sets com equal to the name of the complex type for some reason, but it properly loops through the correct number of items in the list. How can I extract that field to compare it to the textbox?
Dim duplicate = False
Dim com As String = String.Empty
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
com = list(i).ToString
If com = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
FINAL EDIT:
This is the function I used after. Used the String.Compare() method to compare the strings to ignore the case as well.
Private Function checkDuplicates()
Dim duplicate = False
Dim list = Accommodations.GetCommunities 'Put items in a list
For i As Integer = 0 To list.count - 1 'loop through the list
If String.Compare(list(i).CommunityName, txtCommunities.Text, True) = 0 Then 'Compare the two strings, comparrison is not case sensitive
duplicate = True 'set dup flag to true
Exit For 'exit loop
End If
Next
Return duplicate
End Function
In the first case you are comparing n to each character in CommunityName. You should do something like:
For Each n As String In list
If n.CommunityName = txtCommunities.Text Then
In the second case list(i) is a community in the list. Thus list(i).ToString() shows the name of the variable.
I think it should look something more like
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each community As [something] In list
If community.CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next n
or
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
If list(i).CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
Side note: Try to use good variable names instead of just n. Also, when you find a duplicate, you can exit for loop or just return True right away.
Sample 1
Ther is also option using dictionary, If u have your list as a dictionary u don't need looping after if u wana check add or remove by unique key value
Public Class TestClass
Property Name As String
End Class
Private Function TestFun() As Boolean
'Sample List to convert use Accommodations.GetCommunities
Dim List As New List(Of TestClass)
List.Add(New TestClass With {.Name = "a"})
List.Add(New TestClass With {.Name = "b"})
'If at Begining all elements by key are unique u can convert to dictionary
'
'From This Point u can map your list in to dictionary Use Accommodations.GetCommunities instant List. and type of what use in this collection replece as TestClass
Dim CheckInDictionary As Dictionary(Of String, TestClass) = List.ToDictionary(Function(p) p.Name, Function(p) p)
' After you can us if some key or Id exist
Return CheckInDictionary.ContainsKey("a")
End Function 'Return True
Sample 2
Ther is Also Another Possibility to Check if List have duplicate, but this is abit more complex.
List have method Contains that check if element exist in said list, so before add new element you can check if list have it. To Compare is used Method Equal so if you overide it in your base class you can do special rules for equal.
Like
Public Class TestClass
Property Name As String
Public Overrides Function Equals(obj As Object) As Boolean
'IMPORTEND Input is as object if anny case will be somthing diffrent then this type it can meak exception
If DirectCast(obj, TestClass).Name = Me.Name Then Return True
Return MyBase.Equals(obj)
End Function
End Class
and after when u atempt to add new element u dolike that
Dim NewEle = New TestClass With {.Name = "a"}
If Not List.Contains(NewEle) Then
List.Add(NewEle)
End If
I have some two column data that I read from a file and put into a list then sort alphabetically.
//The file
Hummus,0.75
Chili,0.50
Tabouli,1.25
Tzatziki,0.50
//Declaring the variables and public properties
Dim extraList As List(Of extra)
Public Class extra
Implements IComparable(Of extra)
Public Property Name As String
Public Property Price As Decimal
'Public Property extraList As List(Of extra)
Public Function CompareTo(other As extra) As Integer Implements IComparable(Of extra).CompareTo
Return Me.Name.CompareTo(other.Name)
End Function
End Class
//Puts the data into a list and sorts it
Sub Get_Extras_List()
'Reads the extras file and puts the information into a list, splitting the name of the extra and the price into separate columns
Dim allExtras = From line In System.IO.File.ReadLines("C:\Users\ExtrasList.txt")
Let Columns = line.Split(","c)
Where Columns.Length = 2
Let Price = Decimal.Parse(Columns(1).Trim())
Let Name = Columns(0).Trim()
Select New extra With {.Name = Name, .Price = Price}
extraList = allExtras.ToList()
'Sort the list alphabetically
extraList.Sort()
End Sub
Now I need to code a method that allows the user to type in an extra and search for it using a binary search to see if it exists. So far I have tried this but it just doesn't work and even if it did how do I get it to return a true or false value? (If it exists or not?)
Sub Search_Extras_List()
Dim strSearchExtra As String = Console.ReadLine()
Console.WriteLine(vbLf & "BinarySearch for '{0}':", strSearchExtra)
Dim index As Integer =
List(Of extra).BinarySearch(extraList.Name, strSearchExtra)
End Sub
Finally I have to get the user to choose one of the extras and then add the price of it to the total price. How do I refer to the price? extraList.Price? extra.Price? etc.
If you want to do a binary search like that, you will need to write a comparer.
Referring to List(Of T).BinarySearch Method (T, IComparer(Of T)), it could be
Public Class ExtraComparer
Implements IComparer(Of extra)
Public Function Compare(ByVal x As extra, ByVal y As extra) As Integer Implements IComparer(Of extra).Compare
If x Is Nothing Then
If y Is Nothing Then
' If x is Nothing and y is Nothing, they're equal.
Return 0
Else
' If x is Nothing and y is not Nothing, y is greater.
Return -1
End If
Else
' If x is not Nothing...
If y Is Nothing Then
' ...and y is Nothing, x is greater.
Return 1
Else
' ...and y is not Nothing, compare the names of the two extras.
'
Return x.Name.CompareTo(y.Name)
End If
End If
End Function
End Class
Which you can test with
' Get some item from the data so we know it is present...
Dim a = extraList(2).Name
Dim lookFor As New extra With {.Name = a}
Dim idx = extraList.BinarySearch(lookFor, New ExtraComparer)
Console.WriteLine($"Index of {a} is {idx}.")
(although you would probably want to do a case-insensitive string comparison).
If the index returned is negative then the item was not found. (See the documentation linked to above for more details.)
However, you might find it easier to use LINQ:
Dim a = extraList(2).Name
Dim chosenExtra = extraList.FirstOrDefault(Function(x) x.Name.Equals(a, StringComparison.CurrentCultureIgnoreCase))
If chosenExtra IsNot Nothing Then
Console.WriteLine($"User chose {chosenExtra.Name} at a price of {chosenExtra.Price}")
Else
Console.WriteLine($"User's choice of ""{a}"" was not found.")
End If
I used a case-insensitive comparison in there.
Or Just present the user with a dropdown of the available extras so that they don't have to type it.
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
I'm looking at a legacy VB6 app, and trying to understand how VB6 Collections work. Using the Collection.Add method, I am finding that the collection just exists of its last added option, repeated. e.g. if I add 1,2,3,4,and 5 to a collection, I will get 5, 5, 5, 5, and 5 back as the collection contents.
In my test case, I have an encapsulation class module, EncapsulationClass.cls that stores some simple strings. Its implementation:
Option Explicit
'ivars
Private pEntityId As String
Private pEntityName As String
'properties
'pEntityId
Public Property Get entityId() As String
Let entityId = pEntityId
End Property
Private Property Let entityId(ByVal inEntityId As String)
Let pEntityId = inEntityId
End Property
'pEntityName
Public Property Get entityName() As String
Let entityName = pEntityName
End Property
Private Property Let entityName(ByVal inEntityName As String)
Let pEntityName = inEntityName
End Property
'constructor
Public Sub init(ByVal inEntityId As String, ByVal inEntityName As String)
Let entityId = inEntityId
Let entityName = inEntityName
End Sub
I want to store several instances of these in an iterable object, so I used a Collection.
In my test case, I have this simple function:
Private Function getACollection() As Collection
Dim col As New Collection
Dim data(0 To 5) As String
data(0) = "zero"
data(1) = "one"
data(2) = "two"
data(3) = "three"
data(4) = "four"
data(5) = "five"
For Each datum In data
Dim encap As New EncapClass
encap.init datum, datum & "Name"
col.Add encap
Next
'return
Set getACollection = col
End Function
This function is then used in the following simple logic:
Private Sub Form_Load()
Dim col As Collection
Set col = getACollection()
For Each i In col
Debug.Print i.entityId, i.entityName
Next i
End Sub
I would expect the output to be:
one oneName
two twoName
three threeName
four fourName
five fiveName
However, instead, I simply get a repetition of the last element added, five times.
five fiveName
five fiveName
five fiveName
five fiveName
five fiveName
Is there something I'm missing, syntactically? Looking through various books, collections are appended with the Add method, and work as expected.
The lack of a set is effectively reusing the same single instance of encap so changes within the loop modify the single repeated reference already in the collection.
To fix:
Dim encap As EncapClass
For Each datum In data
set encap = New EncapClass
encap.init datum, datum & "Name"
col.Add encap
Next