Store a class module as a variable in VBA - excel

I wrote some code in a class module and userform_initialize_event. All are okay. But when I Dim the new class module and applies in the initialize event it says that variable not defined. Here is my code -
Dim Buttons() As New BtnClass
Private Sub UserForm_Initialize()
Dim ButtonCount As Integer
Dim ctl As Control
' Create the Button objects
ButtonCount = 0
For Each ctl In fmHover.Controls
If TypeName(ctl) = "Label" Then
ButtonCount = ButtonCount + 1
ReDim Preserve Buttons(1 To ButtonCount)
Set Buttons(ButtonCount).ButtonGroup = ctl
End If
Next ctl
End Sub
If I Dim the Buttons inside the event it does not throws any error and the code also don't works. I searched in a lot of place. Every one Dim the new class before the Initialize event. So, why my one doesn't work? Kindly suggest where is my mistake.
Here is my class module code -
Public WithEvents ButtonGroup As MSForms.Label
Private Sub ButtonGroup_Click()
Msg = "You clicked " & ButtonGroup.Name
MsgBox Msg
ButtonGroup.Name
End Sub
Thanks in advance.

Each label on your form should have it's own instance of the class. The form needs a way to remember each instance - stored in a collection at the form level.
Not sure what the ButtonCount is doing, so have removed it.
Your form code:
Option Explicit '!!!Add option explicit to the top of each module!!!
'It helps avoid so many errors that I don't think three
'exclamations marks is enough!!!!
'>Tools>Options>tick Require variable declaration
'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'Each instance of the class is stored here.
Private colLabels As Collection
Private Sub UserForm_Initialize()
Dim BtnEvents As BtnClass
'Initialise the collection to hold the class instances.
Set colLabels = New Collection
Dim ctrl As Control
For Each ctrl In Me.fmHover.Controls
If TypeName(ctrl) = "Label" Then
Set BtnEvents = New BtnClass 'New instance of the class.
Set BtnEvents.AssignButton = ctrl 'Assign the label to it.
colLabels.Add BtnEvents 'Add it to the collection so it's remembered.
End If
Next ctrl
End Sub
Your class module will look like:
Option Explicit 'More exclamation marks!!!!!!!!!
Public WithEvents ButtonGroup As MSForms.Label
'Let the class know what control it's assigned to.
Public Property Set AssignButton(ctrl As Control)
Set ButtonGroup = ctrl
End Property
Private Sub ButtonGroup_Click()
Dim Msg As String
Msg = "You clicked " & ButtonGroup.Name
MsgBox Msg
End Sub

Related

How to perform eventhandling on ActiveX ListBoxes with VBA

Using the designer I created several ListBoxes on a worksheet and instead of writing a sub for each listbox for handling a click (etc.) I want to handle this in one sub.
I've read that this should be possible using a class and assigning existing listboxes to the eventhandler of that class.
But I can't get it working.
a) creating a class ~~~~ CListBoxEventHandler ~~~~ containing on the class module sheet
Public WithEvents CmdEvents As MSForms.ListBox
Private Sub CmdEvents_Click()
MsgBox "Click Event"
End Sub
b) on the worksheet
Private lisHandlers() As CListBoxEventHandler
sub worksheet_activate()
Dim numObjects As Long: numObjects = Me.OLEObjects.count
ReDim lisHandlers(1 To numObjects) As CListBoxEventHandler
dim i as integer: i = 0
Dim ctrl As OLEObject
For Each ctrl In Me.OLEObjects
Dim progID As String: progID = ctrl.progID
If (progID = "Forms.ListBox.1") Then
i = i + 1
Dim myListBox As MSForms.ListBox: Set myListBox = ctrl.Object
myListBox.LinkedCell = ""
Set lisHandlers(i).CmdEvents = myListBox
End If
Next ctrl
Redim Preserve lisHandlers(1 to i) as CListBoxEventHandler
end sub
How should I do it and can I do the same with TextBoxes?
Also: cab I use ~~~~ myListBox.OnAction = "ListBox_Change" ~~~~ for each of the listboxes and distinguish between the listboxes by Application.Caller?
You have not created any instances CListBoxEventHandler
Creating an array of that type just gives you a set of references to Nothing, not an array of instantiated objects.
Untested:
Private colHandlers As Collection 'easier than an array...
Sub worksheet_activate()
Dim ctrl As OLEObject, obj As CListBoxEventHandler
Dim myListBox As MSForms.ListBox
Set colHandlers = New Collection
For Each ctrl In Me.OLEObjects
If (ctrl.progID = "Forms.ListBox.1") Then
Set myListBox = ctrl.Object
myListBox.LinkedCell = ""
Set obj = New CListBoxEventHandler
Set obj.CmdEvents = myListBox
colHandlers.Add obj
End If
Next ctrl
End Sub

