Excel VBA Subscription out of range - excel

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

Related

Excel VBA - Indirectly reference a sheet by it's CodeName

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.

How to use Count If function inside of a sub?

I am trying to count the number of times the string shows up in the range and return to worksheet 1. I am assuming this can be done in a Function () but I'm don't know how to write out the syntax.
Sub DistributedApps()
Dim LastRow As Long
Dim Dist As Long
LastRow = Worksheets(3).Cells(Rows.Count, 25).End(xlUp).Row
Dist = Application.Worksheets(3).WorksheetFunction.CountIf(Range("Y1:Y" & LastRow), "Distributed Apps")
Worksheets(1).Range("N66:P69").Value = Dist
End Sub
Object doesn't support this property or method
Dist = Application.Worksheets(3).WorksheetFunction...
The Worksheets property is returning an Object that could be either a Sheets collection (if given an array of sheet names) or a Worksheet object (if given one sheet name, or a sheet index), so you're getting a Worksheet object, but VBA only knows this at run-time.
Move that knowledge to compile-time by introducing a local Worksheet variable; note that Application.Worksheets will give you the sheets collection of whatever workbook is currently active, so it might be a good idea to make that more explicit by qualifying the member call with an actual Workbook object:
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Worksheets(3)
Now that VBA knows what interface this object has, the editor can help you: when you type the dot in sheet., you'll get a list of all members of the Worksheet interface - and see that none of them is WorksheetFunction: that is why error 438 is raised at run-time, the object doesn't support this property.
The Application object does have a WorksheetFunction member though, so this will work.
lastRow = sheet.Cells(sheet.Rows.Count, 25).End(xlUp).Row
dist = Application.WorksheetFunction.CountIf(sheet.Range("Y1:Y" & lastRow), "Distributed Apps")
Note that the Range member call inside the CountIf argument list is also explicitly qualified with the sheet object. Without this qualifier, Range would be referring to whatever the ActiveSheet is1, and since you don't want to need to Activate any sheets for this to work, using an explicit worksheet object as a qualifier ensures you're computing the count off the correct sheet.
1Unless that code is written in the code-behind for a Worksheet module - in which case the implicit qualifier is Me and the unqualified Range call is referring to that worksheet.
I think this will work.
Sub DistributedApps()
Dim LastRow As Long
Dim Dist As Long
LastRow = Worksheets(3).Cells(Rows.Count, 25).End(xlUp).Row
Dist = Application.WorksheetFunction.CountIf(Worksheets(3).Range("Y1:Y" & LastRow), "Distributed Apps")
Worksheets(1).Range("N66:P69").Value = Dist
End Sub

VB.NET Gembox - Switch Active Spreadsheet

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.

Declaring variable workbook / Worksheet vba

