Let's say I have a user form with two combo boxes created at runtime. First combo sources items from A1:A2 (Fruits, Vegetables). I want the second combo to source either from B1:B2 (Apple,Orange) or from C1:C2 (Carrot,Tomato) deppending what user chose in first combo. Below my class named "FoodClass"
Option Explicit
Public WithEvents MyCombo1 As MSForms.ComboBox
Public WithEvents MyCombo2 As MSForms.ComboBox
Public Sub DrawCombo1(oParent)
Set MyCombo1 = oParent.Controls.Add("Forms.ComboBox.1", "Combo1", True)
With MyCombo1
.Top = 20
'(...) setting position
.RowSource = "A1:A2" 'Fruits;Vegetables
End With
End Sub
Public Sub DrawCombo2(oParent)
Set MyCombo2 = oParent.Controls.Add("Forms.ComboBox.1", "Combo2", True)
With MyCombo2
'(seting position)
End With
End Sub
Public Sub MyCombo1_Change()'When user chose from combo 1...
If MyCombo1.Value = "Fruits" Then MyCombo2.RowSource = "B1:B2" 'Apple;Orange
If MyCombo1.Value = "Vegetables" Then MyCombo2.RowSource = "C1:C2"'Carrot;Tomato
End Sub
Then there is my code in user form:
Option Explicit
Public ComboBox1 As FoodClass
Public Combobox2 As FoodClass
Private Sub UserForm_Initialize()
Set ComboBox1 = New FoodClass
Call ComboBox1.DrawCombo1(UserForm)
Set Combobox2 = New FoodClass
Call Combobox2.DrawCombo2(UserForm)
End Sub
Each time user changes the first combobox Error 91 appears. How can I modify one object by another?
In UserForm_Initialize create just one instance of FoodClass so this instance will have both combo boxes initialized:
dim food as FoodClass
Set food = New FoodClass
call food.DrawCombo1(UserForm)
call food.DrawCombo2(UserForm)
Note: Instead of UserForm the keyword Me can be used inside of the UserForm class. Me refers to current instance of the UserForm.
call food.DrawCombo1(Me)
Related
I am trying to dynamically create an application that will use the name of a button (created at runtime) on the form, to extract a number from the end of it's name:
FundNo = Right(Me.ActiveControl.Name, 1)
It's not working and I suspect it's because my button isn't the active control as I am using the mouse over event to trigger it which I'm guessing doesn't give it the focus.
It's nothing to do with the data type it's just not returning the name of the control.
Does anyone have any ideas as to how I could do this as it will be really cool if I can get it to work?
Thanks and regards, Mark
You are right, the CommandButton is not the active Control when the mouse moves over it. It is possible to do what you want, but not in a single line of code. You need a WithEvents variable in a Class module to return the events from the CommandButtons ... the following is the process, assuming you already have your UserForm set up and it is correctly adding your CommandButtons ... the following uses "MyUserFormName" as the assumed name of your UserForm, change this in the following to the actual name of your UserForm:
Add the following into the code-behind of your UserForm (the Debug.Print is just to test the code works and you can remove it when it is, if you want to):
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
Add a Class module (must be a Class module ... not standard module), name it CCommandButtonEvents, add this code
Option Explicit
Private WithEvents mCommandButton As MSForms.CommandButton
Private mUserForm As MyUserFormName
Sub SetUpCommandButton(cb As MSForms.CommandButton, uf As MyUserFormName)
Set mCommandButton = cb
Set mUserForm = uf
End Sub
Private Sub mCommandButton_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
mUserForm.CommandButtonMovement mCommandButton
End Sub
... remember to change "MyUserFormName" to the actual name of your UserForm.
Back in your UserForm code-behind, for each CommandButton you create, you need to add the following at the module level:
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
... as many as you are adding CommandButtons
Finally, assuming you are dynamically creating your CommandButtons in UserForm_Activate(), then add (where cb1, cb2 is assumed to be the names of the CommandButton ... change this as required) in that method:
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
... again, as many as you are adding CommandButtons
Run your code. That's a lot of changes ... I thought it might help to post a full code example:
Class CCommandButtonEvents - as above
UserForm MyUserFormName
Option Explicit
Dim FundNo As Variant
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim cb1 As MSForms.CommandButton
Set cb1 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton1", True)
cb1.Caption = "First button"
cb1.Top = 20
cb1.Left = 20
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Dim cb2 As MSForms.CommandButton
Set cb2 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton2", True)
cb2.Caption = "Second button"
cb2.Top = 20
cb2.Left = 100
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
If you are adding your CommandButtons in a loop then the example code will need adjusting for that eg to use an Array instead of individual CCommandButtonEvents variables at point 3 etc
EDIT: updated code per the questioners comments, to handle adding CommandButtons in an array and to handle multiple events (MouseMove and Click)
The UserForm code-behind needs some fairly serious re-working though the core principles are the same ... this is the new code that includes an array to hold the CommandButtons and a loop to add them all (change the value of mBtnCount to the number of CommandButtons you want added ... ensure the UserForm is large enough that you can see them all!)
Option Explicit
Const mBtnCount As Long = 5
Dim FundNo As Variant
Dim mCmdBtns() As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim i As Long
ReDim mCmdBtns(1 To mBtnCount)
For i = 1 To mBtnCount
Dim cmdBtn As MSForms.CommandButton
Set cmdBtn = Me.Controls.Add("Forms.CommandButton.1", "CommandButton" & CStr(i), True)
cmdBtn.Caption = "Button " & CStr(i)
cmdBtn.Top = 4 + 26 * (i - 1)
cmdBtn.Left = 4
Set mCmdBtns(i) = New CCommandButtonEvents
mCmdBtns(i).SetUpCommandButton cmdBtn, Me
Next i
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Movement " & FundNo
End Sub
Sub CommandButtonClick(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Click " & FundNo
End Sub
... note there is a new Sub 'CommandButtonClick' that the code in CCommandButtonEvents will call.
Finally, to handle the Click event, in the CCommandButtonEvents Class module (you should normally use the drop-downs at the top of the code window to add the new event handlers to ensure the event 'signature' is correct), add this Sub which calls the new Sub in the UserForm:
Private Sub mCommandButton_Click()
mUserForm.CommandButtonClick mCommandButton
End Sub
I am trying to populate a textbox on a userform with a public variable to allow the user to copy and paste into an external program. I can pass basic strings into the textbox but cannot seem to pass a variable. Here is my current code:
VBA Button-click
Option Explicit
Public PathConfig As String
Public BuildableLand As String
Public Sub Import_Click()
PathConfig = "TestConfig"
BuildableLand = "TestBuildable"
CopyPaste.Show
CopyPaste.ConfigText.Text = PathConfig
CopyPaste.BuildableLandText.Text = BuildableLand
End Sub
Userform
Textboxes are named 'ConfigText' and 'BuildableLandText' respectively
Userform Code
Private Sub ExitForm_Click()
Unload Me
End Sub
Private Sub UserForm_Initialize()
CopyPaste.ConfigText.Text = PathConfig
CopyPaste.BuildableLandText.Text = BuildableLand
End Sub
When I step through the code it doesn't seem like the PathConfig/BuildableLand variables are holding their value over to the userform. Is Public variable not sufficient?
A slightly improved version of the code above could look like that
Option Explicit
Private Sub Import_Click()
Dim frm As CopyPaste
Set frm = New CopyPaste
With frm
.ConfigText.Text = PathConfig
.BuildableLandText.Text = BuildableLand
.Show
End With
End Sub
Additionally create an extra sheet with the codename shConf for the configuration values you need and a module with the following functions
Option Explicit
Function PathConfig() As String
'Using Names instead of a direct cell reference would probably better
PathConfig = shConf.Range("A1")
End Function
Function BuildableLand() As String
BuildableLand = shConf.Range("A2")
End Function
There is no code in the userform initialize event needed.
You should also reconsider using Unload in the exit event of the userform as this destroys the class. Use Hide instead! With Unload the userform no longer exists and you cannot retrieve the user input.
I have a userForm (mappingGuide) that allows user to pick a smartyTag from a list of more user-friendly names.
I have a second user-form (conditionalBuilder) that I would like to call this userForm upon double-clicking a text field so that a user can lookup which smartyTag to apply (in case they don't know).
So logic, is:
open conditionalBuilder
double-click Field text box
mappingGuide opens
pick a smartytag from listbox
fill smartytag value into field text-box in conditionalBuilder
unload mappingGuide
The issue I think I having with completing the requirement is that when I load the forms themselves I cannot find a way to set the text of the fieldName textbox of the loaded instance of conditionalBuilder (see last code block below). I've been searching around, but cannot figure it out.
Here is relevant code:
conditionalBuilder loads from Custom UI ribbon
Sub RunCode(ByVal Control As IRibbonControl)
Select Case Control.ID
Case Is = "mapper": LoadMappingGuide
Case Is = "conditional": LoadConditionalBuilder
End Select
End Sub
Sub LoadConditionalBuilder()
Dim conditionalForm As New conditionalBuilder
conditionalForm.Show False
End Sub
double-click event of fieldName then loads mappingGuide
Private Sub fieldName_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.hide
Dim pickField As New mappingGuide
pickField.Show False
End Sub
smartTag listbox click event then attempts to place selection into fieldName (or selection if form not loaded)
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
If isUserFormLoaded(conditionalBuilder.Name) Then
'*** ---> below is my issue how to reference instance of form
conditionalBuilder.fieldName.Text = smartyTag
conditionalBuilder.Show
Else
Selection.Range.Text = smartyTag
End If
End If
Unload Me
End Sub
If there is a better set-up that would be great to know too. I have the forms separate because there's a couple of levels a user can create tags with.
This is how I would do it, a bit of overkill but in case of multiple forms it will be beneficial.
Module 1:
Option Explicit
Sub test()
frmMaster.Show False
End Sub
Form 1 : frmMaster:
Option Explicit
'/ Declare with events
Dim WithEvents frmCh As frmChild
Private Sub TextBox1_DblClick(ByVal cancel As MSForms.ReturnBoolean)
handleDoubleClick
End Sub
Sub handleDoubleClick()
If frmCh Is Nothing Then
Set frmCh = New frmChild
End If
frmCh.Show False
End Sub
'/ Handle the event
Private Sub frmCh_cClicked(cancel As Boolean)
Me.TextBox1.Text = frmCh.bChecked
End Sub
Form 2: frmChild:
Option Explicit
Event cClicked(cancel As Boolean)
Private m_bbChecked As Boolean
Public Property Get bChecked() As Boolean
bChecked = m_bbChecked
End Property
Public Property Let bChecked(ByVal bNewValue As Boolean)
m_bbChecked = bNewValue
End Property
Private Sub CheckBox1_Click()
Me.bChecked = Me.CheckBox1.Value
'/ Raise an event when something happens.
'/ Caller will handle it.
RaiseEvent cClicked(False)
End Sub
You can do this with a presenter class which controls userform instances and pass values between them. I mocked up something similar to give you an idea.
Presenter. This is a class module which creates the userforms, controls their scope, and catches the event thrown by the
ConditionalBuilder. It makes it super easy to pass values between
userforms.
Private WithEvents CB As ConditionalBuilder
Private MG As MappingGuide
Public Sub ShowCB()
Set CB = New ConditionalBuilder
CB.Show vbModal
End Sub
Private Sub CB_ShowMappingGuide()
Set MG = New MappingGuide
MG.Show vbModal
CB.UpdateTB1 Value:=MG.SmartTag
End Sub
ConditionalBuilder.
This has a simple function to update your textbox and also an event which raises actions in the presenter.
Public Event ShowMappingGuide()
Public Function UpdateTB1(Value As String)
TextBox1.Value = Value
End Function
Private Sub TextBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
RaiseEvent ShowMappingGuide
End Sub
MappingGuide.
The Type and Property could be overkill since we just want one value from the mapping guide but it's still good practice.
Private Type TView
Tag As String
End Type
Private this As TView
Public Property Get SmartTag() As String
SmartTag = this.Tag
End Property
Private Sub UserForm_Initialize()
Tags.List = Array("a", "b", "c")
End Sub
Private Sub Tags_Click()
this.Tag = Tags.List(Tags.ListIndex, 0)
Me.Hide
End Sub
I have one final Standard Module which creates the Presenter. This is what you'd hook up to your ribbon.
Public Sub ShowProject()
With New Presenter
.ShowCB
End With
End Sub
Step 1 (double click text field)
Step 2 (selecting "b")
Step 3 (result)
I actually solved it by placing the below block inside the IF where I check for the form being loaded and I will leave open for better answers, if there are any.
Dim uForm As Object
For Each uForm In VBA.UserForms
If uForm.Name = conditionalBuilder.Name Then
uForm.fieldName.Text = smartyTag
uForm.Show
End If
Next
I have got a piece of code that works which changes a input field from disabled to enabled and changes the colour from gray to white if the corresponsding checkbox is ticked.
Is there a way to loop this or call it for all checkboxes & input fields without having a seperate piece of code for each pair?
The code I have is:
Private Sub CheckBox1_Click()
If CheckBox1.Value = True Then
tb01.Enabled = True
tb01.BackColor = vbWhite
Else: tb01.Enabled = False
tb01.BackColor = vb3DLight
End If
End Sub
Private Sub CheckBox2_Click()
If CheckBox2.Value = True Then
tb02.Enabled = True
tb02.BackColor = vbWhite
Else: tb02.Enabled = False
tb02.BackColor = vb3DLight
End If
End Sub
edit: This code is in a UserForm
Checkboxes are a collection in Excel, you can do something like this:
Sub SelectCheckboxes()
Dim CB As CheckBox
For Each CB In Sheet1
If CB.Name <> Sheet1.CheckBoxes("SkipThisCheckbox").Name Then
CB.Value = 2
End If
Next CB
End Sub
You change the value to all checkboxes, except the "SkipThisCheckbox" checkbox.
Edit: The idea is that the checkboxes are a collection and I have translated your question as "show me how to select/check checkboxes together and not one by one".
On a form it should be something like this:
Private Sub CommandButton1_Click()
Dim cCont As Control
For Each cCont In Me.Controls
If TypeName(cCont) = "Checkbox" Then
Debug.Print cCont
'True or False if checked
'Write your logic here.
End If
Next cCont
End Sub
the more versatile approach is a module class one
here follows a possible application to your case, assuming that TextBoxes and CheckBoxes in your UserForm are paired on a "Name" property basis like "CheckBox1"-"TextBox1", "CheckBox2"-"TextBox2", ...
in the UserForm code pane place the following code
Option Explicit
Dim chkBoxes(1 To 4) As ChkBox_TxtBox_Class 'array of type "ChkBoxClass" which you define in a Class Module
Private Sub UserForm_Initialize()
Dim nControls As Integer, i As Integer
nControls = 4 '<== set here the number of CheckBox-TextBox pairs you have in the Form
For i = 1 To nControls
Set chkBoxes(i) = New ChkBox_TxtBox_Class 'initialize a new instance of 'ChkBoxClass' class and store it in the array i-th position
With chkBoxes(i)
Set .ChkBox = Me.Controls("CheckBox" & i) 'assign the correct CheckBox control to its ChkBox property
Set .TxtBox = Me.Controls("TextBox" & i) 'assign the correct TextBox control to its TxtBox property
.ChkBox.value = False 'set "unchecked" as checkbox initial value
.SetTexBox ' call the class method that will have the "paired" textbox adjust its state accordingly to checkbox value
End With
Next i
End Sub
add a "Class Module" to your project
either clicking Insert-> Class Module in the VBA IDE main Ribbon menu
or right-clicking anywhere in the VBA IDE Project Window and selecting Insert -> Class Module in subsequent sub-menus
expand the "Class Module" node in the Project Window
if you don't see the Project Window you can open it by clicking View-> Project Window in the main ribbon menu, or press "Ctrl+R"
select the new Class you added (it should be some "Class1" or the likes) and change its name to "ChkBox_TxtBox_Class" in the Property Window "Name" textbox
if you don't see the Property Window you can open it by clicking View-> Property Window in the main ribbon menu or press "F4"
in the Class Module code pane place the following
Option Explicit
'declare class properties of CheckBox and TextBox type to be "paired" in every instance of this class. the CheckBox one will be associated to events
Public WithEvents ChkBox As MSForms.CheckBox ' ChkBox is a property of the class of type CheckBox. it's associated to events
Public TxtBox As MSForms.TextBox ' TxtBox is a property of the class of type TextBox
' events associated to ChkBox class property
Sub ChkBox_Change()
Call SetTexBox 'call the "method" (i.e. a Sub attached to the class) associated to ChkBox property state change
End Sub
Sub SetTexBox()
'sub that sets the state of the associated TextBox accordingly to the state of the associated CheckBox
With Me.ChkBox
If .value Then
TxtBox.Enabled = True
TxtBox.BackColor = vbWhite
Else
TxtBox.Enabled = False
TxtBox.BackColor = vb3DLight
End If
End With
End Sub
run your routine
I have the following problem:
Private Sub TextBox1_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox2_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox3_Change()
Control (this) <<<<----- this is Empty
End Sub
Public Sub Control(asdf As MSForms.TextBox)
asdf.Font.Size = 11
asdf.Font.Bold = True
End Sub
The compiler says that 'this' is empty. What should I put there to recognize the TextBox?
Thx
When you use parentheses in your code you're evaluating Me.TextBox1, which ends up passing a String to Control. If you drop the parens it will work.
Private Sub TextBox1_Change()
Control Me.TextBox1 'without the parens
End Sub
Typically you don't use () when calling a Sub, unless you're using Call
If you end up having a lot of textboxes, it may be easier to customize the event handler in a custom class.
In a standard module, put a globally scoped collection variable
Public gcolTextboxes As Collection
Create a custom class module called CTbxEvents
Private WithEvents mtb As MSForms.TextBox
Public Property Get tb() As MSForms.TextBox
Set tb = mtb
End Property
Public Property Set tb(otb As MSForms.TextBox)
Set mtb = otb
End Property
Private Sub mtb_Change()
tb.Font.Size = 11
tb.Font.Bold = True
End Sub
Finally, in ThisDocument, load up all the textboxes when the document opens.
Private Sub Document_Open()
Dim f As Field
Dim clsTbxEvents As CTbxEvents
'Globally scoped collection to hold the classes
Set gcolTextboxes = New Collection
'Loop throught the fields
For Each f In Me.Fields
'Only fields that are activex controls
If f.Type = wdFieldOCX Then
'only activex controsl that are textboxes
If TypeOf f.OLEFormat.Object Is MSForms.TextBox Then
'create a new class, add the textbox, add to collection
Set clsTbxEvents = New CTbxEvents
Set clsTbxEvents.tb = f.OLEFormat.Object
gcolTextboxes.Add clsTbxEvents
End If
End If
Next f
End Sub
Now any textbox you add will use the same event handler and you don't keep having to call a separate sub. If you truly only have three textboxes, it's probably overkill.
The this keyword is specific to C++/C#/Java, the corresponding keyword for VB/VB.NET is Me