Reading/returning variable values to/from custom classes to/from username - VBA Excel

I have a custom created class btnClass based on CommandButton class.
Public WithEvents ButtonEvent As MsForms.CommandButton
Private Sub ButtonEvent_Click()
End sub
I have a UserForm1 that have one ListBox, one Label, and hundreds of dynamically created CommandButtons. I assigned btnClass to Buttons. When clicked on buttons, I want the Click event has the following result:
If number of buttons selected (selQty) less than the Label.Caption (totalQty), and this button hasn't been selected before, add value to listBox and change BackColor.
if this button selected previously, change color and decrease number of buttons selected (selQty) by 1.
I tried creating Public variables, but cannot get the result I want. Is this doable?
P.S. when the UserForm1 is activated, it means no button selected; as I click button I change the color of the button and accept it as selected.
Seems like you want to do something like this
clsButtonClick:
Option Explicit
Public WithEvents ButtonEvent As MSForms.CommandButton
Private Sub ButtonEvent_Click()
'pass the button to the procedure in the userform
ButtonEvent.Parent.HandleClick ButtonEvent
End Sub
Userform code:
Option Explicit
Const CLR_SEL As Long = vbRed 'selected color
Const CLR_NOT_SEL As Long = vbGreen 'unselected color
Dim btnCol As Collection
Dim maxQty As Long 'max number selectable
Dim currQty As Long 'number currently selected
'perform some setup
Private Sub UserForm_Activate()
Const NUM_BUTTONS As Long = 10
Dim i As Long, btn As MSForms.CommandButton
Dim o As clsButtonClick
currQty = 0 'number selected
maxQty = 5 'max selectable
Set btnCol = New Collection
'add some buttons
For i = 1 To NUM_BUTTONS
Set btn = Me.Controls.Add("Forms.CommandButton.1", "btn" & i, True)
btn.BackColor = CLR_NOT_SEL
btn.Height = 18
btn.Left = 20
btn.Top = 20 * i
btn.Caption = "Button " & i
Set o = New clsButtonClick
Set o.ButtonEvent = btn
btnCol.Add o
Next i
End Sub
'handle a button click event (button is passed in)
Sub HandleClick(btn As MSForms.CommandButton)
If btn.BackColor = CLR_SEL Then
btn.BackColor = CLR_NOT_SEL
currQty = currQty - 1
Else
If currQty = maxQty Then
MsgBox "no more selections available"
Else
btn.BackColor = CLR_SEL
currQty = currQty + 1
End If
End If
End Sub
Here's something to get you started.
Create a wrapper class which wraps each button and handles its click event. Then when the form loads, loop through the controls and wrap the buttons.
A module level collection is required to hold the references of wrapped buttons (wrapper classes).
The ButtonWrapper class:
Option Explicit
Private WithEvents objButton As MsForms.CommandButton
'Wrap button
Public Function WrapCommandButton(btn As MsForms.CommandButton) As ButtonWrapper
Set objButton = btn
Set WrapCommandButton = Me
End Function
'Button's event handler
Private Sub objButton_Click()
MsgBox objButton.Caption & " was clicked."
End Sub
'Clean up
Private Sub Class_Terminate()
Set objButton = Nothing
End Sub
The code behind the Form:
Option Explicit
Private m_handlers As Collection
'Initialize
Private Sub UserForm_Initialize()
Set m_handlers = New Collection
Dim ctl As Control
For Each ctl In Me.Controls
If TypeName(ctl) = "CommandButton" Then
With New ButtonWrapper
m_handlers.Add .WrapCommandButton(ctl)
End With
End If
Next ctl
End Sub
'Clean up
Private Sub UserForm_Terminate()
Set m_handlers = Nothing
End Sub
Hope this helps.

