Is there a way to indirectly reference a sheet by it's CodeName without looping through every sheet to test it's CodeName? For example,
Method 1: The following code does not work (would be ideal if it did though):
Dim ws As Worksheet
Set ws = "mySheet" & myVar
Method 2: But this works (if you hardcode the CodeName in):
Dim ws As Worksheet
Set ws = mySheetExample
Method 3: And this works (for a dynamic approach):
Dim ws As Worksheet
For Each sh In ThisWorkbook.Worksheets
If sh.CodeName = "mySheet" & myVar Then
'do something
End If
Next
which is a really long winded way of doing things, which brings me to my question, why does the first method not work?
It will not work that way because you are trying to assign a string to a worksheet object. Select Case is the way to go. OR
Try this
Set ws = Sheets(ThisWorkbook.VBProject.VBComponents("mySheet" & myVar).Properties("Name").Value)
A more elaborate example
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim myVar As Long
Dim shName As String
myVar = 1
shName = "Sheet" & myVar
Set ws = Sheets(ThisWorkbook.VBProject.VBComponents(shName).Properties("Name").Value)
Debug.Print ws.CodeName
End Sub
For this to work, enable "Trust access to the VBA project"
Start Microsoft Excel.
Open a workbook.
Click File and then Options.
In the navigation pane, select Trust Center.
Click Trust Center Settings....
In the navigation pane, select Macro Settings.
Ensure that Trust access to the VBA project object model is checked.
Click OK.
If you are not aware what Trust access to the VBA project object model is then you may read about it in Enable or disable macros in Office files
You're missing the point of the code name property.
The idea is that it gives you a compile-time, project-global programmatic identifier you can use whenever you need to refer to a particular specific worksheet in ThisWorkbook (the workbook hosting your VBA code).
Method 1 doesn't work because the expression "mySheet" & myVar evaluates to a String value, and you're trying to Set-assign that string into a Worksheet object reference. VBA can't read your mind here, it sees a string on the right-hand side, a Worksheet variable on the left-hand side, and has no idea what that means.
Method2 is making a locally-scoped copy of an identifier that is already project-global: it's completely redundant and uselessly confusing indirection, don't do that.
Method3 would be better implemented as a function:
Public Function GetSheetByCodeName(ByVal value As String) As Worksheet
Dim sheet As Worksheet
For Each sheet In ThisWorkbook.Worksheets
If sheet.CodeName = value Then
Set GetSheetByCodeName = sheet
Exit Function
End If
Next
Err.Raise 9 '"subscript out of range", specified name was not found.
End Function
Still, taking something that's compile-time validated, and making it runtime-validated, feels backwards. It's a long-winded way of doing things, because you're supposed to be consuming that identifier as an.. identifier:
Sheet1.Range("SomeRange").Value = 42
Worksheets that exist in ThisWorkbook at compile-time, shouldn't need any kind of complicated logic to extract - they're right there.
Related
I am struggling with proper syntax for setting variables as ranges...
Specifically, I'm testing a function I want to use in an app that creates new profiles and store the data, I will store that data on a hidden sheet, so they can be recalled at run time.
I'm currently construction a userform in order to create a new profile, the profile data needs to be stored to the first free column on the hidden sheet.
(where I will have to create a dynamic namedRange, so that i can use that range to save the associated data, and update the listbox in the userform)
Right now, I'm stumped by this:
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'another debug check - only works if sheet activated
Dim trgtCell As Range
Set trgtCell = ws.Cells(1, trgtCol) '<------- problem line
Debug.Print trgtCell '<----- prints "" to the immediate window.
End Sub
The LastColInSheet function is copied form Ron de bruin's page: https://www.rondebruin.nl/win/s9/win005.htm it simply returns a column number, in this case: 4.(One problem with it is if the sheet is empty, it returns an error, wondering if this can be fixed with an if statement in the function.)
I've tried many iterations of the problem line, some work but only if the storage sheet is activated, and give an error if not activate or selected, as the sheet will be hidden, I need this to work without activating the sheet, (although I could switch off screen activation?).
But I understand that it is best practice to avoid extraneous selects and activates, how can I just point directly to what I want and save that range into a variable?
It just doesn't seem like it should be so difficult, I must be missing something obvious.
It also seems like it shouldn't need so many lines of code to do something so simple.
I tried some more iterations of the "problem line" after some more searching...
-The real problem was with the debug.print line
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'debug Only works if already on sheet
Dim trgtCell As Range
'Set trgtCell = ws.Range _
(ws.Cells(1, trgtCol), ws.Cells(1, trgtCol))
' unnecessarily complex, but correct if using .range?
'but works if insisting on range
Set trgtCell = ws.Cells(1, trgtCol) 'back to original
Debug.Print trgtCell.Address '<---problem was here?
End Sub
I have a workbook that when I open, one of my macros does not work. If i go into my editor and try to recompile my code, i get the
Error 430 Class does not support Automation or does not support expected interface
on this line of code:
If Sheets("sheet1").Range("myRange").Cells(1,1) = 1 Then
Sheets("mysheet").Move before:=Sheets("other sheet") **<<<-- ERROR HERE**
If Sheets("mysheet (1)").Range("DA1").Value > 0 Then
Dim n as long
For n = 2 to Sheets("mysheet (1)").Range("DA1").Value + 1
Sheets("mysheet (" & n & ")").Move before:=Sheets("other sheet")
Next n
End If
End If
That one-liner is doing too many things at once, making it very hard to properly diagnose the problem.
Start by declaring Worksheet variables, so that you have an early-bound interface to make your member calls against - note that qualifying with ActiveWorkbook is an assumption I'm making here, that should be equivalent to your implicitly-qualified Sheets call:
Dim mySheet As Worksheet
Set mySheet = ActiveWorkbook.Worksheets("mySheet")
Dim otherSheet As Worksheet
Set otherSheet = ActiveWorkbook.Worksheets("other sheet")
mySheet.Move Before:=otherSheet
If your code gets to the .Move method call, it should "just work". If it blows up before that, verify:
That the sheet name is correctly spelled in the string literal
That the parent workbook of that sheet is the ActiveWorkbook
If all these sheets exist at compile-time in ThisWorkbook (the workbook that contains your VBA project), then you don't need any variables - you already have them.
Look in the Project Explorer (Ctrl+R), all sheet modules have two names. The one in parentheses is the Name property; the one before that is the CodeName, which you'll find in the Properties (F4) toolwindow under (Name). Change that to e.g. mySheet, and now the identifier mySheet can be used anywhere in this VBA project to refer to that specific sheet - regardless of what its "tab name" says.
I only have 2 simple lines and Excel VBA keeps telling me Subscription out of range... why?
Sub try_again()
'Dim res As Variant
Dim Per_Mnd As Variant
Dim Per_Mnd2 As Variant
Per_Mnd = Worksheets("Res_Lege").Range("F41:AD41")
Worksheets("Res_Kontor_over").Range("F6:AD6").Value = Per_Mnd
End Sub
Try something like this:
Set Per_Mnd = Worksheets("Res_Lege").Range("F41:AD41")
Worksheets("Res_Kontor_over").Range("F6:AD6").Value = Per_Mnd.Cells(1, 1)
It Set-s Per_Mnd as a Range, thus it should be working. Then, when you are asking to put values in Range("F6:AD6"), it takes the first cell of Per_Mnd and it assigns it there.
That would be subscript out of range, but never minding the typo, Rubberduck (disclaimer: I own/manage this open-source project) would fire an inspection result here:
Per_Mnd = Worksheets("Res_Lege").Range("F41:AD41")
And another one here:
Worksheets("Res_Kontor_over").Range("F6:AD6").Value = Per_Mnd
The inspection result would say:
Implicit Active Workbook Reference
Implicit references to the active workbook make the code frail and harder to debug. Consider making these references explicit when they're intended, and prefer working off object references.
In other words, if you're 100% sure that there's no typo in the sheet name, then it looks like the ActiveWorkbook isn't the workbook your code is [implicitly] assuming.
i.e. this code is exactly equivalent:
Per_Mnd = ActiveWorkbook.Worksheets("Res_Lege").Range("F41:AD41").Value
If you mean to work with a workbook your code has opened, make sure you keep a reference to that object when you open it:
Dim wb As Excel.Workbook
Set wb = Application.Workbooks.Open(path)
'...
'now qualify the Worksheets collection with this 'wb' object:
Per_Mnd = wb.Worksheets("Res_Lege").Range("F41:AD41")
If you mean to work with the workbook that contains this code, qualify the Worksheets call with ThisWorkbook:
Per_Mnd = ThisWorkbook.Worksheets("Res_Lege").Range("F41:AD41")
If the worksheet exists at compile-time, give it a code name (the (Name) property in the properties toolwindow /F4) instead - say, ResLegeSheet:
Per_Mnd = ResLegeSheet.Range("F41:AD41")
I've looked all over for this and think I'm just hitting a mental brick wall for something simple. But still. I'm writing a quick program to help me with some mileage spreadsheets for different vehicle. Each vehicle has its on worksheet within the spreadsheet, I'm using GemBox in VB.net.
Basically, dending on which button you press it chooses the correct sheet for the corresponding vehicle. I cannot find anything, anywhere that tells me how to choose a different existing sheet as the active worksheet.
This is my test code atm.
Public Sub SetMiles(vehicle As String)
Dim wb = ExcelFile.Load(file)
Dim ws = wb.Worksheets.ActiveWorksheet(vehicle)
loc = "F12"
ws.Cells(loc).Value = "800"
End Sub
In GemBox.Spreadsheet you don't need to set the sheet as active in order to use it.
In other words, let's say you have an Excel file which has "Sheet1" and "Sheet2". To write into those sheets you can use the following:
Dim wb = ExcelFile.Load(File)
Dim ws = wb.Worksheets("Sheet1")
ws.Cells("A1").Value = "Foo"
ws = wb.Worksheets("Sheet2")
ws.Cells("A1").Value = "Bar"
You can also use the following:
Dim wb = ExcelFile.Load(File)
Dim ws = wb.Worksheets(0)
ws.Cells("A1").Value = "Foo"
ws = wb.Worksheets(1)
ws.Cells("A1").Value = "Bar"
So, I believe that what you need is the following:
Public Sub SetMiles(vehicle As String)
Dim wb = ExcelFile.Load(File)
Dim ws = wb.Worksheets(vehicle)
Loc = "F12"
ws.Cells(Loc).Value = "800"
End Sub
Last, in case you do need to set some sheet as active, then you can do that with GemBox.Spreadsheet as following:
wb.Worksheets.ActiveWorksheet = wb.Worksheets(vehicle)
However, again GemBox.Spreadsheet doesn't care if the sheet is active or not, you can access and modified it regardless of that. By setting the sheet as active, that sheet will be the first visible one that you see when you open that file in an Excel application.
wb.Sheets(vehicle).Activate is the simplest way.
Although I recommend that you also validate the vehicle string to ensure that Sheet actually exists. You can then either ignore, display a message or create a new sheet.
I was assuming that you wanted to activate the sheet so that the user can do manual input. If you are doing automated input (no user interaction), then you are better off not activating the sheet. Something along the lines of:
Public Sub SetMiles(vehicle As String, wb as Workbook, loc as string, Mileage as string)
' passing the values is better encapsulation for OOP purposes
' in your example above, loc="F12", Mileage = "800"
' You have passed the Mileage as a string - but you could also use a numeric value.
' Validate sheet name here if important.
' Validate range name here if important.
wb.Sheets(vehicle).Range(loc).Value = Mileage
End Sub
Edit: Appears GemBox uses Worksheets instead of Sheets.
I have a snippet which throws an error, I assume because the variable s is not initialized. How would I declare the variable s?
Dim X As Integer
Dim WS As Worksheet
'Look for existing sheets named "For Export
'If found, delete existing sheet
For Each s In ActiveWorkbook.Sheets
If s.Name = "For Export" Then
Application.DisplayAlerts = False
' s.Delete
End If
Next s
Probably the easiest way is to search for Option Explicit in your code and to delete it. Then you would not be forced to declare every variable. However, if you are not fan of writing ugly & dirty code try with the following:
Dim s as Sheet
In general, it is better to use the Worksheets collection, instead of the Sheets. Thus:
Dim s as Worksheet
For each s in ActiveWorkbook.Worksheets
The Sheets collection contains both Charts and Worksheets, thus it is better to be more specific.