I know this might come off as a trivial question, but I can't seem to declare a workbook or a worksheet as a variable in VBA. I have the following code, but I can't figure out what I am doing wrong, it should be straight forward. Normally I don't have any problems declaring variables such as Dim i As Integer etc.
sub kl()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ActiveWorkbook
Set ws = Sheet("name")
wb.ws.Select
End Sub
When I run the above code, I receive a type missmatch error.
Use Sheets rather than Sheet and activate them sequentially:
Sub kl()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ActiveWorkbook
Set ws = Sheets("Sheet1")
wb.Activate
ws.Select
End Sub
If the worksheet you want to retrieve exists at compile-time in ThisWorkbook (i.e. the workbook that contains the VBA code you're looking at), then the simplest and most consistently reliable way to refer to that Worksheet object is to use its code name:
Debug.Print Sheet1.Range("A1").Value
You can set the code name to anything you need (as long as it's a valid VBA identifier), independently of its "tab name" (which the user can modify at any time), by changing the (Name) property in the Properties toolwindow (F4):
The Name property refers to the "tab name" that the user can change on a whim; the (Name) property refers to the code name of the worksheet, and the user can't change it without accessing the Visual Basic Editor.
VBA uses this code name to automatically declare a global-scope Worksheet object variable that your code gets to use anywhere to refer to that sheet, for free.
In other words, if the sheet exists in ThisWorkbook at compile-time, there's never a need to declare a variable for it - the variable is already there!
If the worksheet is created at run-time (inside ThisWorkbook or not), then you need to declare & assign a Worksheet variable for it.
Use the Worksheets property of a Workbook object to retrieve it:
Dim wb As Workbook
Set wb = Application.Workbooks.Open(path)
Dim ws As Worksheet
Set ws = wb.Worksheets(nameOrIndex)
Important notes...
Both the name and index of a worksheet can easily be modified by the user (accidentally or not), unless workbook structure is protected. If workbook isn't protected, you simply cannot assume that the name or index alone will give you the specific worksheet you're after - it's always a good idea to validate the format of the sheet (e.g. verify that cell A1 contains some specific text, or that there's a table with a specific name, that contains some specific column headings).
Using the Sheets collection contains Worksheet objects, but can also contain Chart instances, and a half-dozen more legacy sheet types that are not worksheets. Assigning a Worksheet reference from whatever Sheets(nameOrIndex) returns, risks throwing a type mismatch run-time error for that reason.
Not qualifying the Worksheets collection is an implicit ActiveWorkbook reference - meaning the Worksheets collection is pulling from whatever workbook is active at the moment the instruction is executing. Such implicit references make the code frail and bug-prone, especially if the user can navigate and interact with the Excel UI while code is running.
Unless you mean to activate a specific sheet, you never need to call ws.Activate in order to do 99% of what you want to do with a worksheet. Just use your ws variable instead.
Third solution:
I would set ws to a sheet of workbook wb as the use of Sheet("name") always refers to the active workbook, which might change as your code develops.
sub kl()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ActiveWorkbook
'be aware as this might produce an error, if Shet "name" does not exist
Set ws = wb.Sheets("name")
' if wb is other than the active workbook
wb.activate
ws.Select
End Sub
Just coming across the same problem.
What you need to do is to declare ws as Object
Also it should be:
Set ws = wb.Sheets("Sheet1")
And should not be:
Set ws = Sheet("Sheet1")
The code below are working to me.
sub kl()
Dim wb As Workbook
Dim ws As Object
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
MsgBox ws.Name
End Sub
Try changing the name of the variable as sometimes it clashes with other modules/subs
Dim Workbk As Workbook
Dim Worksh As Worksheet
But also, try
Set ws = wb.Sheets("name")
I can't remember if it works with Sheet
to your surprise, you do need to declare variable for workbook and worksheet in excel 2007 or later version. Just add single line expression.
Sub kl()
Set ws = ThisWorkbook.Sheets("name")
ws.select
End Sub
Remove everything else and enjoy.
But why to select a sheet? selection of sheets is now old fashioned for calculation and manipulation.
Just add formula like this
Sub kl()
Set ws = ThisWorkbook.Sheets("name")
ws.range("cell reference").formula = "your formula"
'OR in case you are using copy paste formula, just use 'insert or formula method instead of ActiveSheet.paste e.g.:
ws.range("your cell").formula
'or
ws.colums("your col: one col e.g. "A:A").insert
'if you need to clear the previous value, just add the following above insert line
ws.columns("your column").delete
End Sub
I had the same issue. I used Worksheet instead of Worksheets and it was resolved. Not sure what the difference is between them.
Dim ws as Object
Set ws = Worksheets("name")
when declaring the worksheet as worksheet instead of an ojbect I had issues working with OptionButtons (Active X) in this worksheet (I guess the same will be with any Active-X element. When declared as object everything works fine.
Lots of answers above! here is my take:
Sub kl()
Dim wb As Workbook
Dim ws As Worksheet
Set ws = Sheets("name")
Set wb = ThisWorkbook
With ws
.Select
End With
End Sub
your first (perhaps accidental) mistake as we have all mentioned is "Sheet"... should be "Sheets"
The with block is useful because if you set wb to anything other than the current workbook, it will ececute properly

Reference excel worksheet by name?

I have the name of a worksheet stored as a string in a variable. How do I perform some operation on this worksheet?
I though I would do something like this:
nameOfWorkSheet = "test"
ActiveWorkbook.Worksheets(nameOfWorkSheet).someOperation()
How do I get this done?
There are several options, including using the method you demonstrate, With, and using a variable.
My preference is option 4 below: Dim a variable of type Worksheet and store the worksheet and call the methods on the variable or pass it to functions, however any of the options work.
Sub Test()
Dim SheetName As String
Dim SearchText As String
Dim FoundRange As Range
SheetName = "test"
SearchText = "abc"
' 0. If you know the sheet is the ActiveSheet, you can use if directly.
Set FoundRange = ActiveSheet.UsedRange.Find(What:=SearchText)
' Since I usually have a lot of Subs/Functions, I don't use this method often.
' If I do, I store it in a variable to make it easy to change in the future or
' to pass to functions, e.g.: Set MySheet = ActiveSheet
' If your methods need to work with multiple worksheets at the same time, using
' ActiveSheet probably isn't a good idea and you should just specify the sheets.
' 1. Using Sheets or Worksheets (Least efficient if repeating or calling multiple times)
Set FoundRange = Sheets(SheetName).UsedRange.Find(What:=SearchText)
Set FoundRange = Worksheets(SheetName).UsedRange.Find(What:=SearchText)
' 2. Using Named Sheet, i.e. Sheet1 (if Worksheet is named "Sheet1"). The
' sheet names use the title/name of the worksheet, however the name must
' be a valid VBA identifier (no spaces or special characters. Use the Object
' Browser to find the sheet names if it isn't obvious. (More efficient than #1)
Set FoundRange = Sheet1.UsedRange.Find(What:=SearchText)
' 3. Using "With" (more efficient than #1)
With Sheets(SheetName)
Set FoundRange = .UsedRange.Find(What:=SearchText)
End With
' or possibly...
With Sheets(SheetName).UsedRange
Set FoundRange = .Find(What:=SearchText)
End With
' 4. Using Worksheet variable (more efficient than 1)
Dim MySheet As Worksheet
Set MySheet = Worksheets(SheetName)
Set FoundRange = MySheet.UsedRange.Find(What:=SearchText)
' Calling a Function/Sub
Test2 Sheets(SheetName) ' Option 1
Test2 Sheet1 ' Option 2
Test2 MySheet ' Option 4
End Sub
Sub Test2(TestSheet As Worksheet)
Dim RowIndex As Long
For RowIndex = 1 To TestSheet.UsedRange.Rows.Count
If TestSheet.Cells(RowIndex, 1).Value = "SomeValue" Then
' Do something
End If
Next RowIndex
End Sub
The best way is to create a variable of type Worksheet, assign the worksheet and use it every time the VBA would implicitly use the ActiveSheet.
This will help you avoid bugs that will eventually show up when your program grows in size.
For example something like Range("A1:C10").Sort Key1:=Range("A2") is good when the macro works only on one sheet. But you will eventually expand your macro to work with several sheets, find out that this doesn't work, adjust it to ShTest1.Range("A1:C10").Sort Key1:=Range("A2")... and find out that it still doesn't work.
Here is the correct way:
Dim ShTest1 As Worksheet
Set ShTest1 = Sheets("Test1")
ShTest1.Range("A1:C10").Sort Key1:=ShTest1.Range("A2")
To expand on Ryan's answer, when you are declaring variables (using Dim) you can cheat a little bit by using the predictive text feature in the VBE, as in the image below.
If it shows up in that list, then you can assign an object of that type to a variable. So not just a Worksheet, as Ryan pointed out, but also a Chart, Range, Workbook, Series and on and on.
You set that variable equal to the object you want to manipulate and then you can call methods, pass it to functions, etc, just like Ryan pointed out for this example. You might run into a couple snags when it comes to collections vs objects (Chart or Charts, Range or Ranges, etc) but with trial and error you'll get it for sure.

Resources