Set Listbox selection as variable - excel

I want to create something like a "Patch" file which I can send to users and they use it to patch their existing templates.
What I'm trying to do is to have a userform with a listbox that shows all currently opened Excel files, users then select the file they want to patch and click a button to run the patch script.
Am very new to userforms and vba as a whole, and am having difficulty trying to set the 'Listbox1.Selection' as a variable that the subsequent patch code can refer to. My code currently for the userform/listbox is as below (It just allows selection of item:
Private Sub UserForm_Activate()
Dim wb As Workbook
For Each wb In Workbooks
If Windows(wb.Name).Visible Then _
ListBox1.AddItem wb.Name
Next
End Sub
Once users select the file, how do I go about setting that as a variable?

How do I go about setting that as a variable?
Private Sub doPatch()
With Me.ListBox1
Dim currIndex&
currIndex = .ListIndex ' assign zerobased index number to variable
' how do I go about setting that as a variable?
Dim currWB
currWB = .List(currIndex, 0) ' get chosen list element in column zero based on current index
' 'or simply:
' currWB = .Value ' sufficient in your case as only one column listed
' display both variables in immediate window of your VB Editor
Debug.Print "zerobased Listindex#: " & currIndex & " ~> " & currWB
' do patch stuff...
End With
End Sub
Eventually ou could call the above procedure either by a command button and/or by doubleclick, e.g. via
Private Sub CommandButton1_Click()
doPatch
End Sub
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
doPatch
End Sub

Add a Command button to your UserForm and add the following code:
Private Sub CommandButton1_Click()
ActiveSheet.Range("A1").Value = ListBox1.Text
End Sub
That will print the Selected option to the A1. You can save it to variable to anything further you want.
Basically ListBox1.Text will give you the selected option.

Related

Set a variable name to a Workbook selected from a VBA ComboBox

I will always have Workbook SQ_Macro_v1 as my main DB.
Two named Workbooks old_wk and new_wk will have different names, as I will choose them among the currently active WB on my computer.
I am going for a VBA ComboBox listing it all to a choice of mine, but in the end I am not able to store the name of my chosen WB.
Sub Macro1()
Dim main_wk, old_wk, new_wk As Workbook
Set main_wk = Workbooks("SQ_Macro_v1.xlsm")
Set old_wk = Workbooks(old_chosen) 'also tried UserForm1.ComboBox1.Value
Set new_wk = Workbooks(FileName_New) '
main_wk.Sheets("Main_DB").Range("C4").Value = old_wk.Worksheets("Sheet 1 Synthese").Range("C35").Value
As I run the UserForm code below, the old_chosen variable I set as empty in the main Sub. It seems that as I close the UserForm after it runs, nothing remains stored. Any clues to keep that variable saved after I close the UserForm?
Option Explicit
Public Sub UserForm_Activate()
Dim vWorkbook As Workbook
ComboBox1.Clear
For Each vWorkbook In Workbooks
ComboBox1.AddItem vWorkbook.Name
Next
End Sub
Public Sub CommandButton1_Click()
If ComboBox1.ListIndex <> -1 Then
Call YourMacro(ComboBox1)
End If
End Sub
Public Sub YourMacro(vWorkbookName As String)
Dim old_chosen As String
old_chosen = Me.ComboBox1.Value
MsgBox "You choose: " & Workbooks(vWorkbookName).Name
End Sub
The MsgBox pops up but no value is stored afterward:
Public Sub YourMacro(vWorkbookName As String)
Dim old_chosen As String
old_chosen = Me.ComboBox1.Value
MsgBox "You choose: " & Workbooks(vWorkbookName).Name
End Sub
You have declared the variable at procedure level and hence it is not visible after the userform is closed.
To make this variable available to all procedures in the project, precede it with the Public statement. Insert a module and paste this there
Public old_chosen As String
Having said that, I would recommend moving Macro1 inside the userform and handle the code from there after declaring the variable at module level

Using VBA userform to select ranges on multiple sheets - sheet changes back to original activesheet

I have a userform which has multiple RefEdit controls. I need the user to select ranges from multiple sheets and the userform has to be complete before the rest of the code can run.
Issue: The activesheet is "Sheet1" when the userform is initiated. Each time I select a range on "Sheet2" and click into the next RefEdit the visible Excel sheet returns to "Sheet1". I'd like the sheet to remain on "Sheet2", since clicking between the sheets significantly increases the time it takes to select the data.
Because I need the userform to be completed before continuing with my code, using "vbModeless" doesn't appear to work.
I've tried to step through the userform events which appeared to be relevant but none were activated when I entered the RefEdit, selected the data, or left the RefEdit.
Thanks in advance for any help!
Edit: Using some input from the responses and doing some more research I think I've figured out the problem and a work around.
RefEdit events such as Change or Exit (I tried all of them I think) don't appear to trigger when a change occurs in the control. So I couldn't write code to manipulate the activesheet when I changed the control. A workaround found here: http://peltiertech.com/refedit-control-alternative/ uses a textbox and inputbox to simulate a RefEdit control and will actually trigger when changes are made! Code is below. To add other "RefEdit" controls you should repeat the code in the Userform_Initialize event for each control, then add another TextBox1_DropButtonClick and update TextBox1 to the name of the new control. In use when the control updates the workbook jumps to the previous activesheet and then returns the desired activesheet. Not as smooth as I'd like but much better than it was.
Code:
Private Sub CancelButton_Click()
Unload Me
End
End Sub
Private Sub OKButton_Click()
UserForm1.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
End
End Sub
Private Sub UserForm_Initialize()
Me.TextBox1.DropButtonStyle = fmDropButtonStyleReduce
Me.TextBox1.ShowDropButtonWhen = fmShowDropButtonWhenAlways
End Sub
Private Sub TextBox1_DropButtonClick()
Dim ASheet As String ' Active sheet
Me.Hide
'Use input box to allow user to select a range
On Error Resume Next
Me.TextBox1.Value = Application.InputBox("Select the range containing your data", _
"Select Chart Data", Me.TextBox1.Text, Me.Left + 2, _
Me.Top - 86, , , 0)
On Error GoTo 0
'Check if there is a sheet name - if the range selected is on the activesheet the output of the inputbox doesn't have a sheet name.
If InStr(1, Me.TextBox1.Value, "!", vbTextCompare) > 0 Then ' there is a sheet name
ASheet = Replace(Split(Me.TextBox1.Value, "!")(0), "=", "") ' extract sheet name
Else ' there is no sheet name
Me.TextBox1.Value = "=" & ActiveSheet.Name & "!" & Replace(Me.TextBox1.Value, "=", "") ' add active sheet name to inputbox output
ASheet = ActiveSheet.Name
End If
Worksheets(ASheet).Activate ' set the active sheet
Me.Show
End Sub
Have you tried something as simple as:
Sheets("Sheet2").Select
somewhere in the beginning of your form code ?
Since you haven't posted your code, it's hard to provide a good answer.
Hope this helps a little :)
This form module worked for me.
Private Sub CommandButton1_Click() 'Cancel Button
Unload Me
End Sub
Private Sub CommandButton2_Click() 'GO Button
Dim newSheet As Worksheet
abc = Split(RefEdit1.Value, "!")
cbn = abc(0)
Unload Me
Set newSheet = Worksheets(abc(0))
newSheet.Activate
End Sub

