Userform controlled variables within a macro - vba

Morning Guys,
I have ran into a small roadblock with my project. I'm new to VBA and am trying my best to 'learn by doing' but I cannot seem to get my head around macro/userform interactions.
I have a userform with one textbox and 9 checkboxes. This is supposed to show the userform, allow the user to dictate a sheet name, and (from a list of 9 users) select which is active or not (true or false).
In my main sub, I just have a
Allocator.show
command, as you may have guessed, allocator is my userform name.
Then I've sort of just been trying things so I don't know how right the rest of the userform code is;
Private Sub cbGo_Click()
Unload Allocator
End Sub
Private Sub cboxAlison_Click()
If Me.cboxAlison.Value = True Then
AlisonYN = True
Else
AlisonYN = False
End If
End Sub
Private Sub cboxBeverly_Click()
If Me.cboxBeverly.Value = True Then
BevelyYN = True
Else
BevelyYN = False
End If
End Sub
Private Sub cboxCallum_Click()
If Me.cboxCallum.Value = True Then
CallumYN = True
Else
CallumYN = False
End If
End Sub
Private Sub cboxEllen_Click()
If Me.cboxEllen.Value = True Then
EllenYN = True
Else
EllenYN = False
End If
End Sub
Private Sub cboxGeoff_Click()
If Me.cboxGeoff.Value = True Then
GeoffYN = True
Else
GeoffYN = False
End If
End Sub
Private Sub cboxJames_Click()
If Me.cboxJames.Value = True Then
JamesYN = True
Else
JamesYN = False
End If
End Sub
Private Sub cboxLouise_Click()
If Me.cboxLouise.Value = True Then
LouiseYN = True
Else
LouiseYN = False
End If
End Sub
Private Sub cboxMick_Click()
If Me.cboxMick.Value = True Then
MickYN = True
Else
MickYN = False
End If
End Sub
Private Sub cboxTammy_Click()
If Me.cboxTammy.Value = True Then
TammyYN = True
Else
TammyYN = False
End If
End Sub
Private Sub tbRPName_Change()
End Sub
Private Sub UserForm_Initialize()
Dim GeoffYN, TammyYN, CallumYN, JamesYN, MickYN, AlisonYN, BeverlyYN, LouiseYN, EllenYN As Boolean
Dim RP_Name As String
Me.cboxGeoff.Value = True
Me.cboxTammy.Value = True
Me.cboxCallum.Value = True
Me.cboxJames.Value = True
Me.cboxMick.Value = False
Me.cboxAlison.Value = False
Me.cboxBeverly.Value = False
Me.cboxLouise.Value = False
Me.cboxEllen.Value = False
Me.tbRPName = ""
End Sub
All of the named user variables (xxxxYN) are public in my main module.
These are the variables I want to pull back into my main macro as true or false following the user checking the desired boxes, along with the name as a string, and then continue running the original macro.
Any help would be greatly appreciated, I seem to be taking myself round in circles at the moment!
PS if it helps, my userform looks like this;
UserForm
Cheers,
Callum

