Not able to declare array as public VBA - arrays

When I tried to declare the array as public from the Main Sub() in Sheet1 it's throwing a compile error:
"Arrays not allowed as Public Members of object Modules".
What I am trying to do is declare an array whose elements can be calculated/manipulated from any modules added in VBA project.
Public Segment() As String
Public SegCount As Integer
Sub EDI()
'calls to subroutines placed in modules
End Sub

the usual workaround woud be using a variant.
Public MyArray as Variant
What can go wrong with that ? well a lot , so preferably pass arrays as arguments (to subs byval a() as string , BUT to functions you can only byval a as variant (could be a byref if you get an error))
note : works too (inside a module):
Option Explicit
Public rott() As String 'for a dynamyc array
'Public rott(1 to 10) As String 'for a fixed size array
Sub ff()
ReDim rott(1 To 10) 'or redim preserve rott (1 to 10) if you want to keep values (note you can only redilm the last dimension of the array).
Debug.Print rott(1)
End Sub
note 2 : anything stoping the code would reset the public values....

Insert a standard coding Module - it will become a Module1 then stick Public Segment() As String on top of that module... such as

Related

VBA ByRef argument type mismatch passing varaint array between subs

I have code which reads data from one workbook and uses them in another. I put the data into a 4D Variant Array. It gets passed around between subs using ByRef. When I come to access an element I get
Compile error: ByRef argument type mismatch
If I add importArray to Watchlist in the context of OneIterationOfTask() and then break the code the variant array is still populated with data. How can I get a value out of a specific index e.g. importArray(1,2,3,4)?
'Called when user clicks button - Variant Array okay here
Public Sub WriteValues()
Dim importArray() As Variant
importArray = getImportArray()
useImportArray importArray
End Sub
'Gets values from another workbook - Variant Array okay here
Private Function getImportArray() As Variant
Dim importArray() As Variant ' 4D array of strings stored as variants
[...Get data...]
getImportArray = importArray' Return array
End Sub
'Decides how to use import array - Variant Array okay here
Private Sub useImportArray (ByRef importArray())
oneIterationOfTask importArray
End Sub
'Repetitive code - Variant Array ByRef argument type mismatch.
Private Sub OneIterationOfTask (ByRef importArray())
WriteStringOutput importArray(1,2,3,4) 'Okay if I comment out this line
End Sub
The problem it turns out was not with Variant arrays, despite appearances. I tried using importArray(1,2,3,4) like a String, which is okay for my debugging lines like
MsgBox importArray(0, 0, 1, 0) 'Test accessing array
This was not okay for other calls, though.
The problem was solved by using CStr(importArray(0, 0, 1, 0)) to convert Variants to Strings, which I should have been doing anyway.

Storing & recovering 2-dimensional Array in a Class

