Is it possibile to include Sub inside the user-Type in openoffice? VBA - vba

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)

Related

How to set 2 arguments on Let function in a class in vba (for excel)?

I am creating a Class in vba (for excel) to process blocks of data. After some manipulation of a text file I end up with blocks of data (variable asdatablock() ) which I want to process in a For Loop
I created my own Class called ClDataBlock from which I can get key data by a simple call of the property required. 1st pass seems to work and I am now trying to expand my Let function to 2 argument but it’s not working. How do I specify the 2nd argument?
Dim TheDataBlock As New ClDataBlock
For i = 0 to UBound(asdatablock)
asDataBlockLine = Split(asdatablock(i), vbLf) ‘ split block into line
TheDataBlock.LineToProcess = asDataBlockLine(5) ‘allocate line to process by the class
Dvariable1 = TheDataBlock.TheVariable1
‘and so on for the key variables needed base don the class properties defined
Next i
In the Class Module the Let function takes 2 arguments
Public Property Let LineToProcess(stheline As String, sdataneeded As String)
code extract of what I am looking at -
'in the class module
Dim pdMass As Double
Private pthelineprocessed As String
Public Property Let LineToProcess(stheline As String, sdataneeded As String)
pthelineprocessed = DeleteSpaces(Replace(stheline, vbLf, ""))
Dim aslinedatafield() As String
Select Case sdataneeded
'THIS IS AN EXTRACT FROM THE FUNCTION
'THERE ARE AS NUMBER OF CASES WHICH ARE DEALT WITH
Case Is = "MA"
aslinedatafield() = Split(pthelineprocessed, " ")
pdbMass = CDbl(aslinedatafield(2))
End select
End function
Public Property Get TheMass() As Double
TheMass = pdMass
End Property
'in the "main" module
Dim TheDataBlock As New ClDataBlock
For i = 0 to UBound(asdatablock)
TheDataBlock.LineToProcess = asDataBlockLines(5) 'Need to pass argument "MA" as well
dmass = TheDataBlock.TheMass
'and so on for all the data to be extracted
Next i
When a Property has 2 or more arguments, the last argument is what is getting assigned. In other words, the syntax is like this:
TheDataBlock.LineToProcess("MA") = asDataBlockLine(5)
This means you need to change the signature of your property:
Public Property Let LineToProcess(sdataneeded As String, stheline As String)

VB.Net | Is there a way to reference a dynamic amount of variables as arguments to function/sub?

I'm trying to pass a dynamic amount of variables to a Sub by using ByRef;
Essentially I'm trying to create a module that I can easily import into my projects and make handling the file saving/loading process automated.
The Sub/Function would take a number of variables as references and then loop through them changing each one's value.
I realize I'm missing a crucial point in how visual basic's syntax works but I haven't been able to figure out what I need to do.
The code I've written for this is:
Public Sub LoadSaveToVars(ByRef KeyNamesAndVars() As Object, ByVal FileLoc As String = "")
If isEven(KeyNamesAndVars.Length) Then
Dim Contents As String = My.Computer.FileSystem.ReadAllText(FileLoc)
Dim isOnName As Boolean = True
Dim CurrentVal As String = ""
For i = 0 To KeyNamesAndVars.Length - 1
If isOnName Then
CurrentVal = GetStringValue(KeyNamesAndVars(i), Contents) 'Get the value of the key with the key name in the array
isOnName = False
Else
KeyNamesAndVars(i) = CurrentVal 'Set the variable referenced in the array to the value
isOnName = True
End If
Next
Else
Throw New ArgumentOutOfRangeException("The key names and variables supplied are not even.", "Error loading to variables!")
End If
End Sub
And here's how I try to use this function:
Dim TestVar1 As String = ""
Dim TestVar2 As String = ""
LoadSaveToVars({"key1", TestVar1, "key2", TestVar2})
To keep this question clean I did not include the other functions, but I did make a poor attempt at drawing what I want to happen: https://gyazo.com/eee34b8dff766401f73772bb0fef981a
In the end, I want TestVar1 to be equal to "val1" and TestVar2 to be equal to "val2" and to be able to extend this to a dynamic number of variables. Is this possible?

Looping through Entity Framework complex type

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

Loop through properties of a class in vba

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.

Syntax options creating errors in VBA Macro for Excel

