public array loaded from useform vba - arrays

Iam creating a macro, and I need to access the array that is created and filled in useform (button_click action) in module.
Private Sub CommandButton1_Click()
Dim tmojo As Worksheet
Dim mojocell As Range
Set tmojo = Sheets("table mojo")
colls = tmojo.Range("N1").End(xlToLeft).Column
i = 1
For Each cCont In Me.Controls
If TypeName(cCont) = "ComboBox" Then
If cCont.Enabled = True Then
If cCont.Value = Empty Then
MsgBox "you havent specified all the columns"
Exit Sub
End If
ReDim Preserve collname(i)
collname(i) = cCont.Value
i = i + 1
End If
End If
Next cCont
Call createregion
End Sub
I fill the array collname with values from multiple comboboxes (column names). Then I want to call createregion sub which is located in module and I want to access the values in collname().
im getting error saying that:
Constants, fixed-length strings, arrays, user-defined types, and Declare statements not allowed as Public members of an object module
I need to access this array in multiple subs, is there any workaround?
Thank you in forehand.

The proper way is to transfer your UserForm code into a regular module, and declare your array as public, on the top of the module, before every subs :
Public MyArr()
When you transfer your code, you will need to call the subs into your UserForm's events, and so change all the Me and Me. to the full name of your UserForm.
And if you are lacking time, you can simply declare on the top of the UserForm module :
Dim MyArr()

Related

VBA Array Public Method cant be modified by components

