.Find is not looking in values - vba

For some weird reason when cell B3 has a value "48987" and it is formatted as general (number, not string because it is right aligned) the find function works but when it is formatted as a comma separated number, the find function is unable to find it. The code given below is what I'm trying.
Set Loc = ActiveSheet.Range("B2:B10").Find("48987", LookIn:=xlValues)
If Not (Loc Is Nothing) Then
Debug.Print Loc.Address
End If
Note: I've tried with/without double quotes and the lookin options
But the moment I change the code to the one given below it works!!
Set Loc = ActiveSheet.Range("B2:B10").Find("48,987", LookIn:=xlValues)
I've restarted my system and done a lot of search but I'm unable to find a solution. This happens with dates because they are stored as an integer in excel but I don't know why this would happen with a number. Plus the same cell does get detected when I change the condition

Since you asked ...
When using the Find method and its LookIn parameter, xlFormulas ignores formatting (so if comparing with a range is more comparable to the Value property) whereas xlValues does not (so is actually more comparable to the Text property). Therefore, in most cases xlFormulas is preferable as it searches contents and formulas.
It's also good practice to specify other Find parameters as they can be inadvertently changed (via the worksheet) and may not be set to the values you expect.

Related

Excel VBA named range running out of space in refersto

So I have a large suite of code that creates an archive of data in sheets used by employees. Part of what makes this functional is named ranges on each sheet of usable data. In order for the data integrity to remain, I need to copy the named range objects from the archive sheet to its copy. The named ranges are built programatically and function as expected on the sheets. The problem I'm having is when I go to archive the sheet. Here is the code I'm using to handle the named range object:
For Each n In OldSht.Names
NamedRangeRefersTo = n.RefersTo
NamedRange = n.Name
TrimmedName = Right(n.Name, Len(n.Name) - InStr(1, n.Name, "!", vbTextCompare))
OldSht.Names(n.Name).Delete
OldSht.Names.Add Name:=ArchiveNamedRange, RefersTo:=NamedRangeRefersTo
Next n
The strings that grab data from n are used to add the same name object to the new sheet.
The problem I'm having is when a named range is referencing too large of a range when it hits the line Oldsht.Names.Add, it returns error 1004. I figured out it was the size of the referenced range by messing around with it. I haven't found the exact triggering cause, but this code works as-is when I use it on most of the named ranges. On large data sets with a joined data type that results in a very large named range (it would take a long time to explain how the ranges are built in text. It's a group of 8 sub functions with over 2000 lines of code), this results in the 1004 error.
What I'm confused by is why I can build the named range, use the named range, and copy the named range without issues (if I comment out the offending line, it executes perfectly but I lose data integrity). But when I take the referenced range into a code value, delete the old name reference, then add a new name(with a different name) and assign it the same refersto value of the old name, it can have this problem. I don't understand how it would be different doing this rather than just copying/renaming the name object. Unfortunately, I haven't found a workaround as of yet, nor have I found a clear cause of this error other than the fact that when I remove data or use smaller sets of data in test scenarios, I never have the problem. Does anyone have any ideas of what I can do? Does anyone have any ideas how a named range could be referring to a small enough range that it can be created, but using its refersto value to create a new named range could cause errors only when that is referencing a large range?
I wish I could provide some more concrete examples but unfortunately it would be very difficult to scrub enough sensitive info to provide the full code that would be necessary to reproduce my exact scenarios. Any ideas would be much appreciated.
As requested here's where ArchiveNamedRange gets set:
If Len(OldSht.Name) > 21 Then
ArchiveShtName = Left(OldSht.Name, 21) & DatePart("m", Date) & DatePart("d", Date) & DatePart("yyyy", Date)
Else
ArchiveShtName = OldSht.Name & DatePart("m", Date) & DatePart("d", Date) & DatePart("yyyy", Date)
End If
ArchiveNamedRange = ArchiveShtName & NameObjectName & "Test"
NameObjectName is just the name of the type of object and is passed in from another function. I'm not having an issue with the name just fyi. In the most extreme example the ArchiveNamedRange value at debug run time is = "OutageSystemProcedureMMDDYYYYSecurityRedactionTest" so the name might reach 50 and if things get crazier it might run upwards of 60 characters but it won't ever go beyond that or come anywhere near the 255 character limit. Ultimately, I haven't seen ArchiveNamedRange have an invalid value. It's just a string and it always has a value.
Edit-
Through my troubleshooting I've found that my code works when NamedRangeRefersTo has a length of 2075, but does not work when it has a length of 2091. So somewhere between 2075 characters and 2091 characters is a breaking point for assigning a string to RefersTo: in a named range.
So let's just assume there is a character limit for some reason of 2080 (or whatever it actually is between 2075 and 2091). When I initially find and create these named ranges, they are being given a range object. When I am copying the ranges I am copying as a string. Somehow when I pass a ranged object into RefersTo: it accepts characters beyond 2080 but when I pass in a string it does not. Given that this is my only breaking point of a large suite of code I'd rather find a workaround for this than have to re-factor the entire concept of my archive system. If I use a range object for copying the named ranges, their references follow the old Sheet. That means that when I copy the name over it can be "CriticalSystemsTest1" and referto: "CriticalSystemsTest1!$A$2,..." but once I copy that over and rename the archive worksheet (now CriticalSystems562015) the references adjust to be "CriticalSystems562015Test1!$A$2,..."
So I had to copy as a string to avoid that problem (it breaks data on the new sheet). All I really need is a creative way to overcome this character limit issue on my string. Rebuilding the named range from scratch on the new sheet is also not going to work. So I guess if anyone has ideas for how to work around this string size issue or a way of trimming the string while maintaining functionality of the named range, that would be amazing.
Each of these names has a worksheet level scope, so maybe if there's a way of using just the cell address($A$2) in RefersTo: so it doesn't also contain the worksheet reference (SheetName!), that would be a potential solution but I haven't figured out if that's even possible.
The reason the range definitions as strings are so long is that there are many areas within them. So one workaround would be to build up a new Range object area by area. You can use the string address of each area without running into any limits as each area only has a short reference. Using Range.Address gets the cell reference without the sheet reference, so you can create a new Range on a different sheet but with the same cells. Then use Union() to join all the areas and create the new name using the newly built Range instead of a string:
Dim i As Long, oldRange As Range, newRange As Range
Set oldRange = n.RefersToRange
Set newRange = oldSht.Range(oldRange.Areas(1).Address(External:=False))
For i = 2 To oldRange.Areas.Count
Set newRange = Union(newRange, oldSht.Range(oldRange.Areas(i).Address(External:=False)))
Next i
oldSht.Names.Add Name:="ArchiveNamedRange", RefersTo:=newRange
A couple of notes:
For ranges with many areas this is slow. If you can reliably tie down the threshold where you have problems, it would probably be worth testing for this first and only using this workaround where it was needed.
When testing I also ran into problems with using Worksheet.Range("some very long string range reference"), so this limitation isn't confined to named ranges.

VBA - getting column # with Find(), get error 91 - object var. or with block var. not set

I have a VBA script wherein I am trying to use find() to get the column number of a column on a separate worksheet (same workbook).
So, from "Sheet 1" I run this macro, to find the column on "Sheet 2" that has the word "Ins Val" in row 1. What's odd is that earlier in the macro, I use the same formula to get a column number without issue. However, the below code throws a "Run Time Error 91, Object Variable or With Block Not Set" but I can't figure why.
dim useDataWS as Worksheet, typeValColumn as Integer, theType as String, mainWS as Worksheet
Set mainWS = worksheets("Sheet 1")
Set useDataWS = worksheets("Sheet 2")
theType = mainWS.Cells(49,5).Value
'' the below line gives the error
typeValColumn = useDataWS.rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
But, earlier in that macro, I do the same thing- with no error :?
With useDataWS
noOneCol = .Rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
End With
Note: Even if I change that first part to use "with", the same error occurrs.
Any ideas? (Note: It's a more robust script, but I tried to get the parts that apply. If there's something else that might be causing this, let me know what other kinds of things I'm doing that could mess this up and I'll post more of the code).
edit: Thanks #Branislav Kollár - using search by "xlFormulas" instead of "xlValues" solves the issue. (Although I still don't know why Excel throws the error with 'values', but hey - it works!)
The reason, why the following line
typeValColumn = useDataWS.rows(1).Find(what:=theType, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False).Column
was giving an error is because the Find()method found Nothing, and applying .column to a Nothing results in error.
Solution can be replacing LookIn:=xlValues with LookIn:=xlFormulas.
I'd like to provide more info about why it does work, but I don't know honestly. One thing I found out is that LookIn:=xlFormulas will find even hidden cells and I guess it has more general usage.
More about Find() method can be found on MSDN Range.Find Method, or Find Method in Excel VBA or .Find and .FindNext in Excel VBA
Sometimes it is better to revert back to the native worksheet functions. While .Find is better for a worksheet-wide search, both .CountIf and .Match can easily locate a value in a single row or column. I typically use .CountIf to see if the value if there and .Match to retrieve the column index number.
with useDataWS
if cbool(application.countif(.rows(1), theType)) then
typeValColumn = application.match(theType, .rows(1), 0)
else
debug.print "not found in row 1"
end if
end if
You can also check for the value's existence with If IsError(app.Match(...)) but I prefer the two-function approach. If you can write a MATCH worksheet function that does not return #N/A then the above code will locate theType.
This is not so much an answer as a quick diagnostic tool. You swear that Ins Val is in row 1 of Sheet 2. Put this formula in the cell directly below the one containing Ins Val.
=CODE(MID(H$1, ROW(1:1), 1))
Adjust H$1 for the cell that actually contains Ins Val and fill down for at least 9 rows.
Excel 2013 addendum:
Excel 2013 introduced the UNICODE function that may be more appropriate for this purpose.
=UNICODE(MID(H$1, ROW(1:1), 1))
        
If you are receiving anything other than the results shown above, then you have rogue characters in the cell value.
The easiest one-off solution is to retype the value. If this is a repeated import then other methods need to be in place to compensate. The best method is to adjust the import at the source but that is not always possible. The worksheet function CLEAN does a reasonable job of removing rogue characters but it is not the best. Sometimes custom routines need to be written that address specific circumstances.

troubles passing a discontinuous named range into a custom function

I've been looking around stack overflow for an answer to this for longer than I care to admit now.
Here's what I have: In a worksheet I have a bunch of discontinuous cells which I need to check for the existence of specific text. I've created a simple function to do this and can do this easily when I define that range manually (in code).
However, when I procedurally create a named range (while doing other stuff) and then try passing in the named range, the function never executes.
I know that the named range is being properly created because I have auto-formatting on it and also I can reference the range with excel formula which accept discontinuous ranges (SUM and whatnot).
Here's the pertinent portions of my code:
Function customProcess1(NamedRange As Range) As Long
For Each c in NamedRange.Cells
...
Next c
End Function
In Excel when I type the formula as "=customProcess1(A1:A2)" I get my number back after the function runs. When I type in "=customProcess1(NamedRange)" my function never even executes.
Again, I'm using the named range as defined already in the document. I can observe the name in the name manager, it references the appropriate cells, i can use the range in formula which accept non-continuous ranges, etc. I can't figure out how to get my working named range into my function.
When I put the formula as "=customProcess1("NamedRange")" the function executes, but since the named range is not ""NamedRange"" but is "NamedRange" it fails to set the object as Range (the object is not found). I've tried taking the named range as a string, but again, if I don't put the quotes around the name, it won't even run the function. So then I've tried passing in a string with the quotes and taking the quotes off inside the function, but this isn't exactly working well either.
In short, I just want to get my non-continuous named range in my custom function. Once I do that, everything is golden.
Anyone have any ideas? I'm not sure why this has been such a chore.
I'm not sure why what you're trying doesn't work and don't really have time to research that part of it, but you could do the following:
Function customProcess1(NamedRange As String) As Long
Dim TheRange As Range
Set TheRange = Range(NamedRange)
For Each c in TheRange.Cells
...
Next c
End Function
Hope this helps.
Adapting your UDF(), I coded:
Function customProcess1(NamedRange As Range) As Long
For Each c In NamedRange.Cells
customProcess1 = customProcess1 + c.Value
Next c
End Function
I then assigned the name Mike to the cells B6,C8,D10 and placed values in these cells. I then placed the formula:
=customProcess1(Mike)
in a cell and got the following:
NOTE:
I did not use =customProcess("Mike")

Trouble with undefined Object in VBA Run-Time error '91'

I've been working all week to prepare a VBA application, which I'll be using in a meeting today. Unfortunately the code that has been running all week last week without a hitch, has decided to break over the weekend.
I constantly get Object variable or With block variable not set Run-time error '91' from this statement:
With Sheet5
Set adjrng = .Range(.Cells(.Range("G43:G60").Find(.Range("H39").Value).Row, 10), .Cells(.Range("G43:G60").Find(.Range("H39").Value).Row, 21))
End With
idea is to set a range in the row of the Range G43:G60 where the Value of H39 matches from Column 10 to Column 21.
Anybody spot the issue? My brainz are to nervous and sleepy this morning...
Thanks a bunch
Ben
EDIT:
After playing a bit with find and replace, the issue seems to be that excel has not yet properly calculated the "lookin" and "lookup" Ranges G43:G60 and H39. A simple recalculation didn't make excel rediscover the contents but when I used one of my input toggles to display a different value in those cells, and the went back to the original it did manage to find it.
Maybe using find for this is bad style, the find formula has these kind of hicups usually or any other comments on this? For now everything works fine again, but I'm afraid of running into these issues again. Any tips would thus still be appreciated.
EDIT: (from comment below)
We have a dynamic range (G43:J60) where unique identifiers are listed in column G and data is to the right. if something is changed in the data part of the range AND the lines uniqued identifier in column G matches the one in cell H39 a sub() is triggered via worksheet_ change intersect(target, adjrng) Defining that adjrng is the part that throws errors when find returns null.
I believe you are simply trying to set a range hoping that there will be two matches to the value in H39 within the G43:G60 range. While I avoid on Error Resume Next (never could adjust to the logic of breaking something in the hope to accomplish something), I always check that the values will be there when I look for them.
Dim rwUNIQ as long
Set adjrng = nothing
With Sheet5
if cbool(application.countif(.Range("G43:G60"), .Range("H39").Value)) then
rwUNIQ = application.match(.Range("H39").Value, .Range("G43:G60"), 0)
Set adjrng = .Cells(42 + rwUNIQ, 10).resize(1, 11)
end if
if not adjrng is nothing then
'do something with adjrng
end if
Set adjrng = nothing
End With
That checks to make sure that there are at least two H39 values in G43:G60 before proceeding. There is no further error control because we've counted at least two of them. You might want to compensate with an Else for when there isn't. If a single H39 value was found, you also might want to select a single row.
Remember that the .Find uses many parameters that were retained from the last time Find was used, whether in VBA or with a user on the worksheet. You have a real lack of parameters that specify the options that Find should use to proceed. e.g. xlPart or xlWhole, After:=what?, look in formulas or values, etc.
EDIT:* Modified the code to look for a single instance of the value in H39 and .Resize to expand the width (as per OP's comments).

Why is my conditional format offset when added by VBA?

I was trying to add conditional formats like this:
If expression =($G5<>"") then make set interior green, use this for $A$5:$H$25.
Tried this, worked fine, as expected, then tried to adapt this as VBA-Code with following code, which is working, but not as expected:
With ActiveSheet.UsedRange.Offset(1)
.FormatConditions.Delete
'set used row range to green interior color, if "Erledigt Datum" is not empty
With .FormatConditions.Add(Type:=xlExpression, _
Formula1:="=($" & cstrDefaultProgressColumn & _
.row & "<>"""")")
.Interior.ColorIndex = 4
End With
End With
The Problem is, .row is providing the right row while in debug, however my added conditional-formula seems to be one or more rows off - depending on my solution for setting the row. So I am ending up with a conditional formatting, which has an offset to the row, which should have been formatted.
In the dialog it is then =($G6<>"") or G3 or G100310 or something like this. But not my desired G5.
Setting the row has to be dynamicall, because this is used to setup conditional formats on different worksheets, which can have their data starting at different rows.
I was suspecting my With arrangement, but it did not fix this problem.
edit: To be more specific, this is NOT a UsedRange problem, having the same trouble with this:
Dim rngData As Range
Set rngData = ActiveSheet.Range("A:H") 'ActiveSheet.UsedRange.Offset(1)
rngData.FormatConditions.Delete
With rngData.FormatConditions.Add(Type:=xlExpression, _
Formula1:="=($" & cstrDefaultProgressColumn & _
1 & "<>"""")")
.Interior.ColorIndex = 4
End With
My Data looks like this:
1 -> empty cells
2 -> empty cells
3 -> empty cells
4 -> TitleCols -> A;B;C;...;H
5 -> Data to TitleCols
. .
. .
. .
25
When I execute this edited code on Excel 2007 and lookup the formula in the conditional dialog it is =($G1048571<>"") - it should be =($G1<>""), then everything works fine.
Whats even more strange - this is an edited version of a fine working code, which used to add conditional formats for each row. But then I realized, that it's possible to write an expression, which formats a whole row or parts of it - thought this would be adapted in a minute, and now this ^^
edit: Additional task informations
I use conditional formatting here, because this functions shall setup a table to react on user input. So, if properly setup and a user edits some cell in my conditionalized column of this tabel, the corresponding row will turn green for the used range of rows.
Now, because there might be rows before the main header-row and there might be a various number of data-columns, and also the targeted column may change, I do of course use some specific informations.
To keep them minimal, I do use NamedRanges to determine the correct offset and to determine the correct DefaultProgessColumn.
GetTitleRow is used to determine the header-row by NamedRange or header-contents.
With ActiveSheet.UsedRange.Offset(GetTitleRow(ActiveSheet.UsedRange) - _
ActiveSheet.UsedRange.Rows(1).row + 1)
Corrected my Formula1, because I found the construct before not well formed.
Formula1:="=(" & Cells(.row, _
Range(strMatchCol1).Column).Address(RowAbsolute:=False) & _
"<>"""")"
strMatchCol1 - is the name of a range.
Got it, lol. Set the ActiveCell before doing the grunt work...
ActiveSheet.Range("A1").Activate
Excel is pulling its automagic range adjusting which is throwing off the formula when the FromatCondition is added.
The reason that Conditional Formatting and Data Validation exhibit this strange behavior is because the formulas they use are outside the normal calculation chain. They have to be so that you can refer to the active cell in the formula. If you're in G1, you can't type =G1="" because you'll create a circular reference. But in CF or DV, you can type that formula. Those formulas are disassociated with the current cell unlike real formulas.
When you enter a CF formula, it's always relative to the active cell. If, in CF, you make a formula
=ISBLANK($G2)
and you're in A5, Excel converts it to
=ISBLANK(R[-3]C7)
and when that gets put into the CF, it ends up being relative to the cell it's applied to. So in row 2, the formula comes out to
=ISBLANK($G655536)
(for Excel 2003). It offsets -3 rows and that wraps to the bottom of the spreadsheet.
You can use Application.ConvertFormula to make the formula relative to some other cell. If I'm in row 5 and the start of my range is in row 2, I make the formula relative to row 8. That way the R[-3] will put the formula in A5 as $G5 (three rows up from A8).
Sub test()
Dim cstrDefaultProgressColumn As String
Dim sFormula As String
cstrDefaultProgressColumn = "$G"
With ActiveSheet.UsedRange.Offset(1)
.FormatConditions.Delete
'set used row range to green interior color, if "Erledigt Datum" is not empty
'Build formula
sFormula = "=ISBLANK(" & cstrDefaultProgressColumn & .Row & ")"
'convert to r1c1
sFormula = Application.ConvertFormula(sFormula, xlA1, xlR1C1)
'convert to a1 and make relative
sFormula = Application.ConvertFormula(sFormula, xlR1C1, xlA1, , ActiveCell.Offset(ActiveCell.Row - .Cells(1).Row))
With .FormatConditions.Add(Type:=xlExpression, _
Formula1:=sFormula)
.Interior.ColorIndex = 4
End With
End With
End Sub
I only offset .Cells(1) row-wise because the column is absolute in this example. If both row and column are relative in your CF formula, you need more offsetting. Also, this only works if the active cell is below the first cell in your range. To make it more general purpose, you would have to determine where the activecell is relative to the range and offset appropriately. If the offset put you above row 1, you would need to code it so that it referred to a cell nearer the bottom of the total number of rows for your version of Excel.
If you thought selecting was a bit of a kludge, I'm sure you'll agree that this is worse. Even though I abhor unnecessary Selecting and Activating, Conditional Formatting and Data Validation are two places where it's a necessary evil.
A brief example:
Sub Format_Range()
Dim oRange As Range
Dim iRange_Rows As Integer
Dim iCnt As Integer
'First, create a named range manually in Excel (eg. "FORMAT_RANGE")
'In your case that would be range "$A$5:$H$25".
'You only need to do this once,
'through VBA you can afterwards dynamically adapt size + location at any time.
'If you don't feel comfortable with that, you can create headers
'and look for the headers dynamically in the sheet to retrieve
'their position dynamically too.
'Setting this range makes it independent
'from which sheet in the workbook is active
'No unnecessary .Activate is needed and certainly no hard coded "A1" cell.
'(which makes it more potentially subject to bugs later on)
Set oRange = ThisWorkbook.Names("FORMAT_RANGE").RefersToRange
iRange_Rows = oRange.Rows.Count
For iCnt = 1 To iRange_Rows
If oRange(iCnt, 1) <> oRange(iCnt, 2) Then
oRange(iCnt, 2).Interior.ColorIndex = 4
End If
Next iCnt
End Sub
Regarding my comments given on the other reply:
If you have to do this for many rows, it is definitely faster to load the the entire range into memory (an array) and check the conditions within the array, after which you do the writing on those cells that need to be written (formatted).
I could agree that this technique is not "necessary" in this case - however it is good practise because it is flexible for many (any type of) customizations afterwards and easier to debug (using the immediate / locals / watches window).
I'm not a fan of Offset although I don't state it doesn't work as it should and in some limited scenarios I could say that the chance for problems "could" be small: I experienced that some business users tend to use it constantly (here offset +3, there offset -3, then again -2, etc...); although it is easy to write, I can tell you it is hell to revise. It is also very often subject to bugs when changes are made by end users.
I am very much "for" the use of headers (although I'm also a fan of reducing database capabilities for Excel, because for many it results in avoiding Access), because it will allow you very much flexibility. Even when I used columns 1 and 2; better is it to retrieve the column nr dynamically based on the location of the named range of the header. If then another column is inserted, no bugs will appear.
Last but not least, it may sound exaggerated, but the last time, I used a class module with properties and functions to perform all retrievals of potential data within each sheet dynamically, perform checks on all bugs I could think of and some additional functions to execute specific tasks.
So if you need many types of data from a specific sheet, you can instantiate that class and have all the data at your disposal, accessible through defined functions. I haven't noticed anyone doing it so far, but it gives you few trouble despite a little bit more work (you can use the same principles again over and over).
Now I don't think that this is what you need; but there may come a day that you need to make large tools for end users who don't know how it works but will complain a lot about things because of something they might have done themselves (even when it's not your "fault"); it's good to keep this in mind.