How to textbox checks itself

I am trying to do textboxes which would be validating input (for numbers only).
I am still new to classes and am little bit confused about some things, but trying my best to learn.
I have multiple textboxes in userform and want to every one of them to be numeric input only.
For the begining I started to check just one textbox (vzdalenost1).
First code just to create connection between textbox and class
Dim chk As New Class1
Private Sub UserForm_Initialize()
Set chk.ChkEvents = Controls("Vzdalenost1")
End Sub
Second code is actual class module
Option Explicit
Public WithEvents ChkEvents As MSForms.TextBox
Private Sub ChkEvents_change()
If IsNumeric(Me.Value) Or Me.Value = "" Then
Else
MsgBox "blablabla"
Me.Value = ""
End If
End Sub
When I try to write something into textbox "vzdalenost1" excel shows error message "Method or data member not found"..
I have even something like replacing "me.value" for "me.control.value" which didnt work either..
The keyword Me in this context refers to the class object itself, not the textbox. Use ChkEvents to refer to the textbox - ChkEvents.Value.
Here's how you could re-write your code to deal with multiple textboxes. First, let's give your class a meaningful name, let's call it clsTextbox. Then, in keeping with the principle of encapsulation, let's declare the object in your class as Private, instead of Public. This will prevent the public from having direct access to it. Instead, we'll use a member function to assign a textbox to a class object. So the code for our class module would look like something this...
Option Explicit
Private WithEvents tb As MSForms.TextBox
Private Sub tb_Change()
MsgBox tb.Name
End Sub
Public Function SetTextbox(ByRef obj As MSForms.TextBox)
Set tb = obj
End Function
Then, for our userform, first we declare a collection at the module level to hold our class objects. Then, since we will be creating multiple class objects, we declare a class object without the keyword New. Instead, we'll use that keyword each time we create a new object, and then we'll add that new object to our collection. So the code for our userform would look something like this...
Option Explicit
Dim textboxCollection As Collection
Private Sub UserForm_Initialize()
Set textboxCollection = New Collection
Dim cTextbox As clsTextbox
Dim ctrl As MSForms.Control
For Each ctrl In Me.Controls
If TypeName(ctrl) = "TextBox" Then
Set cTextbox = New clsTextbox
cTextbox.SetTextbox ctrl
textboxCollection.Add cTextbox
End If
Next ctrl
End Sub
If I get your question correctly you want a textbox to accept only numbers, correct?
In that case try this:
Private Sub TextboxName_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
char = Chr(KeyAscii)
KeyAscii = Asc(UCase(char))
Debug.Print KeyAscii
If KeyAscii >= 48 And KeyAscii <= 57 Then
Debug.Print "number"
Else
Debug.Print "other"
KeyAscii = 0
End If
End Sub
Just replace TextboxName in the Sub name with whatever name you have and it should work.

Adding item in combobox when combobox is created on click event