You wrote "All of the named user variables (xxxxYN) are public in my main module." But we see them declared in userform's Sub UserForm_Initialize, too:
Private Sub UserForm_Initialize()
Dim GeoffYN, TammyYN, CallumYN, JamesYN, MickYN, AlisonYN, BeverlyYN, LouiseYN, EllenYN As Boolean
Dim RP_Name As Stringn
...
even if you declared the same variables as Public in any module, the Userform variables hide their Public namsakes so any Userform setting is not "seen" in other modules
so you'd better remove the Userform dimming statement of the "namesakes" and leave only the Public one
moreover in such a declaration statement as you used, every single variable not explicitly associated with a specific type is implicitly associated to a Variant type
so in the main module you should use a "dimming" statement like follows:
Public GeoffYN As Boolean, TammyYN As Boolean, CallumYN As Boolean, JamesYN As Boolean, MickYN As Boolean, AlisonYN As Boolean, BeverlyYN As Boolean, LouiseYN As Boolean, EllenYN As Boolean
But should all what above get you going, nevertheless I'd recommend you to switch to a "class" approach together with the use of Dictionary object, like follows
in the Allocator code pane place the following code
Option Explicit
Dim chkBoxes() As ChkBx_Class 'array of type "ChkBx_Class" which you define in a Class Module
Private Sub UserForm_Initialize()
Dim nControls As Integer, i As Integer
Dim namesArray As Variant, cbIniValues As Variant
UFInit = True
namesArray = Array("Geoff", "Tammy", "Callum", "James", "Mick", "Alison", "Beverly", "Louise", "Ellen") '<== set here the names to be associated to every CheckBox
cbIniValues = Array(True, True, True, True, False, False, False, False, False) '<== set here the initial values of checkboxes
nControls = UBound(namesArray) + 1 '<== retrieve the number of CheckBoxes you're going to consider in the Form
ReDim chkBoxes(1 To nControls) As ChkBx_Class 'redim the "ChkBx_Class" array
For i = 1 To nControls
Set chkBoxes(i) = New ChkBx_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
.Name = namesArray(i - 1) ' assign the Name property of the Checkbox
.ChkBox.Value = cbIniValues(i - 1) 'set the checkbox correct initial value
Me.Controls("Label" & i) = .Name ' set the corresponding label caption
dealersDict.Add .Name, .ChkBox.Value ' fill the dictionary initial pair of Dealer-name/checkbox-value
End With
Next i
Me.tbRPName.Text = ""
UFInit = False
End Sub
Private Sub cbGo_Click()
Me.Hide
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 "ChkBx_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: they will be associated in every instance of this class.
Public WithEvents ChkBox As MSForms.CheckBox ' "ChkBox" is now a property of the class of type CheckBox. it's associated to events
Public Name As String
' events associated to ChkBox class property
Sub ChkBox_Click()
If Not UFInit Then dealersDict.Item(Me.Name) = Me.ChkBox.Value ' set the dictionary pair of Dealer-name/checkbox-value
End Sub
edit your main sub module as follows
Option Explicit
Public dealersDict As New Scripting.Dictionary
Public UFInit As Boolean
Sub main()
myval = "io"
Dim myKey As Variant
Allocator.Show
Unload Allocator
For Each myKey In dealersDict
MsgBox myKey & ": " & dealersDict(myKey)
Next myKey
End Sub
create a reference to Microsoft Scripting Runtime Library to use Dictionaries.
this is done by choosing Tools➜References command in the Visual Basic Editor (VBE) which pops up a dialog box in whose listbox you are to find "Microsoft Scripting Runtime" to put a check mark next and press OK.
run the main sub
whenever you need to retrieve the boolean value associated to a given name you just have to use
myBool = dealersDict(name)
where name can be:
a string literal with the wanted name ("Alison", "Mick" , ..)
a string variable whose value stores the wanted name, so that somewhere in your code you may have typed:
Dim name as string
name = "Mick"
such an approach gives you a lot of flexibility, since you only have to:
set the names and their initial boolean values in those two arrays (namesArray and cbIniValues) in UserForm_Initialize
make sure you have checkboxes named after "CheckBox1", "CheckBox2", and so on as well as have labels named after "label1", "Label2", and so on
make sure that "CheckBoxX" is aligned with "LabelX"
make sure namesArray and cbIniValues have the same items number as labels and checkboxes

IDK what the actual issue is, but I tried to recreate your issue and just decided to show you what I have. See if any of this helps you at all.
All of this code is in the userform code, not at the module level. When I change the check box values, the values are stored (outside of the main sub, which is validated in the "check" sub click event).

To make you code a little shorter, you can directly assign the value of a checkbox to a variable
Dim test as Boolean
test = me.CheckBox1.Value
You can insert this into the code of your go button

Related

How to tell which dynamic control sent to an event?