I just recently moved from VB6 to VB.NET and I'm recoding an old app. So I'm pretty unexperienced with .NET so far.
I have multiple (lets say 4 in this code example) twodimensional string arrays (or actually an array of arrays) which I want to store as a ComboBox items ie. one twodimensional array is one item.
Public Class MyItem
Private sName As String
Private sArr As Array()
Public Sub New(ByVal Name As String, ParamArray Arr As Array())
sName = Name
sArr = Arr
End Sub
Public Property Arr() As Array()
Get
Return sArr
End Get
Set(ByVal sValue As Array())
sArr = sValue
End Set
End Property
Public Overrides Function ToString() As String
Return sName
End Function
End Class
---
Dim sMyArray as Array()
For i As Integer = 0 to 3
sMyArray = Nothing ' resetting the array before refilling it
'
' No code here but filling sMyArray by reading a text file, each line
' number as dim 1 and splitted each line into dim 2 with ";" using Split(sRead, ";")
' so Debub.Print(sMyArray(0)(0)) prints the beginning of the first line until first ";" <- this works fine
'
' Then passing sMyArray to a ComboBox item
'
ComboBox.Items.Add(New MyItem("item" & i, sMyArray))
Next i
The problem is that when recovering the arrays from ComboCox items only the last ComboBox item has array data. So for example
Dim sMyNewArray As Array() = ComboBox.Items.Item(0).Arr
Debug.Print(sMyNewArray(0)(0))
throws an error while
Dim sMyNewArray As Array() = ComboBox.Items.Item(3).Arr
Debug.Print(UBound(sMyNewArray(UBound(sMyNewArray))))
does not and prints the last item's last row's ubound
Can anyone figure out what is it I'm missing or tell me a better way to do this? I'm pretty sure there is one..
I'm not 100% sure, but I think the problem is in this section:
Dim sMyArray as Array()
For i As Integer = 0 to 3
sMyArray = Nothing ' resetting the array before refilling it
Arrays are technically reference types, but like strings, there's some extra compiler magic to make them feel at times more like value types, and I have a sense in this case the actual sMyArray reference was used (perhaps because of a ParamArrays optimzation), such that setting it to Nothing broke things. The more idiomatic way to write this code for .Net it like this:
For i As Integer = 0 to 3
Dim sMyArray as Array()
.Net has a much more sophisticated garbage collector than was available for VB6. We don't often set variables to Nothing any more, but instead just re-assign them or let them fall out of scope. In fact, setting a variable to Nothing can in rare cases be actively harmful. Moreover, we want to see the Dim keyword inside the loop, so you're working with a different variable on each iteration, with the smallest possible scope.
While I'm here, in .Net we pretty much never use the base Array type. Instead of this:
Private sArr As Array()
You pretty much always do this:
Private arr As String()()
or this, for true two-dimensional (non-jagged) arrays:
Private arr As String(,)
or, best of all, this:
Private arr As New List(Of String())
Since VB.Net has more collection types than just array.
Also, I don't have the link handy, but Microsoft's coding guidelines now explicitly ask you not to use hungarian warts for variable and class names (so sArr can just be arr). This is a change from the VB6 era because of changes to the language where the type is more likely to be implicit with the variable and improvements to the tooling, where the prefixes usually no longer add much utility and have been shown to hurt readability.
Not really sure why you have a 2 dimensional array, but here is a small sample NOT using the Array type. It uses just plain strings and string arrays. Let me know if this helps. This splits a few strings, then reads out the results after populating.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim sMyArray()() As String
Dim line1 As String = "a;b;c;d 1;2;3;4;5"
Dim line2 As String = "z;x;y;w 99;65;32;21;18"
sMyArray = ParseString(line1)
cboBox1.Items.Add(New MyItem("System0", sMyArray))
sMyArray = ParseString(line2)
cboBox1.Items.Add(New MyItem("System1", sMyArray))
For i As Integer = 0 To cboBox1.Items.Count - 1
For j As Integer = 0 To UBound(cboBox1.Items(i).arr)
For k As Integer = 0 To UBound(cboBox1.Items(i).arr(j))
Debug.Write(cboBox1.Items(i).arr(j)(k) & " ")
Next
Next
Debug.WriteLine("")
Next
End Sub
Private Function ParseString(s As String) As String()()
Dim rows As String() = s.Split(" ")
Dim matrix As String()() = New String(rows.Length - 1)() {}
For i As Integer = 0 To rows.Length - 1
matrix(i) = rows(i).Split(";")
Next
Return matrix
End Function
End Class
Public Class MyItem
Private sName As String
Private sArr As String()()
Public Sub New(ByVal Name As String, ByVal ParamArray Arr As String()())
sName = Name
sArr = Arr
End Sub
Public Property Arr() As String()()
Get
Return sArr
End Get
Set(ByVal sValue As String()())
sArr = sValue
End Set
End Property
Public Overrides Function ToString() As String
Return sName
End Function
End Class

VBA Dynamic array : change size and use of Get Let Property

