Difference between rng.RefersToRange.Parent.Name and rng.Parent.Name - excel

I need to loop through named ranges of my workbook and check if they are in a specific Worksheet and then do something to it. Found a suggestion to use something like this:
Dim ws As Worksheet, rng As Name
Set ws = somews
For Each rng In ThisWorkbook.Names
If rng.RefersToRange.Parent.Name = somews.Name Then
'do stuff
End If
Next rng
At the beginning that worked fine. Now i have a problem. Somebody else uses this workbook as well and i don't know what they did. The workbook has a new sheet with named ranges, which are identical to some of my ranges in somews, but they refer to some outside source. I guess they were coping stuff between workbooks. The scope of my ranges is for entire workbook and the scope for those new ranges in only for that new sheet. I knew this would happen and that is why i needed this code.
This code breaks now. Because if rng is form the new sheet rng.RefersToRange.Parent.Name still returns the name of my sheet instead of the new sheet. So the If statement gets executed, but with wrong range. If i however use rng.Parent.Name i get the correct name.
What is the difference here? When should i use one and when the other? Is there a better way?

.RefersToRange is not a member of a Range Object. It is a member of a Name Object.
The .RefersToRange object is how you would access the Range Object that is associated with the Name Object. Once you have a Range Object, the .Parent property will return the Worksheet Object that the range belongs to. The .Name property will return the name of the worksheet.
So if rng is a Name Object, rng.RefersToRange.Parent.Name returns the name of the worksheet that contains the range that the name is associated with.
On the other side rng.Parent.Name doesn't go through the range, and is therefore less specific about which name it returns. The .Parent property is applied to the Name Object, which can refer to either a worksheet or the workbook. So depending on how the scope was defined when the Name Object was created, the .Parent.Name will return the name of either a single Worksheet or the Workbook.

Related

In This Excel VBA Could some body tells me Why it refuses to take after parameter in the add method in worksheets object?