This is my first attempt at working with dynamically created controls in a user form. The reason is there will always be a different amount of rows returned by some processing.
I have created a class object cControlEvent with the following code. (I cut out the code not pertaining to the checkbox)
Public WithEvents CHK As MSForms.CheckBox
Private Sub CHK_Change()
** tell me which box was changed **
End Sub
in the code module, I have the following code:
Dim CHK_Evts As New Collection
sub Form_Builder()
**non relevant code deleted****
Set Evt = New cControlEvent
If i_Columns = 1 Then
Set Evt.CHK = ctl
CHK_Evts.Add Evt
Else
** more code**
End if
end sub
What do I need to change/add to be able to get the name of the control that is firing off the change event?
EDITED TO ADD:
I have a series of dynamically created checkboxes and textboxes on each line of a user form, with a checkbox before each line, when the checkbox is checked/unchecked, I need to change the backcolor on all the textboxes in that row. Each control is named by it's type, then row then column like this CHX_1_1 would be a checkbox on row 1 column 1, and TXT_1_5 would be row 1 column 5. So, if I know what the name of the checkbox is, I have all I need to change the other controls on that row with a simple for-next loop.
I am not quite sure if I understand your question correctly. But it seems to me that it boils down to "which FormControl (linked to a particular procedure) caused this sub to run". If that's the case then you should be able to make use of the
Application.Caller
Here is a short video to demonstrate it's use in a very simple environment:
Here's hopefully a full solution showing how to get the properties from the check boxes:
Create a blank userform and add a command button to it.
Add this code to the form (note - CommandButton1_Click should be updated to the name of the button you added).
Public CHK_Evts As New Collection
Private Sub CommandButton1_Click()
Dim ChkBox As Variant
For Each ChkBox In CHK_Evts
MsgBox ChkBox.Position & vbCr & _
ChkBox.Status
Next ChkBox
End Sub
Private Sub UserForm_Initialize()
Dim tmpCtrl As Control
Dim cmbEvent As clsControlEvents
Dim X As Long
For X = 1 To 10
Set tmpCtrl = frmNameParser.Controls.Add("Forms.Checkbox.1", "Name" & X)
With tmpCtrl
.Left = 6
.Top = X * 20 + 24
.Height = 18
.Width = 150
End With
Set cmbEvent = New clsControlEvents
Set cmbEvent.CHK = tmpCtrl
CHK_Evts.Add cmbEvent, "Name" & X
Next X
End Sub
Create a class called clsControlEvents and add this code:
Public WithEvents CHK As MSForms.CheckBox
Public Property Get Position() As String
Position = CHK.Top
End Property
Public Property Get Status() As String
Status = CHK.Value
End Property
Private Sub CHK_Click()
MsgBox CHK.Name
End Sub
The two GET procedures pass information back to the CommandButton1_Click procedure so it can list information about all check boxes on the form (held in the CHK_EVTS collection).
The CHK_Click procedure gives immediate information about the check box being clicked.
http://www.cpearson.com/excel/classes.aspx

Run subprocedure under button -

I have this sub/macro that works if I run it as BeforeRightClick. However, I would like to change it so I can actually use my rightclick and put the macro on a button instead.
So I have tried to change the name from BeforeRightClick.
I have tried with both a normal form button and an ActiveX.
All this + some more code is posted under Sheet1 and not modules
Dim tabA As Variant, tabM As Variant
Dim adrA As String, adrM As String
' Set columns (MDS tabel) where data should be copied to (APFtabel)
'Post to
'P1-6 divisions ' Name adress, etc
Const APFtabel = "P1;P2;P3;P4;P5;P6;E9;E10;E13;E14;E23;N9;N10;N11;N12;N20"
'Load data from
Const MDStabel = "N;O;P;Q;R;S;H;Y;Z;AB;W;AF;T;D;AA;V;"
Dim APF As Workbook
' APFilNavn is the name of the AP form
Const APFilNavn = "APForm_macro_pdf - test.xlsm"
' Const APFsti As String = ActiveWorkbook.Path
Const APFarkNavn = "Disposition of new supplier"
' APsti is the path of the folder
Dim sysXls As Object, APFSti As String
Dim ræk As Integer
Private Sub CommandButton1_Click()
APFormRun
End Sub
' Here I changed it from BeforeRightClick
Private Sub APFormRun(ByVal Target As Range, Cancel As Boolean)
Dim cc As Object
If Target.Column = 8 Then
APFSti = ActiveWorkbook.Path & "\"
If Target.Address <> "" Then
For Each cc In Selection.Rows
Cancel = True
ræk = cc.Row
Set sysXls = ActiveWorkbook
åbnAPF
overførData
opretFiler
APF.Save
APF.Close
Set APF = Nothing
Set sysXls = Nothing
Next cc
End If
End If
End Sub
Private Sub overførData()
Dim ix As Integer
tabA = Split(APFtabel, ";")
tabM = Split(MDStabel, ";")
Application.ScreenUpdating = False
For ix = 0 To UBound(tabM) - 1
If Trim(tabM(ix)) <> "" Then
adrM = tabM(ix) & ræk
If tabA(ix) <> "" Then
adrA = tabA(ix)
End If
With APF.ActiveSheet
.Range(adrA).Value = sysXls.Sheets(1).Range(adrM).Value
End With
End If
Next ix
End Sub
Private Sub opretFiler()
' Here I run some other macro exporting the files to Excel and PDF
btnExcel
btnExportPDF
End Sub
if you put this code in Sheet1, then to access it from a button you need to define its name (in the button) as Sheet1.APFormRun (and I think you need to make it Public).
If you move the sub and everything it calls to a Module (after doing an Insert->Module), then you do not need the Excel Object Name prefix.
A very detailed write-up about scoping is at the link below. Scroll down to the "Placement of Macros/ Sub procedures in appropriate Modules" section: http://www.globaliconnect.com/excel/index.php?option=com_content&view=article&id=162:excel-vba-calling-sub-procedures-a-functions-placement-in-modules&catid=79&Itemid=475
In your code above, I had to comment out all the subs you didn't include just to get it to compile for debugging.
To make a sub accessible to the Macros button or to "Assign Macro..." you have to make it Public
Also to make a sub accessible, it cannot have any passed parameters.
So you will have to remove the passed parameters from the Public Sub APFormRun() definition
Therefore you will have to re-write the initial portion of APFormRun ... currently your APFormRun relies upon getting a passed parameter (Target) of the selected cell that you right-clicked upon. When you press a button, there is no cell that you are right-clicking upon. It is not a cell-identifying Excel event. You will have to obtain the selected cell via the Selection excel object. There are a lot of StackOverflow answers on how to do that.

