Not sure how to reference the worksheet object with a variable that changes each time a sheet is activated.
The point is to reference a cell value based on the last worksheet that was activated (this code affects Sheet1 which does not set the variable when activated)
--Module1
Public MyWS as String
--Sheet3 (Deactivation)
MyWS = Sheet3.Codename
--Sheet2 (Deactivation)
MyWS = Sheet2.Codename
--Sheet1
Sheet1.Range("A3").Value = MyWS.Range("A3").Value
Updated:
Thanks for all the guidance but your instructions are not working for my project at least.
Sheet5.Range("C4").Value = Worksheets(MyWS).Range("A2").Value
Subscript out of range error when the above code is executed on Sheet5 deactivate.
MyWS is declared as a public string.
MyWS is assigned the Sheet5.CodeName string when Sheet5 is activated. Sheet5 exists and that is the unmodified codename of the sheet. I can not use the user defined name of the sheet because that can change.
Public MyWS As String declares a String variable, not an object.
CodeName
The CodeName property returns a String that contains an identifier that VBA uses to generate a project-scoped object variable for a Worksheet; in the properties toolwindow (F4), that's the (Name) property.
This is how such code is legal:
Sheet1.Range("A3").Value = 42
Because Sheet1 has a code name string that returns Sheet1. Note that this identifier isn't necessarily the sheet's name (it is by default though), which the user can change at any time without accessing the Visual Basic Editor.
So if you rename the "Sheet1" tab/sheet to "Summary", but don't change its code name, then it will still be Sheet1 in code - so these two instructions do exactly the same thing:
Sheet1.Range("A3").Value = 42
ThisWorkbook.Worksheets("Summary").Range("A3").Value = 42
Now, if you want an object variable holding a reference to a worksheet that exists at compile-time, you already have one - Sheet1 is exactly that.
If you added a worksheet a run-time (doesn't exist at compile-time), then there's no such project-scope object variable for that sheet; that's when you need to declare your own, and assign it with the Set keyword:
Dim someSheet As Worksheet
Set someSheet = ThisWorkbook.Worksheets.Add
ActiveSheet
The Excel object model also has the ActiveSheet object, which returns whatever sheet is currently active.
Sheet1.Range("A3").Value = ActiveSheet.Range("A3").Value
Notice the explicit qualifiers. If it's written in a standard module (.bas), this code is equivalent:
Sheet1.Range("A3").Value = Range("A3").Value
If it's written in the code-behind of a specific worksheet module, then the above code will instead be doing this:
Sheet1.Range("A3").Value = Me.Range("A3").Value
Where Me is whatever the specific worksheet module you're in is, so if you're writing that code in a worksheet module, you will want to explicitly qualify the Range member call with the ActiveSheet object.
Worksheet Events
If you need to execute code when a worksheet is activated, you can handle the SheetActivate event in the ThisWorkbook module:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Dim sheet As Worksheet
If TypeOf Sh Is Worksheet Then
Set sheet = Sh
Else
'Sh is not a worksheet. could be a chart sheet, or something else.
Exit Sub
End If
Debug.Print sheet.Name & " activated!"
End Sub
If you need to handle the Activated event of a specific worksheet that exists at compile-time, you need an event handler for it in that worksheet's code-behind:
Private Sub Worksheet_Activate()
Debug.Print Me.Name & " activated!"
End Sub
If you need to handle that event for a worksheet that is created at run-time, you need a WithEvents object variable in a class module (.cls):
Private WithEvents MySheet As Worksheet
And then you can write a handler for MySheet_Activate in that module, but that's more advanced stuff and I'm barely scratching the surface here, but that should get you going :)
With ActiveSheet as mentioned in the comments is really the best solution.
However, if you want to do it "your way", write these Activate events in every worksheet:
Private Sub Worksheet_Activate()
lastWS = Me.Name
End Sub
Then lastWs would be the name of the ActiveSheet. And you would be able to refer to it like this Worksheets(lastWs). Thus:
Sheet1.Range("A3").Value = Worksheets(lastWs).Range("A3").Value
Related
I have a macro (Excel 2010) and a textbox with name CSVExport in the sheet. Here is the code:
Option Explicit
Sub Export()
Dim exportRangeStr As String
Dim currSheet As Worksheet
Set currSheet = Worksheets("Skill Experience")
exportRangeStr = currSheet.CSVExportRange.Value
Debug.Print 1
When I try to run it I get an error "Compile error: Method or data member not found" with .CSVExportRange highlighted. But when I comment that line out and set a breakpoint in the last line I see in the watch window that currSheet has the CSVExportRange field and all its data properly stored. What am I doing wrong?
The generic Worksheet class does not have a CSVExportRange member. You either need to declare your currSheet variable as an Object, or using the specific code name for the worksheet in question (or the relevant interface if you've set one up).
I'm very new at VBA ad I have the following problem.
I want to reference worksheets by codenames (because the tab name can be modify by the user)
I know that is not possible add a new worksheet specifying the codename.
In a running of my macro I create a new Worksheet using:
Worksheets.Add().Name = "aSheet"
st = Worksheets("aSheet").CodeName
now I have the codename in variable st.
In a following run of the macro (in one in which I don't create the new worksheets) I want to access the previous created worksheet by codename i.e. I
want to use code with codename hard coded. I don't want to use
st = Worksheets("aSheet").CodeName
because between the two runs of the macro the user must have changed the tab "aSheet" name.
That seems impossible to me, but I hope to be wrong.
Instead of going to the workbook's Worksheets collection, just refer directly to the worksheet by its codename:
debug.print Sheet5.name
You can also use it's index if you are super into the Worksheets collection:
debug.print Sheets(5).name
You might find it helpful to save the worksheet as a global variable (declared outside the scope of your function or subroutine. The global variable will be available after code execution, but will be reset if the workbook is closed and reopened.
Dim st As Worksheet
Sub addWorksheet()
Set st = ThisWorkbook.Worksheets.Add()
st.Name = "test3"
Debug.Print st.Name, st.CodeName
End Sub
That variable st is a worksheet object and can be referenced in any other subroutine or function after it's set.
...later on
Sub printWSName()
Debug.print st.name
End Sub
Finally you may want to save this value if the workbook closes. In your Workbook Object in the Project pane you can use the Workbook_BeforeClose and Workbook_Open events to save and recapture this value:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Worksheets("savedStuff").Cells(1, 1).Value = st.Name
End Sub
Private Sub Workbook_Open()
Set st = Worksheets(st.Name)
End Sub
What I want to achieve:
I want to assign copy of a worksheet to variable, for later use.
What I tried and results
First : The code below works fine. Something like this I would like to achieve, but using worksheet.copy.
Sub DuplicateSheetRenameFirst()
Dim wsDuplicate As Worksheet
Set wsDuplicate = Worksheets.Add
wsDuplicate.Name = "Duplicate"
End Sub
Second : Using the copy method, creates a worksheet in current workbook, but generates a Runtime error 424 - Object required.
Sub DuplicateSheetRenameSecond()
Dim wsDuplicate As Worksheet
Set wsDuplicate = Worksheets("Sheet1").Copy(after:=Worksheets(Worksheets.Count))
'above line : runtime error 424 object required, but the sheet is created
wsDuplicate.Name = "Duplicate"
End Sub
Third : Creates a worksheet in new workbook (so creates book, then sheet), but still generates the same Runtime error 424 - Object required.
Sub DuplicateSheetRenameThird()
Dim wsDuplicate As Worksheet
Set wsDuplicate = Worksheets("Sheet1").Copy
'above line : runtime error 424 object required, but the sheet is created in new workbook
wsDuplicate.Name = "Duplicate"
End Sub
Workaround : I can modify any of the second or third way to at first copy the sheet and then set the variable to activesheet, but I was wandering if there is a one step way of doing this. I'm not sure if this would work all the time, since the activesheet may not be the one just copied, maybe.
The Question:
Is there a simple (one step) way to store the copy of a worksheet in a variable? Preferably without errors or without filtering the error with error handler.
This is maybe ok?
Sub copySheet()
Dim ws As Excel.Worksheet
Excel.ThisWorkbook.Sheets("Sheet1").Copy After:=Sheets(1)
Set ws = Excel.ThisWorkbook.ActiveSheet
End Sub
It is unfortunate that in this case you need to use an Active... object. Generally it is good practice to avoid Active... objects.
You cannot do this though as the method .copy is not returning an object of the worksheet class:
Sub copySheet()
Dim ws As Excel.Worksheet
Set ws = Excel.ThisWorkbook.Sheets("Sheet1").Copy(After:=Sheets(1))
End Sub
Some further explanation is in this previous post:
Why does Worksheet.Copy not return a reference to the new workbook created
In MSDN it is not altogether obvious that the method returns nothing:
https://msdn.microsoft.com/EN-US/library/office/ff837784.aspx
...but in your friend Excel's Object Explorer it is more obvious. If it returned a worksheet object then by the arrow would read:
Sub Copy([Before], [After]) as Worksheet
I have a file where there's a template sheet that needs to run some code when it's activated. This sheet is being duplicated to create sheets that don't need to run this code. Currently, I have the code to check for worksheet's codename when run so that it does nothing on extra sheets, but it still slows usage down when you switch between sheets.
Is there any way to make the macro that makes duplicates also clear their VBA code contents?
(Edit) Please note that the code I need to clear is not in a module. After some research, it seems I found a way to remove modules (by accessing VBProject.VBComponents), but I'm not sure how to access the VBA code of a worksheet.
To remove complete code in all Sheet modules you could try something like this:
Sub Remove_some_vba_code()
Dim activeIDE As Object 'VBProject
Set activeIDE = ActiveWorkbook.VBProject
Dim Element As VBComponent
Dim LineCount As Integer
For Each Element In activeIDE.VBComponents
If Left(Element.Name, 5) = "Sheet" Then 'change name if necessary
LineCount = Element.CodeModule.CountOfLines
Element.CodeModule.DeleteLines 1, LineCount
End If
Next
End Sub
Another way you could approach this is to keep all of your code out of the worksheet. Then you don't have to delete anything. The worksheet's code module is a handy place to code events, but you can create your own class module to handle events too. Put this in a standard module:
Public gclsEvent As CEvent
Sub Auto_Open()
Set gclsEvent = New CEvent
Set gclsEvent.This = Sheet1
End Sub
This will create an instance of CEvent that's global, so it won't lose scope as long as your workbook is open. It assigns the worksheet codenamed Sheet1 to the This property of the class. Create a class module named CEvent with this code
Private WithEvents mwsThis As Worksheet
Public Property Set This(ByVal wsThis As Worksheet): Set mwsThis = wsThis: End Property
Public Property Get This() As Worksheet: Set This = mwsThis: End Property
Private Sub mwsThis_Activate()
Me.This.Copy , Me.This.Parent.Sheets(Me.This.Parent.Sheets.Count)
End Sub
The WithEvents keyword exposes events for that object. Since we're only hooking up the events for Sheet1, activating another sheet won't trigger the code.
I am creating a macro and part of the macros function is to make VBA create a new spreadsheet. Because of the nature of distribution the name will change. I need to add code to this spreadsheet. Is there anyway I can do this?
Jook has already explained how it works. I will take it a step further.
The syntax of adding a worksheet is
expression.Add(Before, After, Count, Type)
If you check inbuilt Excel's help then you can see what Before, After, Count, Type stands for
FROM EXCEL"S HELP
Parameters (All 4 parameters are Optional)
Before - An object that specifies the sheet before which the new sheet is added.
After - An object that specifies the sheet after which the new sheet is added.
Count - The number of sheets to be added. The default value is one.
Type - Specifies the sheet type. Can be one of the following XlSheetType constants: xlWorksheet, xlChart, xlExcel4MacroSheet, or xlExcel4IntlMacroSheet. If you are inserting a sheet based on an existing template, specify the path to the template. The default value is xlWorksheet.
Once the sheet is created then you need to use .insertlines to create the relevant procedure and to also embed the code that you want to run.
NOTE - IMP: If you want the code to embed code in the VBA project, you need to ensure that you have "Trust Access to the VBA Project Object Model" selected. See snapshot.
Here is an example where I am creating a sheet and then embedding a Worksheet_SelectionChange Code which will display a message "Hello World"
CODE - TRIED AND TESTED
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim nLines As Long
Dim VBP As Object, VBC As Object, CM As Object
Dim strProcName As String
Set ws = Worksheets.Add
Set VBP = ThisWorkbook.VBProject
Set VBC = VBP.VBComponents(ws.Name)
Set CM = VBC.CodeModule
strProcName = "Worksheet_SelectionChange"
With ThisWorkbook.VBProject.VBComponents( _
ThisWorkbook.Worksheets(ws.Name).CodeName).CodeModule
.InsertLines Line:=.CreateEventProc("SelectionChange", "Worksheet") + 1, _
String:=vbCrLf & _
" Msgbox ""Hello World!"""
End With
End Sub
This is how the new sheet code area looks once you run the above code.
the following code will add you a spreadsheet.
Public Sub Workbook_Add()
Dim wks As Worksheet
Set wks = ThisWorkbook.Worksheets.Add(, , 1, xlWorksheet)
With wks
'set codename of wks
ThisWorkbook.VBProject.VBComponents(.CodeName).Name = "tblWhatever"
'set tablename of wks
.Name = "whatever"
'add code (untested demo)
'ThisWorkbook.VBProject.VBComponents(.CodeName).CodeModule.InsertLines 1, "Option Explicit"
'add code (as of example from excel-help)
'Application.VBE.CodePanes(1).CodeModule.InsertLines 1, "Option Explicit"
End With
End Sub
If you need to add VBA-Code to this specific spreadsheet, you should further inspect the VBProject object - look for CodeModule and then i.e. InsertLines.
A further hint for you - I would try to use the CodeNames of your tables. It is less likely to be changed - BUT it might be not that comfortable to use in your code at first. I had to get used to it, but for me it has many advantages against using a tables name.
Hope this helps ;)
The default .Add method adds a sheet at the start of the list. Often you want to add it at the end before adding the code lines, as explained by Siddarth Rout. To do that anywhere you can use:
ActiveWorkbook.Worksheets.ADD After:=ActiveWorkbook.Sheets(ActiveWorkbook.Worksheets.Count)
It is easier to read if you have defined and set WB:
Dim WB as Excel.workbook
Set WB = ActiveWorkbook
WB.Sheets.ADD After:=WB.Sheets(WB.Sheets.Count)
Set VBC = ActiveSheet 'If using in Siddarth Rout's code above
Sheets and Worksheets are interchangeable, as illustrated.