Unable to read object properties of Set Range variables - excel

I am developing a workbook with vba; in it are worksheets that can contain a varying amount of named "CheckCells" which act as "boolean" dropdowns ("Yes"/"No") to manipulate other cells within the same worksheet.
I'm trying to develop a Public Function macro which can be called within a formula of a cell. Its purpose is to "collapse" all of these CheckCells into a single Long-type output -- the output will equal the column index from which I read off of special "archive" worksheets within the workbook; the "archive" sheets will act as permanent "memory" for certain values, as they are critical to the workbook function but would be lost upon workbook open/close.
The following is a shortened version of the code I need help with. As indicated by commented Debug.Print lines, it appears that any way I try to Dim a Range-type variable, then Set it to equal a range of cells, most attempts at reading using that variable's object properties throw errors results such as "Cannot get property...", "Object-defined error...", or "Application-defined error...". "VersionCheckCell" is indeed a named cell of the ActiveSheet here.
Public Function CollapseRun() As Long
Dim ActSht As Worksheet
Set ActSht = ThisWorkbook.ActiveSheet
'Variables to get the range of cells to loop through when finding VersionCount.
Dim intVisRows As Long: intVisRows = 1
Dim intVisCols As Long: intVisCols = 1
'[Code to evaluate inVisCols and intVisRows omitted to shorten post...]
Dim EndCell As Range
Set EndCell = ActSht.Cells(intVisRows, intVisCols)
'Debug.Print [VersionCheckCell].Name.Name 'This works. If explicitly giving valid ranges, _
I can read its properties like normal.
'Debug.Print EndCell.Name.Name 'This fails. When I define a _
Range variable, then Set it as some cells, I can't read its object properties.
Dim VisibleRange As Range
Set VisibleRange = ActSht.Range(ActSht.Cells(1, 1), EndCell) 'Cell Range to
loop through.
Dim intCzCells As Integer: intCzCells = 0
Dim Cell As Range
Dim arrCz() As Variant
'This is the Application-defined error-throwing loop.
For Each Cell In VisibleRange
If InStr(Cell.Name.Name, "CheckCell") <> 0 Then 'Checks cell's name for "CheckCell".
intCzCells = intCzCells + 1
arrCz(intCzCells) = Cell.Address 'Storing addresses to later check values.
End If
Next Cell
'[More code after this loop to determine CollapseRun value...]
I did research before asking and found many other questions with labels such as "Application-defined error when doing ...", etc. The problems/solutions of many of these stemmed from the QA improperly Setting the variable which was throwing errors when being read. I've stared and tinkered with this code for several hours and cannot figure out how I might be improperly setting the VisibleRange or the EndCell variables, so I'm inclined to believe something else is wrong, but I'm hoping someone here can nudge me in the right direction.
I'm on Windows 10 using Excel 2016.
(Edit: Insert missing portion of code where intCzCells was initialized as 0.
(Edit 2: Fixed unnecessary range evaluation as pointed out in a comment.)

The main issue, as pointed out by John Coleman in comments, was that .Name.Name would throw errors if the range wasn't a named cell.
One way to resolve this issue is to use workaround error handling within the loop (I also fixed the code to actually add the addresses to the array properly).
For Each Cell In VisibleRange
On Error GoTo NoName
If InStr(Cell.Name.Name, "CheckCell") <> 0 Then
intCzCells = intCzCells + 1
ReDim Preserve arrCz(intCzCells - 1) 'Array indexed from 0.
arrCz(intCzCells - 1) = Cell.Address
End If
NoName:
Resume NextIteration
NextIteration:
Next Cell

Related

Using a variable as a range name

I have a worksheet where the user can input a list of cell references as hardcoded values in cells. The VBA script is then supposed to take that input and work with the values in those exact cells in two separate worksheets. So for instance the input looks like the following:
Input
G29
H38
M92
The script is then supposed to loop through the range on input (in this case G29, H38 and M92) and go into a separate workbook (source workbook) where it then copies the values in those exact cells and then goes into another separate workbook (target workbook) and paste the values into the same cell references.
In the following code I have defined the variables as follows:
wsKpInput_source = The relevant worksheet in the source workbook
wsSCEInput_target = The relevant worksheet in the target workbook
Dim rng As Range: Set rng = Application.Range("Dashboard!E9:E11") 'This is the G29,H38,M92 input from the user
Dim cell_source As Range 'To take the references input by the user (G29,H38,M92) - unsure how to define this?
Dim cell_source_input As Variant 'To use the cell references in cell_source and be put equal to the content in that source workbook cell reference
For i = 1 To rng.Rows.Count
cell_source = rng.Cells(i, 1)
cell_source_input = wsKpInput_source.Range(cell_source)
wsKpInput_target.Range(cell_source) = cell_source_input
Next
Unfortunately this doesn't work as intended and I believe it is probably due to several issues? I would much appreciate any help with this.
This can be done properly on one single line as so:
Sub copy()
Dim rng As Range: Set rng = Application.Range("Dashboard!E9:E11") 'This is the G29,H38,M92 input from the user
Dim i As Integer
For i = 1 To rng.Rows.Count
wsKpInput_target.Range(rng.Cells(i, 1).Value).Value = wsKpInput_source.Range(rng.Cells(i, 1).Value).Value
Next
End Sub
The problem is that you tried .rng.cells(I,1) which will return a cell reference of that cell, where you needed the value within the cell as a cell reference (done with .value). Also copying from one cell to another can always be done with one line on two sides of an = statement.
Also this method is not the most efficient as it can probably done with adding all values to an array at one time, but that would probably take more time coding than you would save.

How to reference named range in VBA from an inactive sheet?

I see this question asked and answered but I'm missing something! In order to use Named Ranges in my VBA I must activate the sheet before using the Named Range, otherwise it accesses the "same" range in the active sheet. I have:
made sure that the named range scope is set to Workbook.
fully qualified every range and cell method or property in my code
Here is the code. It works fine if I put back in the worksheet.activate call but otherwise it references a range in the wrong sheet (the active one).
Sub UserForm_Initialize()
' Application.ScreenUpdating = False
Dim r1 As Long, c1 As Long, r2 As Long, c2 As Long, rng1 As Range, rng2 As Range, s As Worksheet, initSheet As Worksheet
Set s = Sheets("NamePlates")
Set rng1 = s.Range("MasterStoreNamePlates")
Set initSheet = Application.ActiveSheet
r1 = rng1.Row
c1 = rng1.Column
r2 = r1 + storeCount - 1
c2 = c1
's.Activate
With s
listBoxStores.RowSource = .Range(.Cells(r1, c1), .Cells(r2, c2)).Address
End With
'initSheet.Activate
' Application.ScreenUpdating = True
End Sub
It's not too demanding to switch to a different sheet for a quick range lookup, but come on... what's the point of naming the ranges and making the name global and all the posts about "make sure to set the name to global" if I have to still go and switch the active sheet?
Thanks in advance!
An update:
Sub UserForm_Initialize()
Application.ScreenUpdating = False
Dim rng1 As Range, rng2 As Range, sInd As Long, initSheet As Worksheet, s As Worksheet
sInd = Sheets("NamePlates").index
Set s = Sheets(sInd)
Set rng1 = s.Range("NamePlates!MasterStoreNamePlates")
Set initSheet = Application.ActiveSheet
listBoxStores.RowSource = rng1.Range(s.Cells(1, 1), s.Cells(storeCount - 1, 1)).Address
Application.ScreenUpdating = True
End Sub
Doesn't provide any different result. It still accesses the active sheet. If I change the active sheet before doing this, it retrieves the right data, otherwise it pulls the range from the active sheet.
I noticed that s.Range("NamePlates!MasterStoreNamePlates") does get me the correct range... but then when I use that range and do something relative to it, I lose the sheet reference. So I think maybe my solution will involve dynamically updating the named range and then this method should work. Currently you can see that I'm using that constant "storeCount" which is not really a very good way to do things. It is just kind of my hack for getting things up and running. All the other suggested methods fail in my case for the same reason; even if they make a good reference to the correct range, I loose that sheet reference when I start doing anything that defines the range I want, which is 12 rows longer than the named range (thus the "storeCount" constant).
If I can't figure out how to do a dynamically changing named range I'll post a new question but I've seen some things about that so I should be able to get that working and I'll post my solution here... or if someone beats me to it I'll accept that answer even though it's a little different from the OP.
ThisWorkbook.Names("NamedRange").RefersToRange
This will return the range object of the named range.
i am adding another answer as the original question was modified significantly. it looks to me that you are over-complicating a matter which is rather simple if you understand it.
Before i explain further, i think your approach with setting the ".RowSource" with the ".address" property of a range is incorrect because the address property does not contain a reference of the parent sheet so it will always refer to the active sheet even if you have gotten that address from a global wb range. see below:
i hope the below would be helpful to you to achieve what you want. No ranges! No activations! keep it simple. i have been working with vba 12 years and i can count in one hand the number of times i have used .activate & .select
I have initialized my form with 3 list boxes (i) static hardcoded range (ii) named range and (iii) dynamic named range.
Private Sub UserForm_Initialize()
Dim addr1 As String
Dim addr2 As String
Dim addr3 As String
addr1 = "LB!$A$1:$A$4"
addr2 = "List.Items" ' named range defined as "=LB!$A$1:$A$3"
addr3 = "List.Items.Dyn" ' dynamic named range defined as "=OFFSET(LB!$A$1,0,0,COUNTA(LB!$A:$A),1)"
Me.testListBox1.RowSource = addr1
Me.testListBox2.RowSource = addr2
Me.testListBox3.RowSource = addr3
Debug.Print Me.testListBox1.RowSource
Debug.Print Me.testListBox2.RowSource
Debug.Print Me.testListBox3.RowSource
End Sub
here is the Debug.Print result in the immediate window
In my user form i have three listboxes populated from the same cells but referenced differently in the .RowSource property. It also works regardless which is the active sheet.
here i am initializing the same form from another worksheet
why do you want to activate the sheets when you could possibly do:
listBoxStores.RowSource = s.Range(s.Cells(r1, c1), s.Cells(r2, c2)).Address
instead of:
With s
listBoxStores.RowSource = .Range(.Cells(r1, c1), .Cells(r2, c2)).Address
End With

Union function in VBA

I am trying to filter my data, and add the desired data to the Range. I have set my desired range to null and initiated a search in a particular column for a particular keyword, which if found adds the entire row in my Range.
Column 'E' is been searched for a Value "Desired Value", if found the entire row is added to the PTRange, which is initially set to Nothing(is Empty).
Dim rng As Range
Dim PTRange As Range
Dim count As Long
count = 1
Set PTRange = Nothing
Set rng = Range("E2:E20")
For Each cell In rng
If cell.Value = ("Desired Value") Then
PTRange = Application.Union(Range(PTRange), Range(Rows(count)))
End If
count = count + 1
Next cell
But the compiler shows that there is something wrong in the code where Union method is used.
UPDATE :
Initialization of PTRange and the following change
PTRange=Union(PTRange,Rows(count))
worked for me. Thanks
It is hard to get you to your desired goal as I'm a little unclear what that is but the below can serve as both assistance on your code with a possible solution following.
Firstly I would recommend you always have Option Explicit turned on especially if calling out to Stack Overflow for assistance.
Option Explicit forces you to declare all variables, in this instance it would be of help as you cell as a variable which can appear ambiguous. When it is not declared, it starts its life as a variant and is defined to a specific format on first use, in this case the compiler declared cell as a Range.
It also helps encourage better coding and to understand what is happening in the code more. To turn it on, type Option Explicit at the very top of the code pane (before any procedures or variable declarations), also while in the VBE (Visual Basic Editor) click 'Tools' > 'Options' and ensure 'Require Variable Declaration' is ticked.
Below are some comments within the code you have, these should help you understand the issues you are facing.
Dim rng As Range
Dim PTRange As Range
Dim count As Long
count = 1
'If the range is nothing then it can not be used, it must be started as
'something
Set PTRange = Nothing
Set rng = Range("E2:E20")
'This is where declaring 'cell' would help understanding, it will become a
'Range at the VBE discretion\decision based on what you are trying to do
'with it. Even when looking at a single cell, you do so through a Range,
'it is a reference to one or more cells in a worksheet
For Each cell In rng '
If cell.Value = ("Desired Value") Then
'This will not work for a number of reasons
'O- As BrakNicku stated, there needs to be 'Set' at the front
'O- This is not remembering the values into the 'PTRange' but
' adding a reference to where those values are. I.e. You are
' not taking note of everybody in the house, but just remembering
' their house address
'O- You do not need to put 'Range()' around 'PTRange' as it is
' already a range
'O- 'Rows(count)' is not valid syntax and does not pass a range into
' the union
'O- 'Union' will put together 2 or more references to ranges, it
' will not put their values in any specific place on a worksheet
PTRange = Application.Union(Range(PTRange), Range(Rows(count)))
End If
count = count + 1
Next cell
I hope the above has been informative and below is an example to a potential solution:-
Option Explicit
Public Sub Sample()
Dim LngRow As Long
Dim Rng As Excel.Range
Dim Rng_Cl As Excel.Range
Dim Rng_PT As Excel.Range
LngRow = 21
'Initialise my Range
Set Rng_PT = Range("E21") '" & LngRow)
'Get the search range
Set Rng = Range("E2:E20")
'Loop through all the cells
For Each Rng_Cl In Rng
If Rng_Cl.Value = ("Duck") Then
'Adds just 5 column and not the whole row
Range(Rng_Cl.Address, ActiveSheet.Cells(Rng_Cl.Row, Rng_Cl.Column + 5).Address).Copy Range("E" & LngRow)
LngRow = LngRow + 1
End If
Next
'It is good practice to remove references once you are done with them
'This can help with memory consumption/speed and also more serious
'memory leaks (and actual stack overflows!) on larger macros
Set Rng = Nothing
Set Rng_PT = Nothing
End Sub

'Type Mismatch' Error with .Find() method in VBA

I'm writing a sub in Excel VBA, and it keeps giving me a 'Type Mismatch' error message when trying to assign the result of a .Find() to a Range variable. I feel pretty confident that my types are appropriate, so perhaps there's a syntax error somewhere?
Help would be GREATLY appreciated:
(Line preceded by asterisks is where error is thrown)
Sub totalTiger(fCode As String, project As String, ttls() As Double)
'Set shorcuts to Worksheets
Dim this As Workbook: Set this = ThisWorkbook
Dim proj As Worksheet: Set proj = this.Worksheets(project)
'Dim req variables
Dim tRng As Range: Set tRng = proj.Range("A:A").Find(What:="Program Description") 'Establish where Staff data ends and Tiger data begins
***Dim rng As Range: Set rng = proj.Range("C:C").Find(What:=fCode, After:=tRng) 'First fCode entry***
'For each fCode entry BEFORE the Tiger data, sum the assignments by month
Do While Not rng Is Nothing And rng.row > tRng.row
'For each month
For col = 4 To 15
'Add this month's assignment to our running total
ttls(col - 4) = ttls(col - 4) + CDbl(proj.Cells(rng.row, col).Value)
Next
'Update the rng reference
Set rng = proj.Range("C:C").Find(What:=fCode, After:=rng)
Loop
End Sub
I think the problem is in "After:=tRng": it may be out of the range of the "find"
Dim rng As Range: Set rng = proj.Range("C:C").Find(What:=fCode, After:=tRng)
Try removing "After:=tRng" and, if it works after removing, then try to insert a correct range.
Dim rng As Range: Set rng = proj.Range("C:C").Find(What:=fCode)
I'm not sure that's what you need, but you can try:
Dim rng: Set rng = proj.Range("C:C").Find(What:=fCode, After:=proj.Range("C" & tRng.Row))
It finds the first 'fCode' starting by the row where was found "Program Description"
If you are scratching your head because all logical explanations (such as those above) do not fit, it may be CORRUPTION. I have a live workbook example where, as absurd as it looks, a sheet (with nothing on it; hitting control end lands on A1) reproducibly shows ?activesheet.usedrange.address giving "$B$1:$A$1" and ?activesheet.usedrange.count gives 0.
Thus a most rigorous coding defense should test, BEFORE doing the find,
If Not Intersect(tRng, proj.Range("A:A")) Is Nothing
for the example above, and before any general range expression for {expression}.find,
If Not Intersect(tRng, {expression}) Is Nothing
Your "Else" could drill into the issue, e.g.
msgbox tRng.address & "/" & {expression}.address
The If statement would prevent the Type Mismatch error, not only if you "illegally" or improperly set an "After:" that is not in the search range, but also for at least some cases of corrupt workbooks such as I have encountered IRL. Corrupt workbooks are scary, brutally dangerous, but Microsoft won't address the problem, so corruption is going to happen; testing such as above would at least serve to sound a potential corruption alarm (if applicable), as well as serve the basic function of simply identifying if you had an (After and {expression}) combination that is not permitted.
It is possible that tRng is set to nothing, so the problem is not with the returned value of find, but rather with the parameter you give it.

for each control variable must be variant or object

Really new to VBA here... I've looked around and tried to piece together some code to fulfil my need. Think it's almost there, but I'm getting errors that are likely easy to overcome and yet I don't know how.
The code looks at the current sheet (STOCK), and takes a 'target' text value from cell A2. It then searches a named range in another sheet 'Other'. If it determines one of the cells ('cand') in Other to be equal to the target value, then a value of "True" will be applied to column G in the STOCK sheet, on the same row of the original target.
Hopefully this makes sense. I've copied in the code which will maybe shed more light on things.
Dim target As String
Dim cand As String
Dim currentrow As Integer
Sub search_named_range()
' This range is hard coded; we can try A:A if hard code version works '
For Each target In Worksheets("STOCK").Range("A2:A1000")
' retrieve the row of the current range, for use when setting target values '
currentrow = Range(target).Row
' FOR loop to search range of part numbers in Mojave '
For Each cand In Worksheets("Other").Range("N9:N150")
If StrConv(cand.Value, 2) = StrConv(target, 2) Then
Worksheets("STOCK").Range("G" + currentrow) = "True"
GoTo FORend
End If
Next cand
' If part is not found, do nothing and return to find next target '
FORend: Next target
End Sub
Currently I'm getting the error 'For Each control variable must be Variant or Object', but can't find anywhere that explains why this is. I'm sure it's pretty obvious, but a steer would be really appreciated.
Thanks.
You can't use a String variable in a For Each. You're using tartget and cand as the control variables in your For Each loops but you have defined them as strings. They need to be an object, and specifically an object that is contained the collection of objects you're iterating. You're iterating over a range, which is a collection of ranges, so your control variables need to be Range objects.
Sub search_named_range()
Dim rCell As Range
Dim rCand As Range
For Each rCell In Worksheets("STOCK").Range("A2:A1000").Cells
For Each rCand In Worksheets("Other").Range("N9:N150").Cells
If StrComp(rCand.Value, rCell.Value, vbTextCompare) = 0 Then
rCell.Offset(0, 6).Value = "True"
Exit For 'exits the rCand For, but no the rCell one
End If
Next rCand
Next rCell
End Sub
Other changes that weren't correcting errors:
I'm not sure why you declared your variables outside the sub, but I put them inside.
You don't need to define .Cells at the end of the For Each line, but I like to. You could iterate over .Rows or .Columns or .Areas with a Range (although .Cells is the default).
There's nothing wrong with StrConvert, but you could also use LCase() or, as I do, StrComp.
Since I already have a reference to a cell on the current row (rCell), I use that and Offset to fill in a column I want.

Resources