Manipulating ListBoxes as Objects

I am working on a VBA userform that includes ListBoxes.
So far, when I had to manipulate one or more, I always proceeded like this in my subs, with dlg as the dialogbox name, and it did not pose any problem, given that I never wanted to do anything complicated:
Dim List1 As Object
...
List1 = dlg.GetControl("CBXname")
...
List1.addItem("String",index)
...
Now I would like to do the following in this Sub
...
If (List1.Exists(Cell1.String) = False) Then
List1.addItem(Cell1.String,k)
End If
...
List1.Clear
...
But I can do neither since List1 is an Object. However, if I decide to declare List1 as a Listbox instead, I do not know how to get the proper control on the ListBox from the dialogbox (the current getcontrol gives me an error).
One of the issues with your code is that listbox objects do not have an "exists" property. To check if a value already exists in your listbox items, you will need to loop through the items.
dim i as integer
for i = 0 to List1.listcount - 1
if List1.column(0, i) = myvalue then
'myvalue exists in List1, skip
else
List1.additem myvalue
end if
next i
Where myvalue is whatever value you are trying to add to the listbox. But that brings us to the second issue in your code which is where you add "Cell1.String". If you are trying to add a value from a worksheet range you will need to refer to that range's value, as worksheet ranges do not have a "string" property as you use it here. Ie. Cell1 = Range("A1").value
As for getting control of the listbox, you can simply refer to the objects name as an object of the form. For example, dlg.List1, if the object's name is List1.
Here is a general purpose routine you can call for any list box. The calling code assumes a list box called ListBox1, a text box called TextBox1, and a Command Button called CommandButton. When you click on the button, it searches the listbox for the text from textbox1.
Private Function ExistsInListbox(ByRef aListBox As msforms.ListBox, ByVal Item As String) As Boolean
Dim booFound As Boolean
booFound = False
Dim t As Integer
ExistsInListbox = False
For t = 0 To aListBox.ListCount - 1 'correction, aListBox not ListBox1
If Item = aListBox.List(t) Then
'if we find a match, short-circuit the loop
booFound = True
Exit For
End If
Next
ExistsInListbox = booFound
End Function
private sub CommandButton_click()
Dim answer As String
Dim val As Boolean
val = ExistsInListbox(Me.ListBox1, TextBox1.Text)
If val Then
answer = "found"
Else
answer = "Not Found"
End If
MsgBox "found-" & answer
End Sub

How to Select All Text in TextBox After textBox.Setfocus Using Access VBA