I'm having some trouble with syntax options while writing a VBA Macro for Excel. In VBA you can call a method on an object in two different ways:
foo.bar(arg1, arg2)
or
foo.bar arg1, arg2
I absolutely detest the second sort of syntax because I find it lacks any sort of clarity, so I normally adhere to the first option. However, I've come across a situation where using the first option creates an error, while the second executes fine. (This may perhaps be an indicator of other problems in my code.) Here is the culprit code:
Function GetFundList() As Collection
Dim newFund As FundValues
Range("A5").Select
Set GetFundList = New Collection
While Len(Selection.Value)
Set newFund = New FundValues
' I set the fields of newFund and move Selection
The problem is in this next line:
GetFundList.Add newFund
Wend
End Function
FundValues is a class I created that is essentially just a struct; it has three properties which get set during the loop.
Basically, when I call GetFundList.Add(newFund) I get the following error:
Run-time error '438':
Object doesn't support this property or method
But calling GetFundList.Add newFund is perfectly fine.
Does anyone understand the intricacies of VBA well enough to explain why this is happening?
EDIT: Thanks much for the explanations!
Adding items to a collection is not defined as a function returning a value, but as a sub routine:
Public Sub Add( _
ByVal Item As Object, _
Optional ByVal Key As String, _
Optional ByVal { Before | After } As Object = Nothing _
)
When calling another sub routine by name and sending arguments (without adding the "Call" statement), you are not required to add parentheses.
You need to add parentheses when you call a function that returns a value to a variable.
Example:
Sub Test_1()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
fTest_1 iCnt, iResult, iCnt_B
End Sub
Public Function fTest_1(iCnt, iResult, iCnt_B)
iResult = iCnt * 2 + iCnt_B * 2
End Function
Sub Test_2()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
iResult = fTest_2(iCnt, iCnt_B)
End Sub
Public Function fTest_2(iCnt, iCnt_B)
fTest_2 = iCnt * 2 + iCnt_B * 2
End Function
Let me know if not clear.
This Daily Dose of Excel conversation will be helpful.
When you use the parentheses you are forcing VBA to evaluate what's inside them and adding the result to the collection. Since NewFund has no default property - I assume - the evaluation yields nothing, so can't be added. Without the parentheses it evaluates to the instance of the class, which is what you want.
Another example. This:
Dim coll As Collection
Set coll = New Collection
coll.Add Range("A1")
Debug.Print coll(1); TypeName(coll(1))
and this ...
coll.Add (Range("A1"))
Debug.Print coll(1); TypeName(coll(1))
... both yield whatever is in A1 in the debug.window, because Value is Range's default property. However, the first will yield a type of "Range", whereas the type in the 2nd example is the data type in A1. In other words, the first adds a range to the collection, the 2nd the contents of the range.
On the other hand, this works:
Dim coll As Collection
Set coll = New Collection
coll.Add ActiveSheet
Debug.Print coll(1).Name
... and this doesn't:
coll.Add (ActiveSheet)
Debug.Print coll(1).Name
because ActiveSheet has no default property. You'll get an runtime error 438, just like in your question.
Here's another way of looking at the same thing.
Let assume that cell A1 contains the string Hi!
Function SomeFunc(item1, item2)
SomeFunc = 4
End Function
Sub Mac()
' here in both of the following two lines of code,
' item1 will be Variant/Object/Range, while item2 will be Variant/String:
SomeFunc Range("A1"), (Range("A1"))
Let i = SomeFunc(Range("A1"), (Range("A1")))
'this following is a compile syntax error
SomeFunc(Range("A1"), (Range("A1")))
' while here in both the following two lines of code,
' item1 will be Variant/String while item2 will be Variant/Object/Range:
SomeFunc ((Range("A1")), Range("A1")
Let j = SomeFunc((Range("A1")), Range("A1"))
'this following is a compile syntax error
SomeFunc((Range("A1")), Range("A1"))
Set r = Range("A1") ' sets r to Variant/Object/Range
Set r = (Range("A1")) ' runtime error 13, type mismatch; cannot SET r (as reference) to string "Hi!" -- Strings are not objects in VBA
Set r = Range("A1").Value ' runtime error (same)
Let r = Range("A1") ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value during let = assignment
Let r = (Range("A1")) ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value by extra ()'s
Let r = Range("A1").Value ' set r to "Hi!" by explicit use of .Value
End Sub
I only add this to help illustrate that there are two things going on here, which could be conflated.
The first is that the () in an expression that converts the item to its Value property as stated above in other answers.
The second is that functions invoked with intent to capture or use the return value require extra () surrounding the whole argument list, whereas functions (or sub's) invoked without intent to capture or use the return value (e.g. as statements) must be called without those same () surrounding the argument list. These surrounding () do not convert the argument list using .Value. When the argument list has only one parameter, this distinction can be particularly confusing.