Set variable with a loop using the variables name as a string - string

My application requires variables to be set and retrieved multiple times in different forms and subs. Instead of writing the code multiple times i was hoping to use a loop to loop through the variables and set them accordingly. The code below shows my settings menu and how it is built. I'm stuck with setting the variables based on a name that is stored as a string (See last code snippet below).
The section imedialty below are the menu classes. This bit works as expected
Public Class MenuItem
Public Name As String
Public Type As TypeEnum
Enum TypeEnum
CheckBox
ComboBox
TextBox
End Enum
Public Sub New()
End Sub
End Class
Public Class Menu
Public MenuItems() As MenuItem
Public Name As String
Public LinkedVariable as Object
Public Sub New()
End Sub
Public Sub AddMenuItem(ByVal ItemName As String, _
ByVal ItemType As MenuItem.TypeEnum, _
ByVal ItemLinkedVariable as Object)
If MenuItems Is Nothing Then
ReDim MenuItems(0)
Else
ReDim Preserve MenuItems(MenuItems.Length)
End If
MenuItems(MenuItems.Length - 1) = New MenuItem
MenuItems(MenuItems.Length - 1).Name = ItemName
MenuItems(MenuItems.Length - 1).Type = ItemType
MenuItems(MenuItems.Length - 1).LinkedVariable = ItemLinkedVariable
End Sub
End Class
Build the menus in the main form class. This bit works as expected
Public Class Form1
Public Settings As New SettingsForm
Public Sub CreateMenu()
Settings.AddMenu("Menu1")
Settings.Menus(0).AddMenuItem("CheckBoxA", MenuItem.TypeEnum.CheckBox, VariableA)
Settings.Menus(0).AddMenuItem("TextBoxB", MenuItem.TypeEnum.TextBox, VariableB)
Settings.Menus(0).AddMenuItem("TextBoxC", MenuItem.TypeEnum.TextBox, VariableC)
Settings.AddMenu("Menu2")
Settings.Menus(1).AddMenuItem("CheckBoxD", MenuItem.TypeEnum.CheckBox, VariableD)
Settings.Menus(1).AddMenuItem("TextBoxE", MenuItem.TypeEnum.TextBox, VariableE)
'etc......
Settings.Build()
Settings.Show()
End Sub
End Class
Public Class SettingsForm
Public Menus() As Menu
Public VariableA As Boolean
Public VariableB As String
Public VariableC As String
Public VariableD As Boolean
Public VariableE As String
Public Sub New()
InitializeComponent()
End Sub
Public Sub Build()
SettingsTabControl.TabPages.Clear()
If Menus IsNot Nothing Then
For i As Integer = 0 To Menus.Length - 1
SettingsTabControl.TabPages.Add(Menus(i).Name)
SettingsTabControl.TabPages(i).Name = Menus(i).Name & "TabPage"
SettingsTabControl.TabPages(i).Text = Menus(i).Name
AddSettingsItems(Menus(i), i)
Next
End If
End Sub
Public Sub AddSettingsItems(ByVal SelectedMenu As Menu, ByVal TabPageIndex As Integer)
Dim y As Integer = 50
Dim yinc As Integer = 25
If SelectedMenu IsNot Nothing Then
If SelectedMenu.MenuItems IsNot Nothing Then
For i As Integer = 0 To SelectedMenu.MenuItems.Length - 1
Dim formObj As Object
'Specific Settings
Select Case SelectedMenu.MenuItems(i).Type
Case MenuItem.TypeEnum.CheckBox
formObj = New CheckBox
Case MenuItem.TypeEnum.ComboBox
formObj = New ComboBox
Case MenuItem.TypeEnum.TextBox
formObj = New TextBox
formObj.TextAlign = HorizontalAlignment.Right
formObj.BorderStyle = BorderStyle.FixedSingle
End Select
formObj.Name = SelectedMenu.MenuItems(i).Name
formObj.Location = New Point(25, y)
SettingsTabControl.TabPages(TabPageIndex).Controls.Add(formObj)
y = y + yinc
Next
End If
End If
End Sub
End Class
This is the bit i am stuck on i want to be able to set the Variables A,B,C,D,E from the controls that were added to the SettingsTabControl. However i can't work out how to link the Variables to the LinkedVariable objects in each MenuItem. This is what i have but it doesn't work
Private Sub SettingsFromOkButton_Click(sender As Object, e As EventArgs) Handles OkButton.Click
For m As Integer = 0 To Menus.Length - 1
For i As Integer = 0 To Menus(m).MenuItems.Length
Dim currentcontrol As Object = SettingsTabControl.TabPages(m).Controls(Menus(m).MenuItems(i).Name)
Select Case Menus(m).MenuItems(i).Type
Case MenuItem.TypeEnum.CheckBox
If currentcontrol.CheckedState = True Then
Menus(m).MenuItems(i).LinkedVariable = True
Else
Menus(m).MenuItems(i).LinkedVariable = False
End If
Case MenuItem.TypeEnum.ComboBox
Case MenuItem.TypeEnum.TextBox
Menus(m).MenuItems(i).LinkedVariable = currentcontrol.Text
End Select
Next
Next
MsgBox(VariableA & vbCrLf _
VariableB & vbCrLf _
VariableC & vbCrLf _
VariableD & vbCrLf _
VariableE & vbCrLf)
End Sub