I need to select all the text in a textbox of an Access form when I click (or double click) into it. i tried the following code, unsuccessfully:
Me.txt_CompraPreco.SelStart = 0
Me.txt_CompraPreco.SelLength = Len(Me.txt_CompraPreco)
thanks in advance.
You can use the code shown below. If it doesn't work, place a breakpoint at the first line of code. If it doesn't stop on your breakpoint, then your event is not recognized.
Option Compare Database
Option Explicit
Private Sub txt_CompraPreco_Click()
If Len(Me.txt_CompraPreco & "") = 0 Then Exit Sub
Me.txt_CompraPreco.SelStart = 0
Me.txt_CompraPreco.SelLength = Len(Me.txt_CompraPreco)
End Sub
I was looking for a solution regarding this problem, I have the same issue, however, I have a solution to it, I'm not sure if it's efficient, but here's my code:
'Declare a flag
Public flagDblClick As Boolean
'Double click event
Private Sub txtbox_DblClick (Cancel As Integer)
flagDblClick = True
End Sub
'Mouse up Event
Private Sub txtbox_MouseUp(Button As Integer, Shift As Integer, X as Single, Y as Single)
If flagDblClick Then
flagDblClick = False
txtBox.SelStart = 0
txtBox.SelLength = Len(txtBox.Value)
End If
End Sub
This code will resolve your problem (use with userform).
txt_CompraPreco.SetFocus
Me.txt_CompraPreco.SelStart = 0
Me.txt_CompraPreco.SelLength = Len(Me.txt_CompraPreco)
My trial and error found this.
If your textfield is formatted as a Standard Number and you have set the decimal places to a certain length, you will run into trouble when you enter a single digit. For example if your decimal places in the field properties is set to 2 and you enter "1", you will display "1.00". To get the entire field (1.00) selected, you must specify the .Text property when you determine the .SelLength (not the default .Value property)
Me.txtYourFieldname_GotFocus
Me.txtYourFieldName.SelStart = 0
Me.txtYourFieldName.SelLength = Len(Me.txtYourFieldName.Text)
End Sub
This works for me:
Dim bSelect As Boolean
Private Sub fieldX_Click()
If bSelect Then
'Select text only at first mouse click then user can click again
'and is able to put mouse pointer where he prefers
Me.fieldX.SelStart = 0
Me.fieldX.SelLength = Len(Me.fieldX)
bSelect = False
End If
End Sub
Private Sub fieldX_GotFocus()
bSelect = True
'Select text if field got focus via keyboard, Enter or TAB
'this is not enough if field got focus via mouse click
Me.fieldX.SelStart = 0
Me.fieldX.SelLength = Len(Me.fieldX)
End Sub

Reading Userform Object Values

I created a Userform (manually in the VBA Projectbrowser). I have written VBA code, which fills this Userform with different Objects in runtime (Labels, Optionbuttons etc.). So far everything worked fine
The Userform is filled with data read from my Excel sheets and correctly displayed. However I'm not able to read the inputs from the objects on it (for example Optionbutton - TRUE or FALSE). These objects do not appear anywhere (except on the userform) so that I can link them and use them in another Module.
I guess they are only displayed and not really read into the memory or whatever (initialized !?).
There are two ways to go about it.
WAY 1
Declare your option button object as Public.
Module Code
Public theOpBut As Object
Sub Fill()
If theOpBut.Value = True Then
ActiveSheet.Cells(1, 5) = 1
Else
ActiveSheet.Cells(1, 5) = "NO"
End If
End Sub
Userform Code
Private Sub UserForm_Initialize()
Set theOpBut = UserForm1.Controls.Add("Forms.optionbutton.1", "OptionButton", True)
With theOpBut
.Caption = "Test Button"
'.GroupName = OpButGroupCounter
.Top = 10
.Left = 20
.Height = 16
.Width = 50
.Font.Size = 12
.Font.Name = "Ariel"
End With
End Sub
Private Sub CommandButton1_Click()
Call Fill
End Sub
WAY 2
Declare a Boolean Variable and create a click event of the Option button and then set the value of the Boolean Variable in that click event. To create the click event of the Option button at Run Time, see THIS EXAMPLE
You can then check the value of Boolean Variable in Sub Fill() and act accordingly.