I am trying to add items in comboBox. I know how to do this, with myBox.AddItem "words" However I have an issue to implement this to my code as I create the comboBox on click event :
Private Sub CommandButton2_Click()
Dim editBox As MSForms.Control
Dim testBox As ComboBox
Static i
Set editBox = Me.Controls.Add("Forms.ComboBox.1")
i = i + 1
With editBox
.Name = "cmBox" & i
.Top = i * editBox.Height + 10
.Left = 130
End With
End Sub
Do you have any suggestions ?
Thank you
Assuming the items are known at the time you're creating the controls, ...you can do that in the handler directly. Just set testBox to your editBox reference:
Set testBox = editBox 'basically cast from MSForms.Control to MSForms.ComboBox
testBox.Add "test1"
testBox.Add "test2"
On the other hand, if the items are not known at the time you're creating the controls, you have a problem:
Dim testBox As ComboBox
The object is locally-scoped, and goes out of scope as soon as the click handler exits.
You need to hold on to it. Move that declaration to module-level (and qualify it, for consistency):
Private testBox As MSForms.ComboBox
Then you can invoke testBox.Add.... the problem is that you're going to be adding more than one control, so you can't just have one field like this. Have a Collection instead:
Private dynamicControls As Collection
Private Sub UserForm_Initialize()
Set dynamicControls = New Collection
End Sub
Now when you create a dynamic control, add it to the collection with a key:
Set editBox = Me.Controls.Add("Forms.ComboBox.1")
'...
dynamicControls.Add editBox, editBox.Name
If you need to handle events for these dynamic controls, you'll need a different setup though, with an instance of a custom class for each dynamic control:
'Class1
Option Explicit
Private WithEvents box As MSForms.ComboBox
Private Sub box_Change()
'...
End Sub
Public Property Get Control() As MSForms.ComboBox
Set Control = box
End Property
Public Property Set Control(ByVal value As MSForms.ComboBox)
Set box = value
End Property
Then when you create a dynamic control, you add it to a new instance of that class instead:
Set editBox = Me.Controls.Add("Forms.ComboBox.1")
'...
Dim dynamicHandler As Class1
Set dynamicHandler = New Class1 'todo rename that class
Set dynamicHandler.Control = editBox
dynamicControls.Add dynamicHandler, editBox.Name
Now when you're ready to add items to a given box, retrieve the control from the dynamicControls collection:
With dynamicControls("cmBox1")
.Add "test1"
.Add "test2"
End With

Excel VBA Userform Multipage and Frame (copy/paste)