I'm working with a rather complex problem in VBA and I decided to structure it in different classes.
One of the classes containd an array and I need to modify the component individualy outside the class.
The problem can de reduced to the definition of the class
Public a As Variant
Private Sub Class_Initialize()
ReDim a(5) As Double
' Set some value
a(1) = 20
End Sub
And a sub to access the data:
Sub Test()
Dim b As New DummyTest
b.a(4) = 7
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
The result on the debug window is 20 0. I've checked this result with an inspection and confirm that the property a cannot be modified from the exterior of the class.
Funny part is that the property can be modified if replaced by other array, for example:
Sub Test2()
Dim b As New DummyTest
b.a = Array(1, 2, 3, 4, 5)
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
Behaves as expected. I'm confused as element access is allowed inside the class context and in any other situation, but in this specific case it doesn't wotk. There is no error msg, it simply refuses to change the content of the array.
I'll find work around solutions but missing this way to access data is being a really PITA.
Hope you can help me with this issue.
Thank you in advance!
Your "property" is breaking encapsulation, which pretty much defeats its entire purpose.
Public a As Variant
From a public interface standpoint, this is a read-write property (if you added a new class module with Implements DummyTest you'd have to add Property Get and Property Let members for it). From the class' standpoint, it's a public instance field.
When a class encapsulates an array, a collection, a dictionary, or any other data structure, the last thing you want is for that data structure to be publicly exposed, with anyone anywhere being able to just overwrite the class' entire internal state as it pleases - you're not getting any of the advantages of classes that way.
First step to proper encapsulation is to make the instance field Private. Next step is to expose actual Property accessors. You want the array data to be writable? Expose an indexed Property Let member for it.
Private a As Variant
Private Sub Class_Initialize()
ReDim a(0 To 10)
End Sub
Public Property Get Value(ByVal index As Long) As Variant
Value = a(index)
End Property
Public Property Let Value(ByVal index As Long, ByVal newvalue As Variant)
a(index) = newvalue
End Property
This code behaves exactly as advertised:
Public Sub Test()
Dim b As DummyTest
Set b = New DummyTest
b.Value(4) = 7
Debug.Print b.Value(1)
Debug.Print b.Value(4)
End Sub
Note that because the array is encapsulated, that client code is absolutely unable to re-assign the array itself: the array is abstracted away and only accessible through the means exposed by the class.
Reference variable in VBA must be declared, so that we know what type of object we are referring to
i.e. Dim b As DummyTest ' No new required in this step
Before using a declared reference to store information create an instance of the Class.
i.e. Set b = New DummyTest
This might seem confusing but is actually very logical. Because b is a Class the Dim statement sets up b to hold the address of a DummyTest object. At the time of the Dim statement no object has been created (no memory allocated) so the address of b is nothing (no value or null). The set statement tells VBA to create an instance of your DummyTest class (allocate the memory it needs) and puts the address of the instance (memory) into variable b. Then because VBA knows that b points to an instance of a class you never see the address of b when you use it, just the data to which b is pointing. You can get the the address b is holding using the function Objptr but this is only something you would do in very special/specific circumstances

Array as an argument in a procedure vb6 vs vb.net

Here it is a procedure in vb6 and it is working fine like the example included:
' Check_UnCheck
' check an array of some checkboxes, uncheck an array of another checkboxes
' Example of usage :
CheckBox.Check_UnCheck Array(chkCheck3, chkCheck5), Array(chkCheck1, chkCheck4)
Public Sub Check_UnCheck(ByRef CheckArray As Variant, ByRef UnCheckArray As Variant)
Dim i As Integer
Dim conControl As Control
For i = LBound(CheckArray) To UBound(CheckArray)
Set conControl = CheckArray(i)
conControl.Value = 1
Next
For i = LBound(UnCheckArray) To UBound(UnCheckArray)
Set conControl = UnCheckArray(i)
conControl.Value = 0
Next
End Sub
what is the equivalent in vb.net for the above procedure, the MSDN Documentation says :
We cannot use more than one parameter array in a procedure, and it must be the last argument in the procedure definition.
Try below code.
Look into comments for detailed description.
'DECLARE YOUR ARRAYS.
Dim array1 = New CheckBox() {CheckBox3, CheckBox5}
Dim array2 = New CheckBox() {CheckBox1, CheckBox4}
'CALL CHECK AND UNCHECK FUNCTION.
Check_UnCheck(array1, array2)
'YOUR FUNCTION DEFINITION.
Public Sub Check_UnCheck(ByRef CheckArray As CheckBox(), ByRef UnCheckArray As CheckBox())
'LOOP FIRST ARRAY AND CHECK THEM.
For index = 0 To CheckArray.GetUpperBound(0)
CheckArray(index).Checked = True
Next
'LOOP SECOND ARRAY AND UNCHECK THEM.
For index = 0 To UnCheckArray.GetUpperBound(0)
UnCheckArray(index).Checked = False
Next
End Sub
First you are confusing "parameter array" with an array of controls, they are not the same thing. A parameter array is when a method takes a variable number of parameters (all of the same type), the compiler then bundles those up into an array and passes that to the method. In order for that to happen the method has to use the keyword ParamArray.
VB.net does not have conrtol arrays in the sense that vb6 did, but that isn't what you example is using. You example is using a simple array of controls. As an aside, your example is using ByRef which was the default in VB6, but it is unnecessary in this case for both VB6 and VB.net. Given that it's usage in VB.net is no longer the default, using it unnecessarily is misleading.
You are passing in two arrays, the second could be a ParamArray but there is little point in doing so.
The basic and minimum change to get your code to work is simple, change the "Variant" to "CheckBox()". But that is not what I would recommend. There are a couple of other minor changes that make it more flexible and more readable.
Public Sub Check_UnCheck(CheckArray As IEnumerable(Of CheckBox),
UnCheckArray As IEnumerable(Of CheckBox))
' Here I have replaced the Variant, which is not supported in
' .net, with the generic IEnumerable of checkbox. I used an
' Ienumerable, instead of an array because it will allow making
' just a minor change to the call site.
' here I have eliminated the index variable, and moved the
' declaration of the conControl variable into the for each.
' Option Infer On statically types the variable as a checkbox
For Each conControl In CheckArray
' Checkbox controls do not have a value property.
' 1 is not true, true is true.
conControl.Checked = True
Next
For Each conControl in UnCheckArray
conControl.Checked = False
Next
End Sub
This would then be called like so:
Check_UnCheck({chkCheck3, chkCheck}, {chkCheck1, chkCheck4})

Excel vba : assign all properties to a cell from an array of range

My goal is to save a Cell and all its properties and value in an array of range, and then write back all properties and values of this cell to another cell from the array (like a copy paste function but pasting from the array).
Here is a simple test procedure :
Sub Test()
Dim Range_Grid(1) As Range
Dim CellAdress As String
Dim i As Long
Set Range_Grid(1) = ActiveSheet.Cells(2, 3)
ActiveSheet.Cells(4, 1) = Range_Grid(1)
End Sub
So here in the first element of the array Range_Grid(1), I really get the full range saved and I can access every property the original range ActiveSheet.Cells(2, 3) had, like font style, format, color, comment, etc..
But when I try to write this range from array to another empty cell, it only write the value...
Any idea how to write all the properties from the array like if it was a copy/paste from sheet to sheet?
There is no way of moving all of the formatting from a range in an array back into excel at once. This leaves you with two options: either you copy within excel as Scott Craner suggested, or you copy each type of formatting individually. For example, if you wanted to copy the value (which is the default and hence is what is copied in your original code) and the cell background colour, then you could use the following code:
Sub Test()
Dim Range_Grid(1) As Range
Set Range_Grid(1) = ActiveSheet.Cells(2, 3)
ActiveSheet.Cells(4, 1) = Range_Grid(1)
ActiveSheet.Cells(4, 1).Interior.ColorIndex = Range_Grid(1).Interior.ColorIndex
End Sub
Unfortunately there are probably over a dozen types of formatting that you may want to deal with at any given time...
There's an alternative: use Copy and PasteSpecial functions.
Sub Test()
ActiveSheet.Cells(2, 3).Copy
ActiveSheet.Cells(4, 1).PasteSpecial(xlPasteAll)
End Sub

Excel VBA Object Variable Not Set issue, possible improper Redim usage?

I am running into an error with the below code. When I debug, I find the line causing my problem:
Options(a) = New Element
The error displayed is Object Variable or With Block Variable Not Set. With msg boxes I have found the value of a to be 0 at the time of the crash and the TotalItems to be 7. The Element object initialization is empty. I call the PopulateChildren method from another method within the same class. Am I using ReDim improperly? It seems like maybe it isn't increasing the size of my array... I have noticed examples of using it like this...
ReDim Preserve Options(0 to TotalItems)
...but it doesn't seem to do any different when I try it. Anyone have any idea what is going on?
Dim Options() As Element
Dim TotalItems As Integer
Dim Children(100) As Integer
Private Sub PopulateChildren()
ReDim Preserve Options(TotalItems)
For a = 0 To TotalItems - 1
Options(a) = New Element
Options(a).Populate (Children(a))
Next a
End Sub
Thanks
Since Element is Object, you should use Set:
Set Options(a) = New Element
easy, put your dim options inside the SUB, or ...
public dim options()
sub ...
'your code
end sub
of course options doesn't exit for your sub like you wrote it ! so the error showing...

How to have a global Dictionary in VB.NET/WPF application to save data from different windows?

I am new to VB.NET and WPF.
I am building a "Questionnaire" app. Users will be presented sequentially with different questions/tasks (windows). After they respond on each question/task and press a "submit" button a new window will open with a new question/task, and previous window will close. After each question, when the button is pressed, I need to store data to some global object. After all questions are answered the data of this object should be written out to the output file.
I figured out that Dictionary will be the best to store the results after each window.
I am not sure how, where to create this global Dictionary and how to access it. Should I use View Model? If yes, can you give an example? Or, should it be just a simple class with shared property? (something like this)
EDIT 2: I tried many different ways recommended online
GlobalModule:
Module GlobalModule
Public Foo As String
End Module
GlobalVariables:
Public Class GlobalVariables
Public Shared UserName As String = "Tim Johnson"
Public Shared UserAge As Integer = 39
End Class
Global properties:
Public Class Globals
Public Shared Property One As String
Get
Return TryCast(Application.Current.Properties("One"), String)
End Get
Set(ByVal value As String)
Application.Current.Properties("One") = value
End Set
End Property
Public Shared Property Two As Integer
Get
Return Convert.ToInt32(Application.Current.Properties("Two"))
End Get
Set(ByVal value As Integer)
Application.Current.Properties("Two") = value
End Set
End Property
End Class
Here is where I save the data to global variables/properties in the first window. I need to store data in this subroutine before closing an old window and opening a new window. I use MessageBox just for testing.
Private Sub btnEnter_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnEnter.Click
Dim instructionWindow As InstructionsWindow
instructionWindow = New InstructionsWindow()
Application.Current.Properties("number") = textBoxValue.Text
Globals.One = "2"
Globals.Two = 3
MessageBox.Show("GlobalVariables: UserName=" & GlobalVariables.UserName & " UserAge=" & GlobalVariables.UserAge)
GlobalVariables.UserName = "Viktor"
GlobalVariables.UserAge = 34
GlobalModule.Foo = "Test Foo"
'testing if it saved tha value
'MessageBox.Show(Application.Current.Properties("number"))
Application.Current.MainWindow.Close()
instructionWindow.ShowDialog()
End Sub
Next subroutine is where I am trying to retrieve the value from global Properties/variables in the second window, but message boxes come out empty. There might also the case that I am assigning values in a wrong way, or not reading them in a right way (casting?) :
Private Sub FlowDocReader_Initialized(ByVal sender As Object, ByVal e As System.EventArgs) Handles FlowDocReader.Initialized
' Get a reference to the Application base class instance.
Dim currentApplication As Application = Application.Current
MessageBox.Show(currentApplication.Properties("number"))
MessageBox.Show("One = " & Globals.One & " Two = " & Globals.Two)
MessageBox.Show("GlobalVariables: UserName=" & GlobalVariables.UserName & " UserAge=" & GlobalVariables.UserAge)
MessageBox.Show("GlobalModule.Foo = " & GlobalModule.Foo)
Dim filename As String = My.Computer.FileSystem.CurrentDirectory & "\instructions.txt"
Dim paragraph As Paragraph = New Paragraph()
paragraph.Inlines.Add(System.IO.File.ReadAllText(filename))
Dim document As FlowDocument = New FlowDocument(paragraph)
FlowDocReader.Document = document
End Sub
Thanks.
You can make public Dictionary property for form and put your dictionry to this property or make constructor with Dictionary argument.
You already have this dictionary Application.Properties
Look here, please.
First, you can define a dictionary (list of lists) as follows at the beginning of a form or in a module
Dim dic As New Dictionary(Of String, List(Of String))
As the user completes questions on a form, write the partucular form number and query results to a single record in the dic before going to the next form (place this code into the "Next" button):
'Assume q1response=3, q2response=4,..., qpresponse="text", etc.
Dim myValues As New List(Of String)
myValues.Add(formname)
myValues.Add(q1response)
myValues.Add(q2response)
.
.
myValues.Add(qpresponse)
dic.Add(username, myValues)
When a user is done, there will be multiple records in the dictionary, each of which starts with their name and is followed by question responses. You can loop through multiple dictionary records, where each record is for a user using the following:
For Each DictionaryEntry In dic 'this loops through dic entries
Dim str As List(Of String) = DictionaryEntry.Value
'here you can do whatever you want with results while you read through dic records
'username will be = str(0)
'formname will be str(1)
'q1 response on "formname" will be str(2)
'q2 response on "formname" will be str(3)
'q3 response on "formname" will be str(4)
...
Next
The trick is that there will be multiple dictionary records with results for one user, where record one can have results like "John Doe,page1,q1,q2,q3" and record 2 will be "John Doe,page2,q4,q5,q6." Specifically, the "str" in the above loop will be an array of string data containing all the items within each dictionary record, that is, in str(0), str(1), str(2),... This is the information you need to work with or move, save, analyze, etc.
You can always put all the code I provided in a class (which will be independent of any form) and dimension the sic is a Sub New in this class, with the updating .Add values lines in their own sub in this same class). Then just Dim Updater As New MyNewClassName. Call the Updater in each continue button using Call Updater.SubNameWithAddValues(q1,q2,...qp). It won't matter where you are in your program since you using a specific class. The one thing I noticed with my code is that you can only use the line that adds the "key" or the username once, so use it after the last query -so put it in a Sub Finished in your new class and call as Call Updater.Finished(username,q30,q31,last)

Resources