I have a multipage, I was successfully able to copy elements of the first page which is my reference page to new pages which is created dynamically.
My question is, how do I set a commandbutton's actions inside a page in a multipage control?
My goal is to click on the button from any page then pops up another form.
How do I do this?
It's pretty hard to adjust from Android to VB. I really appreciate any help from you guys.
This is my code in cloning pages.
i = 0
MultiPage1.Pages.Add
MultiPage1.Pages(i).Controls.Copy
i = i + 1
MultiPage1.Pages(i).Paste
For Each ctl In Me.MultiPage1.Pages(i).Controls
If TypeOf ctl Is MSForms.Label Then
'~~~ code omitted
Select Case ctl.Tag
Case "startTime"
ctl.Caption = "4:00pm"
End Select
End If
Next
this is how it's going to look like.
the button will concatenate all strings inside the page. the concatenated string will be shown on another userform.
You probably would be better off creating a button in the ribbon so that it is available on all pages:
http://chandoo.org/wp/2012/02/27/how-to-add-your-own-macros-to-excel-ribbon/
EDIT:
My bad, I thought you meant a worksheet instead of a VBA MultiPage in a userform.
Check this out. I was able to make this work for me:
Assign code to a button created dynamically
Class1:
Option Explicit
Public WithEvents CmdEvents As MSForms.CommandButton
Private Sub CmdEvents_Click()
MsgBox "yo"
End Sub
Userform with MultiPage object:
Option Explicit
Dim cmdArray() As New Class1
Private Sub CommandButton1_Click()
Dim newControl As Control
Set newControl = Me.MultiPage1.Pages(0).Controls.Add("Forms.CommandButton.1", "NewCommand", True)
newControl.Object.Caption = "hello"
newControl.Left = 50
newControl.Top = 50
ReDim Preserve cmdArray(1 To 1)
Set cmdArray(1).CmdEvents = newControl
Set newControl = Nothing
End Sub
You can do this with a custom class. The class basically has one member Public WithEvents b as CommandButton.
The trick is the WithEvents keyword. You can now insert some code to generically handle the click of a button that is assigned to this class:
Private Sub b_Click()
MsgBox "You clicked " & b.Name 'Modify this event so that different code is executed base on the page/name/etc.
End Sub
In order to make this work, you need to assign the button you create in your code to an object of this new class:
Private objButtonHandler as New MyClass 'this should be scope a UserForm wide
Sub YourSub
Dim objYourButton as CommandButton
Set objYourButton = ... `your code here
Set objButtonHandler.b = objYourButton
End Sub
Related
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
I have multiple buttons (active x) on a spreadhseet in same column but different rows. These buttons capture the start time of an activity.
If button 1 is pressed cell next to it should be populated by current time.
If button 2 is pressed cell next to it should be populated by current time. and so on.....
I have written a SUB in VBA as follows:
Private Sub StartTimer_Click()
Range("I4").Value = Now
End Sub
I do not want to repeat this code for each button action. Please let me know how it can be made dynamic.
A simple WithEvents example:
in a class (named clsButtons):
Private WithEvents Bt As MSForms.CommandButton
Property Set obj(b As MSForms.CommandButton)
Set Bt = b
End Property
Private Sub Bt_Click()
'uses the right of the name of the CommandButton
Cells(1 + Right(Bt.Name, 1) * 3, 9).Value = Now
End Sub
In the sheetcode (the one with the buttons):
Dim myButtons As Collection
Private Sub Worksheet_Activate()
Dim ctl As OLEObject
Dim ButtonClass As clsButtons
Set myButtons = New Collection
For Each ctl In Sheet1.OLEObjects
If ctl.progID = "Forms.CommandButton.1" Then
Set ButtonClass = New clsButtons
Set ButtonClass.obj = ctl.Object
myButtons.Add ButtonClass
End If
Next ctl
End Sub
Create a standard module and put the procedure in there.
While it is possible to share a procedure in a private module, it's best practice to put any shared procedures in a shared module.
In the VBA Editor click Insert > Module,
Paste into there, and give it a unique name. Using your example you could do something like:
Public Sub SetTimeValue()
Range("I4").Value = Now
End Sub
...then call this public stub from your other one, like:
Private Sub StartTimer_Click()
SetTimeValue
End Sub
...and from any other locations where you need to call your code.
I assume that you have more than one line of code for the actual procedure you're concerned about, otherwise copying it repeatedly isn't really a concern.
More Information:
MSDN : Understanding Scope and Visibility
Office Support : Scope of variables in Visual Basic for Applications
Chip Pearson : Understanding Scope Of Variables And Procedures
PowerSpreadsheets : Excel VBA Sub Procedures: The Complete Tutorial
MVP : Cut out repetition using subs and functions with arguments
In Excel I insert an ActiveX Frame into a worksheet. Right clicking this frame allows me to select:
Frame Object>Edit
Now I am able to add a button to this frame. Great.
How do I add a _Click event to this button so that it will run a macro?
Basically, what you need to do is to create you own class, for instance, "XButton". Inside this 'XButton' there will be an event handler for the button object that is inside the frame.
So you can handle all of the events that are sent by 'btn' and forward it further. Then you will have to create a custom interface (empty class) IXButtonEventHandler, that will look something like this:
Option Explicit
Public Sub Click(Sender as XButton)
End Sub
So, your custom class XButton will look like this:
Private WithEvents btn as MSForms.CommandButton
Private mEventHandler as IXButtonEventHandler
Public Sub CreateObject(EventHandlerOf as MSForms.CommandButton, EventHandler as IXButtonEventHandler)
Set btn = EventHandlerOf
Set mEventHandler = EventHandler
End Sub
Private Sub btn_Click()
If not mEventHandler is Nothing then mEventHandler.Click(Me)
End Sub
Let's say, your Workbook will be the event handler and will need to implement the IXButtonEventHandler interface, for instance:
Implements IXButtonEventHandler
Private Sub IXButtonEventHandler_Click(Sender as XButton)
'your code
End Sub
On Workbook_Load or whatnot you will need to create a collection of XButtons and attach them to your frame controls:
Dim xbtn as Collection
Private Sub AttachButtons()
Set xbtn = New Collection
Dim i as Long
For i = 0 to 3
Dim xb as New XButton
xb.CreateObject <YourFrame>.Controls("CommandButton" & Cstr(i)), Me
xbtn.Add xb
Next i
End Sub
As a follow-up to this question, where a userform allows the creation of x # of tabs in a multipage during runtime, each with the same controls, I am wondering how to set the .OnClick behaviour of the buttons. I am trying to achieve this with the following code:
For Each ctl In MultiPage1.Pages(NumSegs - 1).Controls
If TypeOf ctl Is MSForms.CommandButton Then
ctl.Name = "Segment" & NumSegs & "Button"
ctl.OnClick = "Span_Form_Click_Handler"
End If
Next
But of course there doesn't seem to be a .OnClick method...
You define a variable with events.
A minimal example: UserForm + CommandButton on it. Code in UserForm Module:
Public WithEvents btn As MSForms.CommandButton
Private Sub btn_Click()
MsgBox "Bla"
End Sub
Private Sub UserForm_Click()
Set btn = CommandButton1
End Sub
After a click in the form, the button CommandButton gets the sub btn_Click assigned.
To be able to call everytime the same sub, you need to place the sub and the withevents variable into a class and create an instance of this class for each CommandButton you find.
I currently have built a tool in Excel 2003 which displays a series of data entry forms. The client has requested that there be "Previous Form" and "Next Form" buttons on the forms.
The code used to move between the forms is as follows
Sub NextForm(strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
'Find out which form we are currently on from a list in a range
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
'We want to use the first one
intCurPos = 0
End If
'Get the name of the form to open
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
'Load the form into the Userforms Collection
Set newForm = VBA.UserForms.Add(strNewForm)
'Show the form
newForm.Show
End Sub
The issue I have is that after you do this 25 times (yeah I know) the system crashes. I realise that this is because everytime you get to the newForm.Show line above the code doesn't get to complete and so sits in memory.
Modeless forms would stop this issue but then the user could load other forms and do other things which would cause major issues.
Does anyone have any suggestions to help with this? Somehow to force code execution but not stop the modal ability of the form?
Long shot but appreciate any help.
Maybe you should change your approach.
I would suggest the following:
In the main module use a loop to cycle open the form and loop every time the user press the "Next Form" button.
'This sub in your main Module
sub ShowAndCyleForms
Dim FormName As String
Dim MyForm as Object
Dim CloseForm as Boolean
FormName = "frmMyForm"
do while CloseForm=False
set MyForm = VBA.UserForms.Add(FormName)
MyForm.Show
CloseForm=MyForm.CloseStatus
FormName=MyForm.strNewForm
Unload MyForm
Set MyForm = Nothing
loop
end sub
In every form, declare:
Public CloseStatus as Boolean
Public strNewForm as String
In every "Next Form" button, put something like:
Private Sub btnNextForm_Click()
CloseStatus=False
strNewForm= NextForm(Me.Name)
Me.Hide
End Sub
Modify your sub to be a function that delievers the next Form Name
Sub NextForm(strFormName As String)
Dim intCurPos As Integer
'Find out which form we are currently on from a list in a range
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
'We want to use the first one
intCurPos = 0
End If
'Get the name of the form to open
NewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
'
End Sub
You will also need to modify your O.K. to just hide the form instead of unloading it and setting the CloseStatus to true.
The idea is to control all your forms loading/unloading from outside the from in a single procedure.
Hope it is clear enough.