The problem: I would like to create a dynamic array that change size when an event occurs (Event Calculate), by preserving its content and extending by one its size. Let's say I want to do this for a vector of double (and also a date type). The data is updating in a specific cell.
My understanding: I am coding in the Event "Calculate of Excel". I cannot use "Public", I have to declare the array as private and then use Get and Let Property... But, how can I use Redim Preserve in this case ? Also, I think I am probably missing some points on how it has to be used: Here s a sample of the code I wrote:
Name of the Classe: "Class1"
Code of the class:
Code:
Option Explicit
Private IntradayValueSerie1() As Double 'Dynamic vector which will contain Y1 Values
Public Counter As Long
Public Property Let vIntradayValueSerie1(ByVal Counter_Value As Long)
IntradayValueSerie1(Counter_Value) = Sheets("Sheet1").Range("C5")
End Property
Public Property Get vIntradayValueSerie1(ByVal Counter_Value As Long) As Variant
vIntradayValueSerie1(Counterr) = IntradayValueSerie1
End Property
So I want "Let" To attribute the new value of my extended array
I want "Get" to return the array (extended and updated)
Remark: Counter will be updated and increase at then end of the in the section Event "Worksheet.Calculate"
Test code (HAS TO BE in the section Event "Worksheet.Calculate")
Code:
counter = 1
Dim Serie1 As New Class1
Serie1.IntradayValueSerie1(Counter) = ??? I don't know how to use the property to initialize the vector
counter = counter +1
ReDim Preserve IntradayValueSerie1(Counter)
Also, As I want to return an array, do I have to set Variant for the Get property ? As you can see, some points make me confused, either on the use and the structure.
Thank you for you time !
edited to match the following assumptions
a Sub in any of your modules initializes and uses a variable of Class1 type
I'll call it after Sub ExploitClass1(), but you can rename it as you like
that Sub writes into any cell of the relavant worksheet whose calculate event you want to use to update the dynamic array property of your variable of Class1 type
I'll assume that relevant worksheet is named after "CalculateClass": you can rename it as you like but be sure to fill its code pane with what you'll find in "your relevant worksheet code pane" section of this answer
then proceed like follows:
your Class1 code pane
Option Explicit
Private IntradayValueSerie1() As Double 'Dynamic array which will contain Y1 Values
Private counter As Long '<-- counter to track the size of the dynamic array
Public Sub WriteValue(ByVal Value As Variant) '<-- class method to write a value in the last dynamic array slot
IntradayValueSerie1(counter) = Value
Extend
End Sub
Private Sub Extend() '<-- class method to extend dynamic array size by one
counter = counter + 1 '<-- update the dynamic array size counter by one
ReDim Preserve IntradayValueSerie1(1 To counter) '<-- increase the dynamic array size
End Sub
Private Sub Class_Initialize()
counter = 1
ReDim IntradayValueSerie1(1 To counter) '<-- at class instantiating, initialize the dynamic array
End Sub
'-----------------------------------------------
' added methods to "query" some dynamic array related values
'-----------------------------------------------
Public Function GetCounter() As Long '<-- class method to retrive the current counter (i.e. the dynamic array size) value
GetCounter = counter '<-- return counter
End Function
Public Function GetPenultimateArrayValue() As Variant '<-- class method to retrive the current counter (i.e. the dynamic array size) value
GetPenultimateArrayValue = IntradayValueSerie1(counter - 1) '<-- return dynamic array one before second to last element
End Function
your Sub
Option Explicit
Public Serie1 As Class1 '<-- declare a Public variable of type Class1
Sub ExploitClass1()
Set Serie1 = New Class1 '<-- instantiate a new public object of type Class1
Worksheets("CalculateClass").Range("A1") = 1 ' make something that triggers calulate event in the relevant worksheet: in this case I had cell "A2" of that worksheet with a formula `= A1+1`
MsgBox Serie1.GetPenultimateArrayValue & " - " & Serie1.GetCounter 'show your Class1 dynamic array has been updated exploiting those "querying" methods we added at the bottom of your class
End Sub
your relevant worksheet code pane
it'll use the Public object of Class1 type we declared and initialized in ExploitClass1() sub
Option Explicit
Private Sub Worksheet_Calculate()
Serie1.WriteValue Worksheets("Sheet01").Range("C5").Value '<-- this will write the passed value to your class dynamic array last slot
End Sub

VBA: Arrays and Global Variable Declarations