The code is:
Sub Copy_Filtered_Table()
Dim aSheet As Worksheet
Dim i As Long
i = ActiveSheet.Index
ActiveSheet.AutoFilter.Range.Copy
-> Set aSheet = ActiveWorkbook.Worksheets.Add(After:=i)
aSheet.Range("A1").PasteSpecial
End Sub
The workbook format is .xlsm Excel 2016
it has sheets after and before the active sheet
Also I Have tried doing it with out the aSheet variable like this
ActiveWorkbook.Worksheets.Add After:=i
It did not work too. both cases gives error 1004 Method 'Add' of object 'Sheet' faild.
If I ommited the After parameter it works but putting the result new sheet before the active sheet which exactly I am avoiding.
In order to insert relative to an existing sheet, you need to provide the actual sheet rather than its index, using one of:
Set aSheet = ActiveWorkbook.Sheets.Add(After:=ActiveWorkbook.Worksheets(i))
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Worksheets(i)
Which one you choose depends on whether you immediately want to do something with the sheet without searching for it again but I suspect you already know that, given your question.
As an aside (though I haven't tried), I suspect getting the index of the current sheets then turning that back into the current sheet object is a bit unnecessary. You may want to just try:
Set aSheet = ActiveWorkbook.Sheets.Add(After:=ActiveWorkbook.ActiveSheet)

Set up variable to refer to sheet

Can I set up a variable x = "Sheet1"
In order to do:
x.Range("A3")
instead of
Sheet1.Range("A3")?
What type of variable should it be? I tried string and it didn't work.
Thanks
Update:
I would like a method that won't be affected by changing the worksheet name. i.e.
Sheet1.Range("A3") will always refer to the same worksheet even if I change the worksheet name to "peanuts", at least that's what I thought.
You want to declare it a Worksheet Object:
Dim x as WorkSheet
Since it is an object we must Set the sheet:
Set x = WorkSheets("Sheet1")
or if you want to use the code name:
Set x = Sheet1
Then yes you can use it:
x.Range("A3")...
I think you're mixing two different declarations
Declaring a sheet (must set the sheet)
dim ws as worksheet
set ws = sheets("sheet1")
ws.cells(1,1).value = ""
Declaring a string as the name of a sheet (can utilize the NAME of the sheet as a string)
dim ws_name as string
ws_name = "sheet1"
sheets(ws_name).cells(1,1).value = ""
Additionally you could use the sheet index, which does not utilize the name of the sheet (if you change it later); this is a little different than the previous two, but this example (using a loop) helps more clearly explain how the index can be utilized
dim i as long
for i = 1 to sheets.count step 1
sheets(i).cells(1,1).value = ""
next i
and a simple use of a sheet index
sheets(1).cells(1,1).value = ""
It should be worksheet. The syntax would be:
Dim x as Worksheet
Set x = Sheet1
after that you can use x.range("A3") to refer to Cell A3 in sheet1
You want a Worksheet variable. The "name" you're referring to is the Name property, and that isn't in code; it's the "tab name" of the sheet, that the user can change on a whim and that, for that reason, you don't want to have to hard-code anywhere if you can avoid it.
But it looks like you're referring to a sheet that exists in ThisWorkbook at compile-time; each sheet has a (Name) property that you can edit in the Properties toolwindow (F4). By default that name is Sheet1, and VBA takes that name and makes it a project-scope identifier you can use anywhere you need to refer to that particular sheet.
So if you change a sheet's (Name) to PeanutsSheet, then you can use PeanutsSheet in your code:
PeanutsSheet.Range("A1").Value = 42 '<~ that's the "CodeName", and users can't easily break it
And that is preferable to referring to that same sheet by its "tab name":
ThisWorkbook.Worksheets("Peanuts").Range("A1").Value = 42 '<~ breaks if tab is ever renamed
Declaring a variable for a sheet that exists in ThisWorkbook at compile-time, is completely redundant:
Dim ws As Worksheet
Set ws = Sheet1 '<~ variable 'ws' is 100% redundant
Using such variables makes the code confusing and harder to follow than it needs to be, because now you have 2 (or more) identifiers referring to the same thing.
But this is worse:
Set ws = ThisWorkbook.Worksheets("Sheet1") '<~ now it's redundant *and* super frail

Why am I getting a 'type mismatch' error?

I am trying to copy a range from one workbook to a table in another workbook to capture production data. I am able to copy the correct range and open the workbook with the table successfully. However, when it tries to paste the information into the next available row in the table I get the error 13. I am rather new to vba and can't seem to find a solution, any help would be greatly appreciated.
Dim wbTime As Workbook
Set wbTime = ThisWorkbook
Dim wbData As Workbook
Dim N As Long
N = Cells(Rows.Count, "A").End(xlUp).Row + 1
Dim UsdRws As Long
UsdRws = Cells.Find("*", After:=Range("A32"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
wbTime.ActiveSheet.Range("A6:O" & UsdRws).Copy
Set wbData = Workbooks.Open("S:\Lean Carrollton Initiative\Allen\Buffering Interrupters\1st Shift\B10\Data.xlsx")
wbData.ActiveSheet.Paste Destination:=Worksheets(Sheet1).Range(N & "A")
wbData.Close SaveChanges:=True
End Sub
Destination1:=Worksheets(Sheet1).Range(N & "A")
#QHarr correctly identified the problem with Range(N & "A"), and proposed a fix for the type mismatch error you're getting at Worksheets(Sheet1).
I just wanted to explain what's going on in more details than can fit a little comment box.
Sheet1 is an implicitly declared object variable of type Worksheet, whose compile-time identifier is determined by the (Name) property of the worksheet:
If you change this property value to, say, SummarySheet, then Sheet1 is no longer a valid identifier, and SummarySheet becomes one - and this is part of why specifying Option Explicit at the top of every module is important, because without it VBA will happily compile and run, except now instead of Sheet1 being illegal, it's now an undefined Variant/Empty value that can merrily passed around accidentally, which can make things very hard to debug (the same holds true for any undeclared variable, not just worksheet code names).
So Sheet1 is a Worksheet object reference. If Worksheet had a default member that returned the value of its Name property, your code would work.
But Worksheet doesn't have a default member, so Worksheets(Sheet1) is passing the Worksheet object reference as an argument to the Worksheets.Item indexer (implicitly - because the Worksheets class does have a default member, its Item property), which is expecting a Variant holding either the name of a worksheet (Variant/String), or a numeric index (Variant/Integer or Variant/Long).
Passing a Worksheet object to Worksheets.Item is what's throwing the type mismatch error.
Therefore, assuming that Sheet1 worksheet is the intended destination, this would fix it (making abstraction of the Range parameter bug identified earlier by QHarr):
wbData.ActiveSheet.Paste Destination:=Sheet1.Range(...)
There is never a need to retrieve a worksheet that exists at compile-time in ThisWorkbook, from the Worksheets collection.
Note that in the original code:
Destination1:=Worksheets(Sheet1).Range(N & "A")
Since Worksheets isn't qualified, the Workbook it belongs to is ambiguous: if that code is written in ThisWorkbook, then Worksheets is a member call against Me, i.e. ThisWorkbook.Worksheets. Otherwise, it "conveniently" implicitly refers to whatever workbook happens to be currently active, and that's often a risky assumption for code to be making.
Your range needs to be for correct syntax
Range("A" & N)
You could also use
Cells(N, "A")
And sheet1 should be "Sheet1" i.e. Worksheets("Sheet1"). Sheet1 by itself, unquoted, will be seen as a variable. Unless you have a string variable by that name? Use Option Explicit at the top of your module to check for variable declarations.
Additional comment from #MathieuGuidon:
I'll add that Sheet1 is the default code name of the "Sheet1" worksheet in a new workbook; it's a project-scoped identifier VBA creates for free, giving you a compile-time reference to that specific Worksheet object - thus, retrieving that object from the Worksheets collection is redundant altogether: Sheet1.Range(...) would be preferable -- and ideally, that sheet's (Name) property should be modified to a meaningful identifier, e.g. SummarySheet, which makes SummarySheet.Range(...) legal, without needing to declare any SummarySheet variable

Excel VBA: Formatting Based on The Format of Another Cell, Getting Method Error

I'm working on a spreadsheet that breaks out the tasks in a given department. Each task is then assigned to a person in that department using a data validation drop down that dynamically fetches values from the tables that list each employee/role in the department. The tables are on a different sheet than the task breakdown.
Each individual needs their own background color that automatically fills in when their name is selected from the drop down on the tasks sheet. What I'm hoping to do is write a macro that looks for that person's name in the table (note that I'm using named ranges) and then matches the formatting of the selected cell to their row in the table. I'm a super beginner with VBA and have hit the end of my abilities. Several answers that come close to what I want to do, but in terms of adapting it for my specific use case, I'm stuck.
I grabbed code from this thread, which is essentially the result I want to achieve, except that their key is on the same sheet and mine can't be: https://superuser.com/questions/472918/excel-conditionally-format-a-cell-using-the-format-of-another-content-matching
So far, I've compiled this:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If VarType(Target) > vbArray Then Exit Sub
' if multiple cells are changed at once, then exit
Dim kr1 As Range
Dim kr2 As Range
Dim KeyRange As Range
Dim TargetRange As Range
Dim lCell As Object
Dim kCell As Object
Set kr1 = Application.Range("ESFormattingRange")
Set kr2 = Application.Range("CSFormattingRange")
Set KeyRange = Application.Union(Range("kr1"), Range("kr2"))
' formatting key is here
Set TargetRange = ThisWorkbook.Worksheet("Sheet3").Range("A:X")
' changing cells in this area
For Each kCell In KeyRange.Cells
If kCell.Value <> "" Then
For Each lCell In TargetRange.Cells
If lCell.Value = kCell.Value Then
' only change cells that match the edited cell
lCell.Font.Color = kCell.Font.Color
lCell.Interior.Color = kCell.Interior.Color
' copy whatever you feel needs to be copied
End If
Next
End If
Next
End Sub
When I run this, I get the following method error, but no lines are highlighted when I try to debug:
Compile error:
Method or data member not found
I'm thinking maybe there's something wrong with the way I've constructed my range variables, but I've googled everything I can think of and I'm at a loss.
Any help is greatly appreciated!
The VBE should be highlighting the word Worksheet in ThisWorkbook.Worksheet("Sheet3").Range("A:X").
"Method or data member not found" means Worksheet isn't a member of ThisWorkbook.
You can fix this by pressing CTRL+SPACE to autocomplete the member name to Worksheets.
Alternatively, you can re-type the . dereferencing operator after ThisWorkbook, and press TAB when the names list dropdown is shown, highlighting the Worksheets member - that will also autocomplete the member name to Worksheets.
Rule of thumb, if you type a . dereferencing operator and then type something that isn't in the names list, you can expect this compile error.
When you make this typo against a Variant or Object variable, the code will happily compile, but error 438 will be thrown at run-time - like this:
ThisWorkbook.Worksheets("Sheet3").Ragne("A:X") ' <~ typo compiles. Option Explicit can't save you.
The reason is that Worksheets returns an Object reference, so any member calls chained to it are resolved at run-time. A safer way to code is to declare a local Worksheet variable to hold this object, and then work with that object instead:
Dim sourceSheet As Worksheet
Set sourceSheet = ThisWorkbook.Worksheets("Sheet3")
sourceSheet.Rnage("A:X") ' <~ typo throws at compile-time now!
If the worksheet object exists in ThisWorkbook at compile-time however, then it's probably a better idea to work with its code name instead. Locate Sheet3 in the Project Explorer (CTRL+R), then find its (Name) property - change it to e.g. SourceSheet. And then you can do this:
SourceSheet.Range("A:X") ' <~ a typo wouldn't compile here.
...without needing to explicitly declare a SourceSheet.

Set variable to a sheet's Codename, Using it in the code isn't working

This works, referring to the codename directly (Sheet3)...
Sheet3.Shapes(PopUp).Visible = True
...but using a variable set to the same Codename value doesn't...
WS = ActiveSheet.CodeName
WS.Shapes(PopUp).Visible = True
Why? I ask because I need to extend the larger macro's functionality to the whole workbook, so I'm hoping the variable will let this apply to whatever worksheet the user happens to be on.
Thanks for any guidance!
If you Dim the variable ws as a string, it will not work as a sheet object.
If you have changed the (Name) property of the sheet to "MyCodename", for example, you can just use that codename as the sheet object
MyCodename.Shapes(PopUp).Visible = True
.. assuming that the variable popup is of the proper type and initialised with the proper value.
If your goal is to pass a worksheet and work off that, then pass a worksheet:
Sub DoSomething(ByVal ws As Worksheet, ByVal popup As String)
ws.Shapes(popup).Visible = True
End sub
And then you can use the code name to get the specific worksheet object you want to pass that procedure:
DoSomething Sheet1, "shape1"
DoSomething Sheet2, "shape12"
DoSomething Sheet3, "SomeButton"
I just want a sheet's macro to pass the variables for its Codename
When used like above, the code name is a Worksheet variable. VBA uses the string value of the Worksheet.CodeName property to generate a project-scoped object variable by that name - and indeed, using these "free" variables whenever you need to refer to any sheet that exists at compile-time, is a thousand times better than dereferencing that exact same object reference through the Sheets or Worksheets collection using the sheet's Name, which your user can change on a whim at any given time (unless workbook structure is protected), and break your code!
On the other hand...
[...] apply to whatever worksheet the user happens to be on
This is the textbook definition of ActiveSheet - if you need to work on whatever worksheet the user happens to be on, then you use ActiveSheet, which refers to exactly that.

Resources