I have a Userform Control Panel that I am making for a workbook. I have a page named #1, which is for the workbook sheet #1. I also have an 'Add sheet' button that copies the #1 page and creates a #2 page.
The problem is that the code for the controls on the #1 page do not work on the newly created #2 page. And I don't know what the page #2 controls are called so I can't make code for it beforehand.
This is the Copy/Paste code that I found somewhere. Page 0 is the General settings page and page 1 is the #1 page. I have a Frame taking up the whole Multipage area so it copies the frame and everything in it and copies it.
Option Explicit
Private Sub AddProgramButton_Click()
Dim l As Double, r As Double
Dim ctl As Control
Dim PAGECOUNT As Long
MultiPage1.Pages.Add
MultiPage1.Pages(1).Controls.Copy
PAGECOUNT = MultiPage1.Pages.Count
MultiPage1.Pages("Page" & PAGECOUNT).Paste
MultiPage1.Pages("Page" & PAGECOUNT).Caption = "#" & PAGECOUNT - 1
For Each ctl In MultiPage1.Pages(1).Controls
If TypeOf ctl Is MSForms.Frame Then
l = ctl.Left
r = ctl.Top
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(PAGECOUNT - 1).Controls
If TypeOf ctl Is MSForms.Frame Then
ctl.Left = l
ctl.Top = r
Exit For
End If
Next
End Sub
OK, I haven't got much information from you to go with, but I can make this work by using the following method. If you want to use it, you will have to modify it to suit your own needs.
To follow this example, you will need to create a new UserForm, preferably in a new workbook, and follow my instructions below.
I have created a UserForm as you state, with a Multipage - currently I have pages 0 and 1 on it. Page 0 I am ignoring for the purposes of this example (you mentioned it was just the General Settings page).
Seperate from the Multipage, I have put the main CommandButton (the one which actually adds the new Page when it's clicked) and have named it AddProgramButton as you did.
On Page 1, I have a frame as you state. Within this frame, I have put a CommandButton, a TextBox and a ComboBox on mine. I don't know what your controls are, but you will need to follow my example for now.
Now we need to start entering the code. First, if you haven't already got one, insert a Standard Module in your project. At the top of this standard module, enter the following code:
Option Explicit
Public myButtonArr() As New CButton
Public myComboArr() As New CCombo
Public myTextBoxArr() As New CTextBox
Now, in your UserForm module, you should input the following (note that some of this is the information you first provided):
Option Explicit
Private Sub UserForm_Initialize()
Dim ctl As Control
For Each ctl In MultiPage1.Pages(1).Controls
Select Case TypeName(ctl)
Case Is = "CommandButton"
ReDim Preserve myButtonArr(1 To 1)
Set myButtonArr(1).myButton = ctl
Case Is = "ComboBox"
ReDim Preserve myComboArr(1 To 1)
Set myComboArr(1).myCombo = ctl
ctl.AddItem "A"
ctl.AddItem "B"
Case Is = "TextBox"
ReDim Preserve myTextBoxArr(1 To 1)
Set myTextBoxArr(1).myTextBox = ctl
End Select
Next ctl
End Sub
Private Sub AddProgramButton_Click()
Dim l As Double, r As Double
Dim ctl As Control
Dim PAGECOUNT As Long
MultiPage1.Pages.Add
MultiPage1.Pages(1).Controls.Copy
PAGECOUNT = MultiPage1.Pages.Count
MultiPage1.Pages("Page" & PAGECOUNT).Paste
MultiPage1.Pages("Page" & PAGECOUNT).Caption = "#" & PAGECOUNT - 1
For Each ctl In MultiPage1.Pages(1).Controls
If TypeOf ctl Is MSForms.Frame Then
l = ctl.Left
r = ctl.Top
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(PAGECOUNT - 1).Controls
If TypeOf ctl Is MSForms.Frame Then
ctl.Left = l
ctl.Top = r
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(PAGECOUNT - 1).Controls
Select Case TypeName(ctl)
Case Is = "CommandButton"
ReDim Preserve myButtonArr(1 To PAGECOUNT - 1)
Set myButtonArr(PAGECOUNT - 1).myButton = ctl
Case Is = "ComboBox"
ReDim Preserve myComboArr(1 To PAGECOUNT - 1)
Set myComboArr(PAGECOUNT - 1).myCombo = ctl
ctl.AddItem "A"
ctl.AddItem "B"
Case Is = "TextBox"
ReDim Preserve myTextBoxArr(1 To PAGECOUNT - 1)
Set myTextBoxArr(PAGECOUNT - 1).myTextBox = ctl
End Select
Next ctl
End Sub
Now, for each control I have within the frame, we need to create a new Class. Insert three new Class Modules. You must name these as follows:
CButton
CCombo
CTextBox
Now open the CButton class module, and insert the following code:
Option Explicit
Public WithEvents myButton As MSForms.CommandButton
Private Sub myButton_Click()
MsgBox "You clicked the button on one of the pages"
End Sub
Next, open the CCombo class module, and insert the following code:
Option Explicit
Public WithEvents myCombo As MSForms.ComboBox
Private Sub myCombo_Change()
MsgBox "You changed the value of the ComboBox on one of the pages"
End Sub
Finally, open the CTextBox class module, and insert the following code:
Option Explicit
Public WithEvents myTextBox As MSForms.TextBox
Private Sub myTextBox_Change()
MsgBox "You changed some text in the TextBox on one of the pages"
End Sub
Now, if you test your Userform, it should work. You should hopefully be able to modify my example to match your own requirements.
Note: the events in the class module will produce an identical response regardless of which page is selected. You will have to modify the code yourself (or provide more information) to "personalise" the results.
BTW you probably found your original code here: Copy Elements From One Page To Another in Multipage with VBA in Excel.

Resources