How to open and run a userform?

I have a userform on an Excel file called "userform":
Private Sub add1_Change()
End Sub
Private Sub add2_Change()
End Sub
Private Sub Calc_Click()
Result.Value = Val(add1.Value) + Val(add2.Value)
End Sub
This userform takes the value from the user and adds them together and shows the result in a textbox.
I want to create a new macro in another workbook named "input". The macro in this workbook should open the userform workbook, enter values in the textbox add1 and add2, then run the userform calculate button.
What I've tried thus far:
Set add1.value to extract a value from say, cell A1, and similarly for add2.value.
Then I created a macro on the input workbook to change the values in cells A1 and A2.
The problem from here is I don't know how to open the userform and click calculate.
Ideally, I would like a macro which opens the userform, enters the data and hits calculate then closes the userform - Rather than editing the userform itself.
You could add the 2 values in the UserForm in this way(its slightly different then you try to do it now):
You use your current code to open the UserForm:
Sub userform()
Workbooks.Open (ThisWorkbook.Path & "\userform.xlsm")
Application.Run "userform.xlsm!Calc"
End Sub
As shown above you don't assign any values this will happen in your userform.xlsm Workbook
Below is the code you put into the sub Initialize of your UserForm:
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = Workbooks("input.xlsx")
Set ws = wb.Worksheets("Input")
Dim i as Integer
Dim k as Integer
UserForm.add1.Value = ws.Range("A2").Value
UserForm.add2.Value = ws.Range("B2").value
UserForm.calc.Value = val(UserForm.add1.Value) + val(UserForm.add2.Value)
End Sub
As shown above calc is changed to a Textbox, therefor you don't need to click a button its directly done when the UserForm is loaded.
You could also use a Label instead of a Textbox.
the code would then change to:
UserForm.calc.Caption = Str( val(UserForm.add1.Value) + val(UserForm.add2.Value) )
#DirkReichel could you elaborate a bit more on this? I've added what you said, but say I wanted to change the value on the add1 textbox how would I call it? Right now, I have this: 'Sub userform() Dim a As Integer Dim b As Integer a = Cells(1, 2) b = Cells(2, 2) Workbooks.Open (ThisWorkbook.Path & "\userform.xlsm") Application.Run "userform.xlsm!Calc" End Sub' the calc macro just opens up the userform, I don't know how to actually "input" data or hit calculate
The answer:
I created 2 WB and just this simple code worked for me ... however: you may need to change the settings of the trust center.
Book1 Module: (the WB with Userform1 holding TextBox1 and CommandButton1)
Option Explicit
Public Function getUF()
Set getUF = UserForm1
End Function
Book2 Module:
Option Explicit
Public ExtUF As Variant
Sub the_UF()
Workbooks.Open "Book1.xlsm"
Set ExtUF = Application.Run("Book1.xlsm!getUF") 'get the Form
Debug.Print ExtUF.TextBox1.Value 'check old value
ExtUF.TextBox1.Value = "dada" 'change it
Debug.Print ExtUF.TextBox1.Value 'check for new value
ExtUF.CommandButton1.Value = True 'hit the button
ExtUF.Show 'show the form
Stop 'to check the userform
ExtUF.Hide 'hide it again
End Sub
Now just run the_UF and check for functionality. If everything does work, adopt it to your code the way you need it.
If you have any questions, just ask ;)

