Excel VBA named range running out of space in refersto - vba

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.

Related

Referring Range with Known Columns and Unknown Rows Excel VBA

How do you refer to a range where the number of columns is known but you don't know which row? What's the correct way of rendering Range("A&i:J&i")?
For i = 8 To WSData.Range("A8").End(xlDown).Row
If Cells(i, 1) = "Overall Totals:" Then
WSData.Range("A&i:J&i").Interior.Color = RGB(217, 217, 217)
End If
Next
Scott's answer is off course quite correct. However there are several other ways of referring to a variable range which you might find useful.
1) You could also use WSData.Range("A10", "J10"), i.e. you specify the top left and bottom right cells as two separate parameters. (The order of the paraneters doesn't actually matter!)
In your example, you would use: WSData.Range("A" & i , "J" & i)
2) I find using numbers, rather than letters for columns is useful, especially if your columns will be unknown in advance. The basic structure is as follows.
WSData.Range(Cells(1,10), Cells(10,10) 'A10 to J10)
or in your example
WSData.Range(Cells(1,i), Cells(10,i))
However one has to be careful! The default worksheet for the Cells range is the Active Worksheet. If this is not the same as the WSData, it will lead to a run time error. However, this can easily by avoided by specifying the worksheet to which the "Cells" belong:
WSData.Range(WSData.Cells(1,i), WSData.Cells(10,i))
This may look rather long-winded but it gives you complete flexibility in specifying your range as you can use variables for each of the cell parameters.

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

Using VBA to name ranges and create charts with those ranges

I'm new to VBA. I'm attempting to create over 500 xlClusteredColumn charts using two columns of information and I'd like to expedite the work. The first column contains names I'd like to use for named ranges (i.e.: Line1, Line2, etc.) and the second column contains the indirect references of the data ranges (i.e., Sheet1!C4:D28, Sheet1!C28:D90). I noticed that if I use a named range for the "Chart Data Series" field, the data shows up nicely (but I have to first create that named range being sure to include the INDIRECT formula in the reference, (e.g.: Named Range Line1 is equal to =INDIRECT(Sheet1!C4:C28)). The ranges will be static.
In reviewing prior questions I couldn't seem to find a solution that would select the first cell in this set and name it, then uses the second cell to define that range. I think I might need the ActiveWorkbook.Names.Add Name:= formula and combine it with a loop (but I couldn't get it to use a selection or cell to define the Add Name aspect, only a hard coded name).
If the solution requires it, I can go back and extract the individual ranges (i.e.: C4:D28) from the cell and have the chart reference only that if it makes the code simpler. I know my first outlined attempt isn't the only solution and there's probably one much more elegant. I figured using named ranges would speed up the chart work, but perhaps there's a way to cut that step out?
Populating a new sheet with all the charts for each of these ranges would be icing on the cake, but I'll be happy enough receiving help to get the data set up to chart.
Example information:
NameRange1 (let's say in cells A1:A4)
WKD_1_NB
WKD_2_EB
WKD_3_EB
SerRange1 (in cells B1:B4)
WKDpivot!C4:D43
WKDpivot!C84:D140
WKDpivot!C197:D233
(Chart data range requires the reference of named range "WKD_1_NB" to be '=INDIRECT(WKDpivot!C4:D43)' in order for the chart to work.
OK so why don't you try the two-step process. I am going to do this without Indirect because I don't see that it is necessary.
Sub CreateNames()
Dim rng As Range
Dim r As Range
Dim myName As String
Dim addr As String
Set rng = Range("A1:A2") '## Modify as needed
For Each r In rng.Cells
myName = r.Value
addr = "=" & r.Offset(0, 1).Value
ThisWorkbook.Names.Add myName, addr
Next
End Sub
This creates your names (screenshot). Note there are some rules about naming conventions and allowable names, etc., the code above does not take any of these in to account.
From there it should be fairly simple to create a loop that adds your charts one by one, and assigns each named range to each chart.

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.