I need to declare an array in VBA that will be used by every function. However, I cannot declare it as a global as I would do in C++.
My code is as follows:
Option Explicit
Dim test(0 to 10) as String
test(0) = "avds"
test(1) = "fdsafs"
....
The following conceptualizes what I am trying to do.
public function store() as boolean
Worksheets("test").cells(1,1) = test(0)
End Function
How can I achieve this functionality?
For global declaration, change Dim to Public like so:
Public test(0 to 10) as String
You can call this like (assuming it is in Module1, else change Module1 to whatever you've named it):
Module1.test(0) = "something"
Or simply:
test(0) = "something"
Why wouldn't you create everything in a class? That's the reason why classes where invented after all.
Consider the Class1 definition
Option Explicit
Private m_data() As String
Private Sub Class_Initialize()
ReDim m_data(0 To 10)
End Sub
Private Sub Class_Terminate()
Erase m_data
End Sub
Public Property Get Count() As Integer
Count = UBound(m_data) - LBound(m_data) + 1
End Property
Public Property Get Data(index As Integer) As String
Data = m_data(index)
End Property
Public Property Let Data(index As Integer, value As String)
m_data(index) = value
End Property
Public Function Store(rng As Range) As Boolean
Store = (rng.value = m_data(0))
End Function
You can add all the functions you want that can access your array just like Store().
with the test code in a worksheet of
Public Sub Test()
Dim c As New Class1
c.Data(0) = "January"
Debug.Print c.Store(Cells(1, 1))
End Sub
You can also cache the location of the cell where it is referencing, or used an assumed named argument and only supply a reference to the worksheet once after class initialization.
You can use the Public keyword to declare a variable that you need to access in any module.
Remember that in vba you cannot declare variables or code outside of procedures.
See here for more information
I have a recommendation that is a bit lighter than a class (although class is a great recommendation)
Option 1
Define your desired constant array as a delimited string constant:
Public Const cstrTest = "String 1;String 2; String 3; String 4; String 5; String 6"
Next, whenever you need it just use Split to create an array with minimal code:
Dim arrStrings
arrStrings = Split (cstrTest, ";")
Option 2
You might replace (or combine with Option 1) a simple public function
Public Function constStringArray() As String()
constStringArray = Split (cstrTest, ";")
End Function
So then, in use...
Dim arrStrings
'Option 1 example
arrStrings = Split (cstrTest, ";")
'Option 2 example
arrStrings = constStringArray()
one can do it (with global initialization) via Static Property quite straight-forward without creating a class or string parsing - as described in detail and with examples here

VBA - Returning array from Property Get

If arrays are returned by reference, why doesn't the following work:
'Class1 class module
Private v() As Double
Public Property Get Vec() As Double()
Vec = v()
End Property
Private Sub Class_Initialize()
ReDim v(0 To 3)
End Sub
' end class module
Sub Test1()
Dim c As Class1
Set c = New Class1
Debug.Print c.Vec()(1) ' prints 0 as expected
c.Vec()(1) = 5.6
Debug.Print c.Vec()(1) ' still prints 0
End Sub
You don't have a let property. Also, the get property is returning the entire array, rather than just the element in question. Change the return type of Property Get from Double() to just plain Double. Add Property Let. Note that it takes two inputs, but only one is passed to it. The last variable (MyValue, in this case) is assumed to get it's value from whatever is after the = sign. Put a break point somewhere early in Test1() and see how the values are affected in the Locals window. Compare the variables created by the original code versus my code:
'Class1 class module
Private v() As Double
Public Property Get Vec(index As Long) As Double
Vec = v(index)
End Property
Public Property Let Vec(index As Long, MyValue As Double)
v(index) = MyValue
End Property
Private Sub Class_Initialize()
ReDim v(0 To 3)
End Sub
' end class module
'Begin module
Sub Test1()
Dim c As Class1
Set c = New Class1
Debug.Print c.Vec(1) ' prints 0 as expected
c.Vec(1) = 5.6
Debug.Print c.Vec(1) ' prints 5.6
End Sub
'End module
In VBA, arrays are never returned by reference unless they are returned through a ByRef parameter. Furthermore, whenever you use = to assign an array to a variable, you've made a new copy of the array, even if you're assigning it to a ByRef argument inside of a procedure, so you're pretty much out of luck trying to make this work.
Some alternative are...
Use a VBA.Collection instead of an array.
Make your own class that encapsulates an array and exposes procedures for indirectly accessing and manipulating the internal array.
I want to suggest another nice way to do this using a Collection and a static Property without the need to use a class:
imagine you want to have the xlCVError enum as an array (or collection), e.g. to loop through it on errors and handle it based on the actual error.
The following is initialized once on access:
'from https://stackoverflow.com/a/56646199/1915920
Static Property Get XlCVErrorColl() As Collection
Dim c As Collection 'will be already initalized after 1st access
'because of "Static Property" above!
Set XlCVErrorColl = c
If Not c Is Nothing Then Exit Property
'initialize once:
Set c = New Collection
c.Add XlCVError.xlErrDiv0
c.Add XlCVError.xlErrNA
c.Add XlCVError.xlErrName
c.Add XlCVError.xlErrNull
c.Add XlCVError.xlErrNum
c.Add XlCVError.xlErrRef
c.Add XlCVError.xlErrValue
Set XlCVErrorColl = c
End Property
Turning this into an array or implementing it as an array is straight forward, but collections seem to be more useful to me, with the disadvantage that their elements are not implicitely typed/(compile-time-)type checked.
So this would e.g. turn it into an (read-only) array (with the in-mem-copy-disadvantage mentioned in other answers/comments):
'from https://stackoverflow.com/a/56646199/1915920
Static Property Get XlCVErrorArr() As XlCVError()
Dim a() As XlCVError
XlCVErrorArr = a
If UBound( a ) > 0 Then Exit Property
'initialize once:
Dim c As Collection: Set c = XlCVErrorColl
ReDim a(c.Count)
Dim i As Integer: For i = 1 To c.Count
a(i) = c(i)
Next i
XlCVErrorArr = a
End Function
So transforming the example from Clayton Ss answer into a static, modifiable module property using some array it would be:
'module (no class required)
'from https://stackoverflow.com/a/56646199/1915920
Private v() As Double
Static Property Get Vec(index As Long) As Double
If UBound(v) < 3 Then 'initialize once:
ReDim v(0 To 3) 'one could initialize it with anyting after here too
end if
Vec = v(index)
End Property
Public Property Let Vec(index As Long, MyValue As Double)
v(index) = MyValue
End Property

Resources