I used a reflection
Dim Flags As BindingFlags = BindingFlags.GetField Or BindingFlags.Instance Or BindingFlags.Public
Dim fVar As System.Reflection.FieldInfo = Me.GetType.GetField(ObjectName, Flags)
If TypeOf SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName) Is TextBox Then
ctrlValue = SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName).Text
ElseIf TypeOf SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName) Is CheckBox Then
ctrlValue = CType(SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName), CheckBox).Checked
ElseIf TypeOf SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName) Is RadioButton Then
ctrlValue = CType(SettingsTabControl.TabPages(TabPageIndex).Controls(CtrlName), RadioButton).Checked
End If
fVar.SetValue(Me, ctrlValue)

Related

VBA Excel ListView Checkboxes do not show in Userform

I have a UserForm with a MultipageControl (name Controller_MultiPage).
At runtime my code adds pages to the Multipage and creates a newListView on each page.
Every ListView has:
With newListView
.MultiSelect = False
.Width = Controller_MultiPage.Width - 10
.Height = Controller_MultiPage.Height - 20
.View = lvwReport
.HideColumnHeaders = False
.ColumnHeaders.Add Text:="Signal Name", Width:=.Width / 10 * 4
.ColumnHeaders.Add Text:="Type", Width:=.Width / 10
.ColumnHeaders.Add Text:="I/O", Width:=.Width / 10
.ColumnHeaders.Add Text:="Description", Width:=.Width / 10 * 4
.CheckBoxes = True
.FullRowSelect = True
End With
then I populate the newListView with data from an XML file:
For Each node In list
With node.Attributes
Set listItem = newListView.ListItems.Add(Text:=.getNamedItem("Name").Text)
listItem.ListSubItems.Add = .getNamedItem("Type").Text
listItem.ListSubItems.Add = IIf(.getNamedItem("Input").Text = "1", "IN", "OUT")
listItem.ListSubItems.Add = .getNamedItem("Description").Text
listItem.Checked = False
End With
Next
but the checkboxes do not show. I can see the space for them in front of the first column and by clicking that space the checkbox of that particular row then appears. What I also noticed is that if I change the property
listItem.Checked = True
the behavior described above does not change, and when I click the free space in front of the first column (checkboxes space) the chsckbox that then shows up is still unchecked.
Any idea?
The problem seems to be in the behavior of the MultiPage control.
What I noticed was that if I forced the checkboxes' status (checked or unchecked) from the code, using the MultiPage_Change event, then the checkboxes show up.
So what I did was to create a class that holds the status of all checkboxes of all listviews on a single page, instantiate the Class for each ListView and store everything into a Dictionary, using the newListView.Name as Key
Then when the user changes page, the MultiPage_Change event that fires resets all the values of the checkboxes according to the Dictionary stored values.
In the Listview_N_ItemChecked event some other code updates the status of the item stored in the Dictionary.
Kind of cumbersome but it works.
the class (updated):
' Class Name = ComponentsSignalsRecord
Option Explicit
Dim Name As String
' NOTE: Signals(0) will always be empty and status(0) will always be False
Dim Signals() As String
Dim Status() As Boolean
Dim Component As String
Property Let SetComponentName(argName As String)
Component = argName
End Property
Property Get GetComponentName() As String
GetComponentName = Component
End Property
Property Get getSignalName(argIndex) As String
If argIndex >= LBound(Signals) And argIndex <= UBound(Signals) Then
getSignalName = Signals(argIndex)
Else
getSignalName = vbNullString
End If
End Property
Property Get dumpAll() As String()
dumpAll = Signals
End Property
Property Get Count() As Long
Count = UBound(Signals)
End Property
Property Get getStatus(argName As String) As Integer
' returns: -1 = Not Found; 1 = True; 0 = False
getStatus = -1
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then getStatus = IIf(Status(i) = True, 1, 0): Exit For
Next
End Property
Property Let setName(argName As String)
Name = argName
End Property
Property Get getName() As String
getName = Name
End Property
Public Sub UncheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = False
Next
End Sub
Public Sub CheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = True
Next
End Sub
Public Sub deleteSignal(argName As String)
Dim spoolSignals() As String
Dim spoolStatus() As Boolean
Dim i As Integer
spoolSignals = Signals
spoolStatus = Status
ReDim Signals(0)
ReDim Status(0)
For i = 1 To UBound(spoolSignals)
If argName <> spoolSignals(i) Then
ReDim Preserve Signals(UBound(Signals) + 1): Signals(UBound(Signals)) = spoolSignals(i)
ReDim Preserve Status(UBound(Status) + 1): Status(UBound(Status)) = spoolStatus(i)
End If
Next
End Sub
Public Sub addSignal(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then GoTo bye
Next
ReDim Preserve Signals(UBound(Signals) + 1)
ReDim Preserve Status(UBound(Status) + 1)
Signals(UBound(Signals)) = argName
Status(UBound(Status)) = argValue
bye:
End Sub
Public Sub setStatus(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then Status(i) = argValue: Exit For
Next
End Sub
Private Sub Class_Initialize()
ReDim Signals(0)
ReDim Status(0)
End Sub
The Form relevant code. Module level:
Dim myDict As New Dictionary ' the Dictionary
Dim ComponentsSignalsList As ComponentsSignalsRecord ' the Class
for each ListView created, may be one or more for every single MultiPage page :
Set ComponentsSignalsList = New ComponentsSignalsRecord
ComponentsSignalsList.setName = newListView.name
while populating the listview(s) in a loop for each single item added:
ComponentsSignalsList.addSignal List_Item.Text, List_Item.Checked
end of each loop, add the Class instance to the Dictionary:
myDict.Add ComponentsSignalsList.getName, ComponentsSignalsList
Now when changing Page in the MultiPage widget:
Private Sub Controller_MultiPage_Change()
If isLoading Then Exit Sub 'avoid errors and undue behavior while initializing the MultiPage widget
Dim locControl As Control
Dim controlType As String: controlType = "ListView"
With Controller_MultiPage
For Each locControl In .Pages(.value).Controls
If InStr(1, TypeName(locControl), controlType) > 0 Then
Call Check_CheckBoxes(locControl)
End If
Next
End With
End Sub
Private Sub Check_CheckBoxes(argListView As listView)
If argListView.CheckBoxes = False Then Exit Sub 'some ListViews don't have checkboxes
Dim myItem As ListItem
For Each myItem In argListView.ListItems
With myItem
.Checked = myDict.Item(argListView.name).getStatus(.Text)
End With
Next
End Sub
when ticking/unticking a checkbox (note the the ItemChecked event handler is defined in another Class Public WithEvents, where the handler calls this method passing both the ListView ID and the Item object) :
Public Sub ListViewsEvents_ItemCheck(argListView As listView, argItem As MSComctlLib.ListItem)
With argItem
myDict.Item((argListView .name).setStatus argName:=.Text, argValue:=.Checked
End With
End Sub
I just found the answer to the same problem that I also had and I feel so stupid. I had the first column of the Listview set to Width = 0... and thus the checkboxes would no longer show.
I gave it a width and everithing is back to normal...

Trying to add Textboxes to a userform dynamically?

I have code inside a excel workbook that helps me create mass emails to send to users of various programs. I have a userform that pops up and the user populates all the info needed. but that only counts for one app at a time. Can someone share code with me that dynamically adds textboxes to a userform dependant on what checkboxes are ticked ?
In the first frame I have check boxes that indicate what applications are affected, second frame I have option buttons to describe what type of incident and then I would like the textboxes to appear according to what has been ticked.
Any guidance much appreciated as I think this is way too deep for me at the moment
I've reverse engineered this code it adds the boxes I want but I need to be able to populate them with cell data and then use it in the emails:
Option Explicit
Dim SpnColct As Collection
Private Sub CommandButton2_Click()
Dim cSpnEvnt As cControlEvent
Dim ctlSB As Control
Dim ctlTXT As Control
Dim lngCounter As Long
For lngCounter = 1 To 7
Set ctlTXT = Me.Frame7.Controls.Add("Forms.TextBox.1", "Text" & lngCounter)
ctlTXT.Name = "Text" & lngCounter
ctlTXT.Left = 5
ctlTXT.Height = 125: ctlTXT.Width = 280
ctlTXT.Top = (lngCounter - 1) * 125 + 2
Set cSpnEvnt = New cControlEvent
Set cSpnEvnt.SP = ctlSB
Set cSpnEvnt.TXT = ctlTXT
SpnColct.Add cSpnEvnt
Next lngCounter
Me.Frame1.ScrollHeight = (lngCounter - 1) * 17 + 2
End Sub
This added to a class module:
Option Explicit
Public WithEvents SP As MSForms.SpinButton
Public WithEvents TXT As MSForms.TextBox
Private Sub SP_SpinDown()
SP.Value = SP.Value - 1
MsgBox "Spin Down to " & SP.Value
End Sub
Private Sub SP_SpinUp()
SP.Value = SP.Value + 1
MsgBox "Spin Up to " & SP.Value
End Sub
Private Sub TXT_Change()
MsgBox "You changed the value."
End Sub
Updated This is going to be a bit of a long one - step through it see if you understand it. Have changed it to create the textboxes on the CheckBox_Click event but change to the commandbutton if you wish. Any more then this and I think you'll need to start a new question.
I've been doing something similar recently and found that the reason you're having issues is due to the order of loading objects. I unfortunately can't find the link that explains it at the moment (will update if can) but briefly to be able to achieve this you need an additional Class that does the loading of the objects, otherwise the Userform can't see them. This is the kind of solution that I came up with (using your example)
Userform:
Option Explicit
Private WithEvents cControls As EventController
Private Sub cControls_Click(ctrl As CheckBoxControl)
Dim tBox As TextBoxControl
Dim i As Long
Dim NextTop As Long, FrameHeight As Long
For i = 1 To cControls.GetControls.Count
Debug.Print TypeName(cControls.GetControl(i))
If TypeName(cControls.GetControl(i)) = "TextBoxControl" Then
Set tBox = cControls.GetControl(i)
If tBox.TXT.Parent Is Me.Frame7 Then
NextTop = tBox.Top + tBox.Height
End If
End If
Next i
Set tBox = cControls.AddTextBox
With tBox
.Height = 125
.Width = 280
.Left = 5
.Top = NextTop
.TXT.Text = ctrl.cBox.Caption
FrameHeight = NextTop + .Height
End With
If FrameHeight > Me.Frame7.InsideHeight Then
With Me.Frame7
.ScrollBars = fmScrollBarsVertical
.ScrollHeight = FrameHeight
.Scroll yAction:=6
End With
End If
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim cBox As CheckBoxControl
Set cControls = New EventController
' This can be set to a userform or a frame
Set cControls.UserForm = Me
For i = 1 To 8
Set cBox = cControls.AddCheckBox
cBox.cBox.Left = 5
With cBox.cBox
.Top = 5 + (i - 1) * .Height
.Caption = IIf(i = 8, "App Unknown", "App " & i)
End With
Next i
End Sub
Private Sub cControls_Change(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
MsgBox ctrl.TXT.Name & " Change"
End Sub
Private Sub cControls_SpinDown(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
With ctrl.SP
If .Value >0 Then
.Value = .Value - 1
End If
End With
MsgBox ctrl.SP.Name & " Spin Down"
End Sub
Private Sub cControls_SpinUp(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
With ctrl.SP
.Value = .Value + 1
End With
MsgBox ctrl.SP.Name & " Spin Up"
End Sub
Classes - These need to be named as in bold
EventControl
Option Explicit
Private CtrlCollection As Collection
Private cUserForm As UserForm1
Public Event SpinDown(ctrl As TextBoxControl)
Public Event SpinUp(ctrl As TextBoxControl)
Public Event Change(ctrl As TextBoxControl)
Public Event Click(ctrl As CheckBoxControl)
Public Property Set UserForm(v As UserForm1)
Set cUserForm = v
End Property
Public Property Get UserForm() As UserForm1
Set UserForm = cUserForm
End Property
Public Function AddTextBox() As TextBoxControl
Dim tBox As TextBoxControl
Set tBox = New TextBoxControl
tBox.Initialize Me
CtrlCollection.Add tBox
Set AddTextBox = tBox
End Function
Public Function AddCheckBox() As CheckBoxControl
Dim cBox As New CheckBoxControl
cBox.Initalize Me
CtrlCollection.Add cBox
Set AddCheckBox = cBox
End Function
Public Function GetControl(Index As Long)
Set GetControl = CtrlCollection(Index)
End Function
Public Function GetControls() As Collection
Set GetControls = CtrlCollection
End Function
Private Sub Class_Initialize()
Set CtrlCollection = New Collection
End Sub
Public Sub SpinDown(ctrl As TextBoxControl)
RaiseEvent SpinDown(ctrl)
End Sub
Public Sub SpinUp(ctrl As TextBoxControl)
RaiseEvent SpinUp(ctrl)
End Sub
Public Sub Change(ctrl As TextBoxControl)
RaiseEvent Change(ctrl)
End Sub
Public Sub Click(ctrl As CheckBoxControl)
RaiseEvent Click(ctrl)
End Sub
CheckBoxControl
Option Explicit
Public WithEvents cBox As MSForms.CheckBox
Private cParent As EventController
Public Property Set Parent(v As EventController)
Set cParent = v
End Property
Public Property Get Parent() As EventController
Set Parent = cParent
End Property
Public Sub Initalize(Parent As EventController)
Set Me.Parent = Parent
Set cBox = Parent.UserForm.Frame1.Controls.Add("Forms.CheckBox.1")
End Sub
Private Sub cBox_Click()
Parent.Click Me
End Sub
TextBoxControl
Option Explicit
Public WithEvents SP As MSForms.SpinButton
Public WithEvents TXT As MSForms.TextBox
Private cParent As EventController
Public Sub Initialize(Parent As EventController)
Set Me.Parent = Parent
With Parent.UserForm.Frame7.Controls
Set SP = .Add("Forms.SpinButton.1")
Set TXT = .Add("Forms.TextBox.1")
End With
End Sub
Public Property Set Parent(v As EventController)
Set cParent = v
End Property
Public Property Get Parent() As EventController
Set Parent = cParent
End Property
Public Property Let Left(v As Single)
TXT.Left = v
SP.Left = TXT.Left + TXT.Width
End Property
Public Property Get Left() As Single
Left = TXT.Left
End Property
Public Property Let Top(v As Single)
TXT.Top = v
SP.Top = v
End Property
Public Property Get Top() As Single
Top = TXT.Top
End Property
Public Property Let Height(v As Single)
TXT.Height = v
SP.Height = v
End Property
Public Property Get Height() As Single
Height = TXT.Height
End Property
Public Property Let Width(v As Single)
TXT.Width = v - SP.Width
SP.Left = TXT.Left + TXT.Width
End Property
Public Property Get Width() As Single
Width = TXT.Width + SP.Width
End Property
Public Sub SP_SpinDown()
Parent.SpinDown Me
' SP.Value = SP.Value - 1
' MsgBox "Spin Down to " & SP.Value
End Sub
' The commented out lines below you can either leave in here, or handle in the Userform
Public Sub SP_SpinUp()
Parent.SpinUp Me
' SP.Value = SP.Value + 1
' MsgBox "Spin Up to " & SP.Value
End Sub
Public Sub TXT_Change()
Parent.Change Me
' MsgBox "You changed the value."
End Sub
The issue is stemmed from that when the Userform is loaded the controls aren't loaded and therefore the Userform hasn't registered that they're something that has an Event. By using the intermediary class the Userform recognises that that class has an Event and we load this statically on initialize of the Userform. We can then add in whatever Controls we want to this Class and the Userform will handle them.
Demo:

Call variable by string in other module

I have a user form allowing users for option. The options should be saved until the excel file is closed. Before that, the options will be recorded even the user form is closed. In the user form, I have 11 checkboxes.
So I put 11 global variables under "sheet1"(renamed to DataBase) module:
Public chk1Last As Boolean
Public chk2Last As Boolean
Public chk3Last As Boolean
Public chk4Last As Boolean
Public chk5Last As Boolean
Public chk6Last As Boolean
Public chk7Last As Boolean
Public chk8Last As Boolean
Public chk9Last As Boolean
Public chk10Last As Boolean
Public chk11Last As Boolean
And I use the following code in a class to record the checkbox status to the global variable under sheet1(renamed to DataBase):
Private Sub Chk_Change()
Select Case Chk.Name
Case "chk1"
DataBase.chk1Last = Chk
Case "chk2"
DataBase.chk2Last = Chk
Case "chk3"
DataBase.chk3Last = Chk
Case "chk4"
DataBase.chk4Last = Chk
Case "chk5"
DataBase.chk5Last = Chk
Case "chk6"
DataBase.chk6Last = Chk
Case "chk7"
DataBase.chk7Last = Chk
Case "chk8"
DataBase.chk8Last = Chk
Case "chk9"
DataBase.chk9Last = Chk
Case "chk10"
DataBase.chk10Last = Chk
Case "chk11"
DataBase.chk11Last = Chk
End Select
'Do other things beneath'
And in the main module, I use the following code to initialize the user form", if the user form is first loaded, all checkboxes will be enabled, if not, the checkboxes will follow the lase status:
Private Sub UserForm_Activate()
If Not DataBase.Activated Then
For i = 1 To 11
Controls("chk" & i) = True
Next i
Else
chk1 = DataBase.chk1Last
chk2 = DataBase.chk2Last
chk3 = DataBase.chk3Last
chk4 = DataBase.chk4Last
chk5 = DataBase.chk5Last
chk6 = DataBase.chk6Last
chk7 = DataBase.chk7Last
chk8 = DataBase.chk8Last
chk9 = DataBase.chk9Last
chk10 = DataBase.chk10Last
chk11 = DataBase.chk11Last
End If
'Do other things beneath'
What I am trying to do is to simplify the code under class module something like below:
In sheet1(renamed to DataBase):
Controls("DataBase." & Chk.name & "Last") = Chk
And under main to simplify the code with for loop and controls() something like below:
Private Sub UserForm_Activate()
If Not DataBase.Activated Then
For i = 1 To 11
Controls("chk" & i) = True
Next i
Else
For i = 1 to 11
Controls("chk" & i) = Controls("DataBase.chk" & i & "Last")
Next i
End If
'Do other things beneath'
But it failed, what is the correct syntax if I want to call the variable in other modules by controls()?
Thank you very much for all of your sincere help.
Instead of
Public chk1Last As Boolean
Public chk2Last As Boolean
'etc etc
use:
Public ChkLast(1 to 11) As Boolean
Then you can address the values in the same loop as you do for the controls.
You cannot build a string to address a public variable by name. You can do certain things such as using CallByName() if your variables are properties in an instance of a class, but for cases such as yours an array makes more sense and avoids the extra complications.

WithEvent in Class module not getting variable from Let Property in Excel VBA

I think I'm missing something simple here. Trying to use a variable in a Class module. Let and Get work fine. But if I try to use the variable in a different sub in the Class module I just get a value of 0.
Class Module clsCombobox
Public WithEvents ComboBox As MSForms.ComboBox
Public WithEvents ComboTextBox As MSForms.TextBox
Public Num As Long
Public Property Let Number(Value As Long)
Num = Value
End Property
Public Property Get Number() As Long
Number = Num
End Property
Private Sub ComboBox_Change()
Me.ComboTextBox.Value = Num
'this gives value of 0
End Sub
Userform Module
Dim obEvents as clsCombobox
Set obEvents = New clsCombobox
obEvents.Number = 52
MsgBox obEvents.Number 'this prints 52
Sub that sets ComboBox
Private Sub GroupCombobox()
Dim i As Long
Dim obEvents As clsCombobox
Set collCombobox = New Collection
For i = 1 To 5
Set obEvents = New clsCombobox
Set obEvents.ComboBox = Me.Controls("cbAbility" & i)
Set obEvents.ComboTextBox = Me.Controls("tbAbility" & i & "Text")
collCombobox.Add obEvents
Next i
End Sub
It doesn't look like you ever set the value. I assume you mean to do that in the loop? Perhaps not with the value of i, but here you can see the idea...
For i = 1 To 5
Set obEvents = New clsCombobox
'Set the value here
obEvents.Number = 52 ' 52 or whatever is needed as Number
Set obEvents.ComboBox = Me.Controls("cbAbility" & i)
Set obEvents.ComboTextBox = Me.Controls("tbAbility" & i & "Text")
collCombobox.Add obEvents
Next i

Excel VBA: Is there a way to reference an instance of a class stored in a dictionary?

I currently have instances of classes stored using the data structure presented in the image below. Each -List item is a dictionary and each -Info item is an instance of a class.
I read elsewhere that if you Set an instance variable equal to another instance, it just references the original instance. Is this correct?
I have been able to create a reference for fileInfo(1) (in the image) using the following code.
Dim prflInfo As File_Info
Set prflInfo = New File_Info
Set prflInfo = fileList.Items(0)
I have attempted to reference the branchInfo instance using the following code, but I get a Run-time error 13: Type mismatch when I attempt to do so.
Dim prbrInfo As Branch_Info
With prflInfo
Set prbrInfo = New Branch_Info
brKey = .getbrKey(0)
Set prbrInfo = .getbrItem(brKey)
End With
Edit: Included below is the code for the File_Info class. All other classes follow this basic model.
'Class Module: File_Info
'Initialise class variables
Private pfileID As Integer
Private pfilePath As String
Private pfileName As String
Private pbranchList As Scripting.Dictionary
'Declare variantcopy subroutine
Private Declare Sub VariantCopy Lib "OleAut32" (pvarDest As Any, pvargSrc As Any)
Private Sub Class_Initialize()
Set pbranchList = New Scripting.Dictionary
End Sub
Public Property Let fileID(pfileIDi As Variant)
pfileID = pfileIDi
End Property
Public Property Get fileID() As Variant
fileID = pfileID
End Property
Public Property Let filePath(pfilePathi As Variant)
pfilePath = pfilePathi
End Property
Public Property Get filePath() As Variant
filePath = pfilePath
End Property
Public Property Let fileName(pfileNamei As Variant)
pfileName = pfileNamei
End Property
Public Property Get fileName() As Variant
fileName = pfileName
End Property
Public Sub addbrConn(branch As Branch_Info)
pbranchList.Add branch.branchID, branch.brConn
Debug.Print "addbrConn ID: " & branch.branchID
End Sub
Public Sub addBranch(branch As Branch_Info)
pbranchList.Add branch.branchID, branch
Debug.Print pbranchList.Count
End Sub
Public Function countbrList()
countbrList = pbranchList.Count
End Function
Public Function getbrKey(Key As Variant)
getbrKey = pbranchList.Keys(Key)
End Function
Public Function getbrItem(Key As Variant)
getbrItem = GetByRefVariant(pbranchList.Items(Key))
End Function
Public Sub dpbrList()
With pbranchList
Debug.Print pbranchList.Count
For k = 1 To pbranchList.Count
Debug.Print .Keys(k - 1), .Items(k - 1)
Next k
End With
End Sub
Public Sub updbrList(branch As Branch_Info)
Dim branchID As String
branchID = branch.branchID
If pbranchList.exists(branchID) Then
pbranchList.Remove (branchID)
pbranchList.Add branchID, branch
Debug.Print "Complete: " & branchID & " added."
Else
Debug.Print "Error: " & branchID & "does not exist."
End If
End Sub
Private Function GetByRefVariant(ByRef var As Variant) As Variant
VariantCopy GetByRefVariant, var
End Function
Is there a way to reference the branchInfo class, to make it easier to extract the data within it?
Thanks!
Eeshwar
I do things differently in that I iterate through the keys list using a For ... each loop rather than referring to an item number. Here is a snippet that works using two levels.
You can ignore the lines where the property values are written to an array, but they were part of the original code.
cf.Dependents is a dictionary of cDependents within the cFamily object
'Declarations in Main Module
Dim dF As Dictionary, cF As cFamily, cD As cDependents
Dim I As Long, J As Long
Dim V As Variant, W As Variant
...
For Each V In dF
I = I + 1
Set cF = dF(V)
With cF
vRes(I, 1) = .FirstName
vRes(I, 2) = .LastName
vRes(I, 3) = .Birthdate
J = 2
For Each W In .Dependents
J = J + 2
Set cD = .Dependents(W)
With cD
vRes(I, J) = .Relation
vRes(I, J + 1) = .DepName
End With
Next W
End With
Next V
Note that in the sequence, as you show in your question:
set Obj = new Obj
set Obj = myClass(0)
the first line is unnecessary.
IMO it is possible to use simple VBA.Collection, here example for the FileList and BranchList. In this example List classes have Items and Info classes have reference to List where List is wrapper for a VBA.Collection. HTH
For more reading have a look e.g. here.
FileList Class
Option Explicit
Private m_fileInfoCollection As FileInfoCollection
Private Sub Class_Initialize()
Set m_fileInfoCollection = New FileInfoCollection
End Sub
Public Property Get Items() As FileInfoCollection
Set Items = m_fileInfoCollection
End Property
FileInfo Class
Option Explicit
Private m_branchList As BranchList
Private m_fileID As Integer
Private Sub Class_Initialize()
Set m_branchList = New BranchList
End Sub
Public Property Get FileID() As Integer
FileID = m_fileID
End Property
Public Property Let FileID(ByVal vNewValue As Integer)
m_fileID = vNewValue
End Property
Public Property Get BranchList() As BranchList
Set BranchList = m_branchList
End Property
FileInfoCollection Class
Option Explicit
Private m_collection As VBA.Collection
Private Sub Class_Initialize()
Set m_collection = New VBA.Collection
End Sub
Public Sub Add(ByVal newItem As FileInfo)
m_collection.Add newItem, CStr(newItem.FileID)
End Sub
Public Function ItemByKey(ByVal key As String) As FileInfo
Set ItemByKey = m_collection(key)
End Function
Public Function ItemByIndex(ByVal index As Long) As FileInfo
Set ItemByIndex = m_collection(index)
End Function
Public Function Count() As Long
Count = m_collection.Count
End Function
BranchList Class
Option Explicit
Private m_branchInfoCollection As BranchInfoCollection
Private Sub Class_Initialize()
Set m_branchInfoCollection = New BranchInfoCollection
End Sub
Public Property Get Items() As BranchInfoCollection
Set Items = m_branchInfoCollection
End Property
BranchInfo Class
Option Explicit
Private m_branchID As Integer
Public Property Get branchID() As Integer
branchID = m_branchID
End Property
Public Property Let branchID(ByVal vNewValue As Integer)
m_branchID = vNewValue
End Property
BranchInfoCollection Class
Option Explicit
Private m_collection As VBA.Collection
Private Sub Class_Initialize()
Set m_collection = New VBA.Collection
End Sub
Public Sub Add(ByVal newItem As BranchInfo)
m_collection.Add newItem, CStr(newItem.branchID)
End Sub
Public Function ItemByKey(ByVal key As String) As BranchInfo
Set ItemByKey = m_collection(key)
End Function
Public Function ItemByIndex(ByVal index As Long) As BranchInfo
Set ItemByIndex = m_collection(index)
End Function
Public Function Count() As Long
Count = m_collection.Count
End Function
Standard Module
Option Explicit
Sub Demo()
' Fill
Dim bi As BranchInfo
Set bi = New BranchInfo
bi.branchID = 111
Dim fi As FileInfo
Set fi = New FileInfo
fi.FileID = 222
fi.BranchList.Items.Add bi
Dim fl As FileList
Set fl = New FileList
fl.Items.Add fi
' Get
Dim fi1 As FileInfo
Set fi1 = fl.Items.ItemByIndex(1)
Dim bi1 As BranchInfo
Set bi1 = fi1.BranchList.Items(1)
End Sub

Resources