Make vba code work for all boxes

Hello so what i want to do is make this code work for all Check Box's 1-50 I want the code to only effect the box that is clicked.
Private Sub CheckBox1_Click()
If MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbYes Then
ActiveSheet.CheckBox2.Enabled = False
Else
End If
End Sub
I see several options (none of which are pretty since this is VBA).
Option 1: generate the code for all of your check boxes. This is probably the most maintainable. You would first choose reasonable names for all your check boxes (you can assign them by selecting them in Excel and renaming in the top left corner, or run code which will do this for you if you already have a lot of check boxes. This may be useful).
You can then generate the code and have each one of your subprocedues as follows:
'example code for one checkbox
Private Sub chkBox_1_Click()
Call lockMeUp(Sheet1.chkBox_1.Object)
End Sub
After you're done with all your code for each checkbox, you could have your lockMeUp subprocedure as follows:
Sub lockMeUp(chkBox as Object)
If MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbYes Then
chkBox.Enabled = False
End If
End Sub
Option 2: Keep track of all your checked/unchecked statuses through either an Array or a "Settings" hidden sheet, and watch out for that triggered event. You could fire off based off of a sheet's Changed event, and match the row number to your CheckBox number so that you can go off of the Target's row number.
Other options I can think of become more convoluted... I'd be interested to see what other suggestions people have. Thanks!
EDIT You can use some code to refer to a single function as in my example, in conjunction with brettdj's example to get your optimal solution. Bam!
The easy way is to write a class module that will apply one code routine to a collection of Checkboxes
Assuming yu want to run this on all ActiveX checkboxes on the ActiveSheet, then borrowing heavily from Bob Phillip's code from VBAX
Insert a Class Module named clsActiveXEvents
Option Explicit
Public WithEvents mCheckboxes As MSForms.CheckBox
Private Sub mCheckboxes_Click()
mCheckboxes.Enabled = (MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbNo)
End Sub
In a normal module use this code
Dim mcolEvents As Collection
Sub Test()
Dim cCBEvents As clsActiveXEvents
Dim shp As Shape
Set mcolEvents = New Collection
For Each shp In ActiveSheet.Shapes
If shp.Type = msoOLEControlObject Then
If TypeName(shp.OLEFormat.Object.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckboxes = shp.OLEFormat.Object.Object
mcolEvents.Add cCBEvents
End If
End If
Next
End Sub
In case you do not know, all Form Controls are treated as Shapes in a Worksheet.
I have a solution that you need to create a new Module, copy-paste in code below and then from Immediate window to the same module. With some assumptions:
All Check Box Objects are named "Check Box #" where # is a number
No macro named ResetCheckBoxes() in any other modules of the workbook
No macro named CheckBox#_Click() in any other modules of the workbook
Run this ResetCheckBoxes once to enable check boxes and Assign a macro to it for you, with relevant generated codes in the immediate window (you might want to put a pause in the loop every 25 check boxes as line buffer in it are limited).
Sub ResetCheckBoxes()
Dim oWS As Worksheet, oSh As Shape, sTmp As String
Set oWS = ThisWorkbook.ActiveSheet
For Each oSh In oWS.Shapes
With oSh
If .Type = msoFormControl Then
If InStr(1, .Name, "Check Box", vbTextCompare) = 1 Then
.ControlFormat.Enabled = True
sTmp = "CheckBox" & Replace(oSh.Name, "Check Box ", "") & "_Click"
.OnAction = sTmp
Debug.Print "Sub " & sTmp & "()"
Debug.Print vbTab & "ActiveSheet.Shapes(""" & .Name & """).ControlFormat.Enabled = False"
Debug.Print "End Sub" & vbCrLf
End If
End If
End With
Next
End Sub
Example Immediate window output (2 test check boxes):
Happy New Year mate!
To build on the solution offered by #brettdj, since he is specifying ActiveX Controls, I would suggest the following in the Standard Module:
Dim mcolEvents As Collection
Sub Test()
Dim cCBEvents As clsActiveXEvents
Dim o As OLEObject
Set mcolEvents = New Collection
For Each o In ActiveSheet.OLEObjects
If TypeName(o.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckboxes = o.Object
mcolEvents.Add cCBEvents, o.Name
End If
Next
End Sub
The differences are:
I use the OLEObjects Collection because it is more direct and doesn't waste time on non-OLE shapes.
I use TypeName instead of (the mysterious) TypeOf operator because (apparently) the later does not discriminate between OptionButton and CheckBox.
I register the Object Name as Key in the Collection to allow for efficient indexing if required.
EDIT:
I should have followed the link provided by #brettdj before posting. My solution is using the same principles as are outlined there. Hopefully, its convenient to have it documented here as well?

Worksheet_Activate Code for New Sheets

I have three questions about VBA and controlling/manipulating new windows.
I have several sheets set up.
Master | Worksheet1 | Worksheet2 | Notes | Work Orders | Contact Info
1) I have WorkSheet_Activate functions set up on Notes, Work Orders, Contact Info that open up all three sheets in seperate windows and arrange them vertically.
Private Sub WorkSheet_Activate()
ActiveWindow.NewWindow
ActiveWindow.NewWindow
Windows.Arrange ArrangeStyle:=xlVertical
Sheets("Notes").Select
Windows("Mastersheet.xlsm:2").Activate
Sheets("Work Orders").Select
Windows("Mastersheet.xlsm:1").Activate
Sheets("Contact Info").Select
End Sub
The problem with it is that if I can activate these sheets again, it will open more windows. I would like the code to detect if the windows are already open and break if it is.
2) Now, when I navigate to a different sheet, such as Master, I would like the extra windows to close and for the Master sheet to be active. I was using the following code on the Master sheet.
Private Sub WorkSheet_Activate()
Windows("Mastersheet.xlsm:2").Activate
ActiveWindow.Close
Windows("Mastersheet.xlsm:1").Activate
ActiveWindow.Close
ActiveWindow.WindowState = xlMaximized
End Sub
The problem with this code is that if the extra windows aren't open then it will error out. Can I do a logic check of some sort to get this to work? I don't know what values to check...
3) The last problem is that there are new sheets generated dynamically by macros within the workbook. Those new worksheets won't carry the above code that closes multiple windows and focuses on the activesheet. Is there a different object that I should be putting the code to so that it applies to the Master | Worksheet1 | Worksheet2 sheets and any new sheets?
That's a lot of questions. :) For 3, you need to move your events out of where they are and into a custom class module that handles application level events. Start by inserting a new class module into your project (Insert - Class Module). Name that module CAppEvents (F4 to show the property sheet where you can change the name). Then paste this code into the class module
Option Explicit
Private WithEvents mobjWb As Workbook
Private Sub Class_Terminate()
Set mobjWb = Nothing
End Sub
Public Property Get wb() As Workbook
Set wb = mobjWb
End Property
Public Property Set wb(objwb As Workbook)
Set mobjWb = objwb
End Property
Private Sub mobjWb_SheetActivate(ByVal Sh As Object)
Dim wn As Window
If IsSplitSheet(Sh) Then
If Not IsSplit(Sh) Then
CreateSplitSheets Sh
End If
Else
If IsSplit(Sh) Then
For Each wn In Me.wb.Windows
If wn.Caption Like Me.wb.Name & ":#" Then
wn.Close
End If
Next wn
ActiveWindow.WindowState = xlMaximized
Sh.Activate
End If
End If
End Sub
Private Function IsSplitSheet(Sh As Object) As Boolean
Dim vaNames As Variant
Dim i As Long
IsSplitSheet = False
vaNames = GetSplitSheetNames
For i = LBound(vaNames) To UBound(vaNames)
If vaNames(i) = Sh.Name Then
IsSplitSheet = True
Exit For
End If
Next i
End Function
Private Function IsSplit(Sh As Object) As Boolean
Dim wn As Window
IsSplit = False
For Each wn In Me.wb.Windows
If wn.Caption Like Sh.Parent.Name & ":#" Then
IsSplit = True
Exit For
End If
Next wn
End Function
Private Sub CreateSplitSheets(Sh As Object)
Dim vaNames As Variant
Dim i As Long
Dim wn As Window
Dim wnActive As Window
vaNames = GetSplitSheetNames
Set wnActive = ActiveWindow
For i = LBound(vaNames) To UBound(vaNames)
If vaNames(i) <> Sh.Name Then
Set wn = Me.wb.NewWindow
wn.Activate
On Error Resume Next
wn.Parent.Sheets(vaNames(i)).Activate
On Error GoTo 0
End If
Next i
Sh.Parent.Windows.Arrange xlVertical
wnActive.Activate
Sh.Activate
End Sub
Private Function GetSplitSheetNames() As Variant
GetSplitSheetNames = Array("Notes", "Work Orders", "Contact Info")
End Function
Then insert a standard module (Insert - Module) and paste this code
Option Explicit
Public gclsAppEvents As CAppEvents
Sub Auto_Open()
Set gclsAppEvents = New CAppEvents
Set gclsAppEvents.wb = ThisWorkbook
End Sub
Here's what's happening: When you open the workbook, Auto_Open will run and it will create a new instance of your CAppEvents object. Since gclsAppEvents is public (aka global) it won't lose scope for as long as the workbook is open. It will sit there listening for events (because we used the WithEvents keyword in the class).
In the class there's a sub called mobjWb_SheetActivate. This is what will fire whenever any sheet in this workbook is activated. First it checks if the sheet you just activated (the Sh variable) is one of the ones you want to split (using IsSplitSheet). If it is, it then checks to see if it already has been split. If not, it splits them.
If Sh (the sheet you just activated) is not one of the 'split sheets', then it checks to see if a split has been done (IsSplit). IF it has, it closes all the split windows.
If you even want to add, change, or delete sheets that cause a split, you go to the GetSplitSheetNames function and change the Array arguments.
Because we're using a custom class and sniffing for events at the workbook level, you can add and delete sheets all you want.
1) To test if a window is already open, use this function
Function IsWindowOpen(windowTitle As String) As Boolean
Dim i As Long
For i = 1 To Windows.Count
If Windows(i).Caption = windowTitle Then
IsWindowOpen = True
Exit Function
End If
Next
IsWindowOpen = False
End Function
For example:
if not IsWindowOpen("Mastersheet.xlsm:2") then
' code to open windows
end if
2) You can reuse the function again, same idea:
if IsWindowOpen("Mastersheet.xlsm:2") then
' code to close windows
end if
3) Add your code to a module, not to a sheet. Then call the routine from the macro which adds the new sheets after it has done this. If this macro is in a different module, you may have to make sure your Sub is public.

Resources