Worksheet_Activate Code for New Sheets - excel

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.

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

Set Listbox selection as variable

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.

VBA excel control multiple checkboxes with 1 macro

I have a quite simple macro to hide the row the checkbox is in when clicked. It works but the problem is there are A LOT of rows.
Private Sub CheckBox3_Click()
[3:3].EntireRow.Hidden = CheckBox3.Value
Range("AB3").Value = True
End Sub
I of course can make a separate macro for every single checkbox i have (all 250 of them), but i hope i can avoid a macropage that is 6 pages long.
My question is: is there a way to combine it into 1? the only thing that is different for all of them is the number (in the example its 3).
You should create those CheckBoxes programatically and create Class Module to handle events. Here is example how to achieve it:
ClassModule i.e. MyCheckBox:
Option Explicit
Dim WithEvents m_CheckBox As MSForms.CheckBox
Dim m_Row As Long
Public Sub CreateCheckBox(ByVal sh As Worksheet, ByVal rowNumber As Long)
m_Row = rowNumber
Set m_CheckBox = sh.OLEObjects.Add(ClassType:="Forms.CheckBox.1", Left:=sh.Cells(rowNumber, 1).Left, Top:=sh.Cells(rowNumber, 1).Top, Width:=108, Height:=19.5).Object
End Sub
Private Sub m_CheckBox_Change()
MsgBox "I'm in row " & m_Row & " MyValue is " & m_CheckBox.Value
End Sub
Module using this code:
Option Explicit
Dim chkBoxes As New Collection
Public Sub test()
Dim sh As Worksheet
Dim chk As MyCheckBox
Set sh = ActiveSheet
Set chk = New MyCheckBox
chk.CreateCheckBox sh, 1
chkBoxes.Add chk
Set chk = New MyCheckBox
chk.CreateCheckBox sh, 2
chkBoxes.Add chk
End Sub
Of course method CreateCheckBox has to be adjusted to your needs (my one is creating checkbox in first column). Global collection in module is required otherwise classes are destroyed automatically when sub is finished and events are never fired. If workbook is opened and closed then this code must be upgraded to either:
1) Recreate classes on Workbook event i.e. open
2) Recreate CheckBoxes every time workbook is opened

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 ;)

userform inside the subfunction

I have created the following userform in workbook .
This is my code
Sub Macro6()
Dim lines As Long
Dim letter As String
Dim no As String
Dim count As Integer
Dim i As Integer
Dim xRow As Long
Dim TargetFiles As FileDialog
Dim xDirect$, xFname$, InitialFoldr$
Dim DataBook As Workbook, OutBook As Workbook
Dim DataSheet As Worksheet, OutSheet As Worksheet
count = Sheets.count
If count > 1 Then
For i = 1 To Sheets.count
If Sheets(i).Name <> "Sheet1" And Sheets(i).Name <> "Execute" And Sheets(i).Name <> "DBCONNECTORS" And Sheets(i).Name <> "Cil Connectors" Then
Sheets(i).Select
Set OutBook = ActiveWorkbook
Set OutSheet = OutBook.Sheets(i)
Dim myValue, myValue1, myValue2, myValue3, myValue4, Myvalue5, myValue6, myValue7, myValue8, myValue9, myValue10, myValue11 As Variant
UserForm1.TextBox1.Value = OutSheet.Name
UserForm1.Show
Windows("DB.xlsm").Activate
Rows("1:1").Select
I am calling UserForm1.Show inside sub function after entering the details in UserForm.
Question: is it possible to execute nextline in sub function once I have issued the UserForm1.Show? If so, how?
Once you have issued the command UserForm1.Show in the sub, the sub stops running and the code on the form (initialize, show, waiting for user input on the form, etc.) will run.
This does not mean that the rest of the is neglected or just dropped. The remainder of the code merely got put on hold. Once the form is closed the rest of the code in your sub should run. Yet, at the point that you initiate the UserForm the focus shifts away from your sub and the Form with all its code and events gets "slid in between".
If you want the sheet to get activated (and the first row selected) before the form is shown then you should move the line UserForm1.Show to the end of the sub and run the lines Windows("DB.xlsm").Activate and Rows("1:1").Select before.
If you want this to happen once the form is shown then you'll have to either:
make the form modeless or
you'll have to shift the rest of your code into a place that gets run once the form is shown (for example UserForm_Initialize).
So, the code for the above two alternatives are:
Alternative 1
'... only copied over the last few rows or your above sub
UserForm1.TextBox1.Value = OutSheet.Name
UserForm1.Show (False)
Windows("DB.xlsm").Activate
Rows("1:1").Select
Alternative 2
'... in the code module of the UserForm us the following
Private Sub UserForm_Initialize()
Windows("DB.xlsm").Activate
Rows("1:1").Select
End Sub
Notes:
The modal functionality does not only ensures that the rest of your code runs. Also - and perhaps most importantly - this allows a user to interact with the sheet and the form simultaneously. The form no longer has the exclusive focus. Read the above referenced link and make sure that this is what you want.
Moving the rest of the code from your sub to UserForm_Inizialize is just a proposition. There are other places you could put the code such as UserForm_Activate or you could even decide for the rest to happen as the first even occurs on the UserForm.

Resources