How to update RefersToR1C1 Excel named range in Visual Studio/VB - excel

Latest edit: I've tried a ton of stuff, still can't figure this out. A more concise question is how I assign the variable. Per the first comment I got, I'm using VSTO.
Dim wksSheetVSTO As Worksheet = Globals.Factory.GetVstoObject(wsThisSheet)
Dim nrThisNamedRange As NamedRange = ???
Original Question:
I am brand new to Visual Studio/VB.Net but am an experienced VBA programmer. I have a very complex spreadsheet that uses Excel named ranges, and I can’t seem to figure out how I can manipulate them.
The existing spreadsheet uses Excel named ranges that essentially act like database tables. When a user adds a row, it adds a row to the named range. Since Excel doesn’t automatically extend the range itself, the VBA code re-defines the named range to include the new row by re-defining the RefersToR1C1 property of the named range. It works perfectly in VBA. (I can post the working VBA code if it helps.)
In Visual Studio/VB, I can’t figure out a way to assign a variable to the named range. The only documentation I can find talks about assigning a variable by creating a NEW named range, but I already have the named range. My work-around (which works, but looks bad to me) is below:
Dim rngDataStore As Excel.Range
Dim nrThisNamedRange As NamedRange
...<<stuff happening here>>...
rngDataStore.Resize(1).Insert(Excel.XlInsertShiftDirection.xlShiftDown)
' The next line is what I don't want.
nrThisNamedRange = wksSheetVSTO.Controls.AddNamedRange(wksSheetVSTO.Range("A1"), <<name>>)
nrThisNamedRange.RefersToR1C1 = <<new named range R1C1 here>>
I don’t want to just extend the working range (rngDataStore), I need to actually update the definition of the named range so that I can continue to reference and write to it. (This is doing what ctrl+F3 allows you to do within Excel.) I’ve tried looping through the Names collection, but other than using the ‘Controls.AddNamedRange’ I can’t figure out how to simply assign the nrThisNamedRange variable and update its RefersToR1C1 property.
Added: The above code doesn't always work, it's giving me an error that the named range already exists. (Which of course I know!)

For global named range:
Dim name = Globals.ThisWorkbook.Names.Item("name")
Dim range = name.RefersToRange
name.RefersTo = range.Resize(range.Rows.Count + 1)

#Slai helped me uncover the answer (the worksheet had global named ranges in it) so I thought I'd at least share the full function for anyone who might need it.
Function ExpandNamedRange(sRangeName As String, lRowsToAdd As Long, sSheetName As String) As Boolean
' Adds the specified number of rows and resets the surrounding named range.
Dim wsThisSheet As Excel.Worksheet = Globals.ThisWorkbook.Application.Worksheets(sSheetName)
Dim rngDataStore As Excel.Range = wsThisSheet.Range(sRangeName)
If lRowsToAdd > 0 Then
rngDataStore.Resize(lRowsToAdd).Insert(Excel.XlInsertShiftDirection.xlShiftDown)
' For a global/workbook named range
Globals.ThisWorkbook.Names.Item(sRangeName).RefersToR1C1 = "=" & wsThisSheet.Name &
"!R" & rngDataStore.Row - lRowsToAdd & "C" & rngDataStore.Column &
":R" & rngDataStore.Row + rngDataStore.Rows.Count - 1 & "C" & rngDataStore.Column + rngDataStore.Columns.Count - 1
' For a local/worksheet named range
'wsThisSheet.Names.Item(sRangeName).RefersToR1C1 = "=" & wsThisSheet.Name &
' "!R" & rngDataStore.Row - lRowsToAdd & "C" & rngDataStore.Column &
' ":R" & rngDataStore.Row + rngDataStore.Rows.Count - 1 & "C" & rngDataStore.Column + rngDataStore.Columns.Count - 1
End If
Return True
End Function
Two things I learned:
(1) I didn't actually have to refer to a NamedRange object at all, I could just use the Names collection of the workbook; I was originally trying that because my first attempts failed. But this is nice, it saved me steps.
(2) In VBA, you can refer to 'global' Named Ranges from anywhere. That's where I got stuck.

Related

Change named range reference to be a combination of two other named ranges

i am currently working on a app that is a combination of java + vba.
What i want to do:
Some of the data is exported to excel with the help of the apache poi library in the form of a table. I am using this table to define a couple of sub tables that contain the same information, just not the whole table, for example my main table has columns A to F and 300 rows of info, my sub table has column A to C with 150 rows of info. What i do is declare via apache poi 2 different named ranges, one for the header of the table and a second one for the exact content that i want. My goal is to combine these 2 named ranges into one by joining the references, since i want it as a separate table that i will export to a powerpoint template containing placeholders for these tables.
What i have:
I will show just the vba code, since i don't have any problems with the java part.
Public Sub test()
Dim rangeHeaderValue As Variant
Dim rangeContentValue As Variant
rangeHeaderValue = ThisWorkbook.Names("TABL1_Performance_Data_1").RefersTo
rangeContentValue = ThisWorkbook.Names("TABL2_Performance_Data_1").RefersTo
ThisWorkbook.Names.Add Name:="test", RefersTo:=Right(rangeHeaderValue, (Len(rangeHeaderValue) - 1)) &
";" & Right(rangeContentValue, (Len(rangeContentValue) - 1))
End Sub
What i receive as reference to the named range:
="Performance_Data!$A$10:$Q$14;Performance_Data!$A$14:$Q$18"
What i expect: The same, but without quotes, since it doesn't work for some reason. What may be the problem or is there any type of better solution? I am not really experienced with VBA, sorry in advance.
Tried doing it with Union, but it gives me errors because it expects ranges and the ranges of the named ranges change dynamically, a.k.a on each export, since the data is different.
add an =-sign: (and the , instead of the ;)
Public Sub test()
Dim rangeHeaderValue As String
Dim rangeContentValue As String
rangeHeaderValue = ThisWorkbook.Names("TABL1_Performance_Data_1").RefersTo
rangeContentValue = ThisWorkbook.Names("TABL2_Performance_Data_1").RefersTo
ThisWorkbook.Names.Add Name:="test", RefersTo:="=" & Right(rangeHeaderValue, (Len(rangeHeaderValue) - 1)) & "," & Right(rangeContentValue, (Len(rangeContentValue) - 1))
End Sub

Switch Worksheet Names without Updating References to those Sheets

My file has two identical Worksheets for users to input two different sets of assumption variables, called "InputA" and "InputB". I want to quickly switch which Input sheet is feeding into the other sheets of the model.
Using Find and Replace took over 5 minutes, and there were over 350,000 references to "InputA".
I tried the following macro, which takes an instant to run, but unfortunately also changes all references in the workbook, effectively keeping everything referenced to the original input sheet.
Sheets("InputA").Name = "temp"
Sheets("InputB").Name = "InputA"
Sheets("temp").Name = "InputB"
Is there a way to execute the macro but prevent any references to worksheets from changing to the new sheet name, basically freezing everything except the sheet name change? Or perhaps any other solution that will work quickly? I don't want to go through 350,000 instances and rewrite using INDIRECT(), as that is the only other solution I've seen, because my references are complex and nested and that will take an age.
Thanks.
Assuming that your 2 Input-Sheets have the same structure, I would suggest the following:
Create a named range on Workbook-level, name it for example InputData. This range should contain all data from InputA.
Create a helper-sheet and name it Input - you can later set it to hidden.
Mark the range in the new sheet that is exactly the size of the Input-Data-Range and enter the formula =InputData as Array-formula. You can do so by entering Ctrl+Shift+Enter. The formula should have curly brackets and the sheet should now display the data of InputA.
Change all you formulas to refer to the helper Sheet Input instead of InputA.
Create a macro:
Sub switchInput()
Const sheetAName = "InputA"
Const sheetBName = "InputB"
With ThisWorkbook.Names("inputData")
If InStr(.RefersTo, sheetAName) > 0 Then
.RefersTo = Replace(.RefersTo, sheetAName, sheetBName)
Else
.RefersTo = Replace(.RefersTo, sheetBName, sheetAName)
End If
End With
End Sub
This routine will just change the definition of the named range to point either to the first or second input sheet. As a consequence, the helper sheet will show the data of the selected Input-Sheet. All your formulas itself stays unchanged.
As stated in the comments, you could take the approach recommended by Damian and use a conditional in all relevant cells. The generic conditional would be
=IF(A1="InputA",FORMULA INPUTA,FORMULA INPUTB)
This formula makes A1 the cell that decides which input to pull. This will make changing the around 350.000 output formulas in your workbook the bottleneck, the following macro takes care of changing all the formulas to conatin the conditional:
Sub changeFormulas()
Dim rng As Range, cll As Range
Set rng = shOutput.Range("A2:C10") 'Your relevant worksheet and range here
Dim aStr As String, bStr As String, formulaStr As String
aStr = "InputA"
bStr = "InputB"
For Each cll In rng
If cll.HasFormula And InStr(1, cll.Formula, aStr, 1) Then
formulaStr = Right(cll.Formula, Len(cll.Formula) - 1)
cll.Formula = "=IF(A1=" & Chr(34) & aStr & Chr(34) & "," & formulaStr & "," & Replace(formulaStr, aStr, bStr) & ")" 'Change A1 to the reference most suited for your case
End If
Next cll
End Sub
This might take a bit of time, since it has to access all the relevant cells one by one, but it will only have to run once.
To explain: This macro will go through all the cells in your range rng specified at the top. If a cell has a formula in it and the formula contains "InputA" it will change that formula to fit the generic conditional (with the cells own formula of course). Chr(34) is the quotation mark ", I find using Chr(34) more readable than multiple quotation marks """, but that is just preference.

Hard Coding Range works but using Named Range failes - Excel VBA

I have a code snippet that I can't understand why it's failing. I get "Application-Defined or Object-Defined Error". I tried hard coding the range and it does work, but I even hardcoded the "Named Range" and it still failed.
Eg: This Doesn't Work
If regexp.test(strInput) Then
Set clloffset = rcell.Offset(0, 1)
Call GetColLet(rcell)
'Set PalletCol = Range(rcell.Address(False, False) & ":K39")
Set PalletCol = ActiveWorkbook.ActiveSheet.Range("K24:K39")
Set FormulaCol = ActiveWorkbook.ActiveSheet.Range("L24:L39")
ActiveWorkbook.ActiveSheet.Range(FormulaCol).Formula = "=" & Chr(34) & "BINWH" & Chr(34) & "&" & rcell.Address(0, 1)
But this does and I can't get it!
If regexp.test(strInput) Then
Set clloffset = rcell.Offset(0, 1)
Call GetColLet(rcell)
'Set PalletCol = Range(rcell.Address(False, False) & ":K39")
Set PalletCol = ActiveWorkbook.ActiveSheet.Range("K24:K39")
Set FormulaCol = ActiveWorkbook.ActiveSheet.Range("L24:L39")
ActiveWorkbook.ActiveSheet.Range("L24:L39").Formula = "=" & Chr(34) & "BINWH" & Chr(34) & "&" & rcell.Address(0, 1)
Note: I tried using both (FormulaCol) and ("FormulaCol"), I'm not sure when to use "" inside a named range to be honest, but neither work. Then I tried hard coding ("L24:L39"). and it worked. I appreciate any help troubleshooting this.
There are three main methods of accessing ranges in Excel:
Hard-coded values
Variables
Named Ranges
Hard-coded values
I won't really spend time explaining these, but here are some examples:
ThisWorkbook.Sheets("Name").Range("A1:B2")
ActiveWorkbook.ActiveSheet.Range("C3:E50")
Sheet1.Cells(1,3) 'Range("C1")
Notes
ThisWorkbook is a reference to the workbook which holds the code. An important distinction from ActiveWorkbook, as they are not always the same workbook (primarily when 2+ workbooks are open).
Sheet1 is the code name of a sheet. Sheet code names can only be used for the sheets contained in ThisWorkbook. If referencing a sheet from a different workbook, you'd have to use either the name or index to access it.
Variables
This would be like the FormulaCol in your original code. Variables are either declared in the code (Set FormulaCol = ActiveWorkbook.ActiveSheet.Range("L24:L39")) at local or global scope level, or are passed into routines as parameters (Function Sample(rangeVariable as Range) : End Function).
Interacting with a range variable could be thought of as using substitution, such as:
Set FormulaCol = Sheet1.Range("L24:L39")
FormulaCol.Formula = ""
instead of
Sheet1.Range("L24:L39").Formula = ""
Named Ranges
Interacting with a named range is similar to interacting with a hard-coded range. Assuming that the named range "Test" references Range("B2:C3") on Sheet1, it would be interacted with via:
Sheet1.Range("Test")
However, before named ranges can be referenced through VBA, they first have to be set up in the workbook. This can be done a couple of different ways:
Select all the cells for the named range, then in the "Name Box" (to the left of the formula bar) type in the name for that named range, and press enter (if you forget "enter", the name doesn't get saved).
Open the Name Manager in the Formulas tab and press "New" (or "Edit", if editing an existing named range). Note: when adding a named range via the manager, you can select the scope to be either the workbook or any of the sheets within the workbook. This affects which sheets can reference the named range.
and a window will open where you choose the name for the named range, and select what range it's referencing.

Populate correct headers from closed workbook

I have been working on the problem below for a while now and I got a very basic version to work without error handling. My goal is to have this macro run from the personal macro workbook.
I am importing pipe delimited text files from various sources and none of the formatting in the headers match (some providers decided to play around with the entire layout). With this issue at hand, I created an Excel workbook named Reference(aka Map) with the incoming files layout and standardized/corrected columns' name formatting.
The good news for me is, the file ID will always be on column A. I have about 65 files that need to processed each month so I need to minimize all possible steps and therefore need to have the Reference workbook closed.
With this, I looked around online and put together most of the solution to pull in the new headers based on the ID located in A3. Yet a dilemma still exists, sometimes the ID on A3 will not exist on the Reference workbook - I need to have the vlookup move down to the next row until the result does not equal #N/A or 0 or blank.
At this point I got the 'Do Loop Until' to find the correct row for the first match - works perfectly with out any code following it.
As soon as the vlookup finds a row with an existing ID, then run the snippet below to populate the remaining headers. The only side effect of the next step is, for some reason rID offsets +1 row, undoing the 'Do Loop until' if the final row does not contain a matching ID.
'> Populate remining headers
For Each cell In rng1
cell.Value = ("=VLOOKUP(" & cID & rID & "," & map & "," & i & ",FALSE)")
i = i + 1
Next
This is what I have so far:
Sub DAc_lookup_headers()
Dim wb1 As Workbook 'Current text/file
Dim map As String 'reference Map
Dim cID As String 'wb1 look up column A
Dim rID As String 'wb1 starting row number
Dim rng1 As Range 'wb1 Collection header range
Dim i As Long 'Index number per cell in range
Set wb1 = ActiveWorkbook
Set rng1 = wb1.ActiveSheet.[A1:G1]
map = ("'C:\Users\x165422\Desktop\New folder\[Reference.xlsx]Ref'!$A$1:$I$13")
rID = 3 'Row where ID is - will increment + 1 if not found
cID = "A" 'Column where ID is
i = 3 'Starting vlookup Index number - to increment per cell in range
'>Look for ID until value is found
Do
wb1.ActiveSheet.[a1].Value = ("=VLOOKUP(" & cID & rID & "," & map & "," & i & ",FALSE)")
rID = rID + 1
Loop Until wb1.ActiveSheet.[a1].Text <> "#N/A" Or "0"
'> Populate remining headers
For Each cell In rng1
cell.Value = ("=VLOOKUP(" & cID & rID & "," & map & "," & i & ",FALSE)")
i = i + 1
Next
'> Convert to values
With rng1
.Value = .Value
End With
End Sub
I'll try to help a bit, because you show effort to learn. I will give you some useful tips regarding overall VBA coding and also regarding your issues. Please try to remember them.
It's difficult to read your code, because your variables are poorly named. In VBA there is a difference between a cell and it's value. In your code you have rng1 and rng2. I see that currently rng1 represents cell's value, and rng2 - a cell itself. I'm not sure if you intentionally did that, but now you will understand it. IF you mean a cell itself, then naming the variable rng2 is well understood. But IF you mean cell's value, naming the variable rng1 is misleading. That's only for being able to read the code more easily. Since you defined this variable as string, I suspect that you are expecting it to receive a value of string type. So, you should name your variable something in relation with it's type, i.e. something beginning with str... or s... which would mean that this variable is of string type, e.g. strValue or sID. preferable approach is to use str, as s could be confused with single type (there is such data type in VBA too). You should apply this logic to every variable that you use. String - str..., long - lng..., integer - int..., double - dbl..., boolean - bln..., etc. One type is kind of specific and you use it here too. It's the object type. Objects are not only objects, they are broken down to many different types, and within your code you use range and workbook objects too. You can name them o... or obj..., or usually it's better to use a more specific name, like rng... (for range) or wb... (for workbook). Usual data type variables take values without Set clause, while objects always need Set in order to be associated with some actual object, like range, workbook, sheet, etc. In your code we see wb2 variable which is a string. As now you know, that's not good.
So, in this case you should rename rng1 if you expect it to be string and not a range and learn to always use this naming convention. Same goes to wb2.
Always use Option Explicit as the topmost line in your code module, before all of the subs. This simply prevents from typos. And that's actually an indispensable line. I always use it and everyone should. After doing this, I see that you code will not run, because cell variable is not defined. Use Dim cell As Range, because cell is actually an object of range type. Set is omitted in this case, because For Each ... In ... Next loop does that for you.
Now your actual problems.
You would find Worksheet.UsedRange useful for determining columns count. That represents the area in a sheet from A1 to the last used cell (that's actually an intersection of the last used column and the last used row). Given that your data spans from the column A and when it ends there are no more data to the right not belonging to any column, you might use ActiveSheet.UsedRange.Columns.Count to get the number of columns in your UsedRange and hopefully in your sheet.
I mentioned UsedRange first, because I wanted you to get acquainted with it. We'll use it to solve the problem of vlookup. So the first step that I would suggest before the vlookup is to find the cell with your ID. We should first dim variables that we'll use, e.g. rng3:
Dim rng3 As Range
Then my suggestion is to find the cell with ID looping through cells in the column A, beginning A3, but not looping until the end of the column, because there are 1m rows, but until we reach the last actually used row:
For Each cell In wb1.ActiveSheet.Range("A3:A" & wb1.ActiveSheet.UsedRange.Rows.Count)
If cell <> "" Then
Set rng3 = cell
Exit For 'we found the value, no need to continue looping, so we exit the For loop
End If
Next 'there is no need to write "cell" after "Next", because VBA knows which loop it is in.
Now, that we have the cell where your ID is, we can put lookups in:
i = 2 'Starting vlookup Index number per cell in range
For Each cell in rng2
cell.Value = "=VLOOKUP(" & rng3.Address & "," & wb2 & "," & i & ",FALSE)" 'again, wb2 is not a nice name for String variable
i = i + 1
Next
I hope here are no typos, as I have not tested it. However, I've double checked the code, it looks ok.
I got it to work with:
Sub DAc_lookup_headers()
Dim wb1 As Workbook 'Current text/file
Dim map As String 'reference Map
Dim cID As String 'wb1 look up column A
Dim rID As String 'wb1 starting row number
Dim rng1 As Range 'wb1 Collection header range
Dim i As Long 'Index number per cell in range
Set wb1 = ActiveWorkbook
Set rng1 = wb1.ActiveSheet.[A1:G1]
map = ("'C:\Users\x165422\Desktop\New folder\[Reference.xlsx]Ref'!$A$1:$I$13")
rID = 2 'Row where ID is - will increment + 1 if not found
cID = "A" 'Column where ID is
i = 3 'Starting vlookup Index number - to increment per cell in range
'>Look for ID until value is found
Do
rID = rID + 1
wb1.ActiveSheet.[a1].Value = ("=VLOOKUP(" & cID & rID & "," & map & "," & i & ",FALSE)")
Loop Until wb1.ActiveSheet.[a1].Text <> "#N/A" Or "0"
'> Populate remining headers
For Each cell In rng1
cell.Value = ("=VLOOKUP(" & cID & rID & "," & map & "," & i & ",FALSE)")
i = i + 1
Next
'> Convert to values
With rng1
.Value = .Value
End With
End Sub

Populating a list box with data from worksheet

I currently get the following error when I run the code (shown below)
Type Mismatch
To give a bit of context, the worksheet CourseSelection has row 3 populated from A to F. I would like to put the entries from A2:A6 into a listbox. However, I want to generalize this process and make it dynamic to include additional categories if they are added after column F. Therefore I need an automatic way to do this through code similar to what I have below. However, I am getting error messages and I am unsure why.
I defined TaskList as a Range prior to this code. When I hover over xlToRight when I run the code I see a very large negative value (-4191). I am unsure if this is part of the problem.
With Worksheets(CourseSelection).Range("A3")
Set TaskList = Range(.Offset(0, 1), .End(xlToRight))
End With
frmTaskSelection.lbTasks.RowSource = TaskList
Unless you have CourseSelection defined as a constant returning an existing worksheet name then the code will fail on With Worksheets(CourseSelection).Range("A3"). If you want to work with a sheet name CourseSelection you would use With Worksheets("CourseSelection").Range("A3").
Given you error message though you appear to have gotten past this point and your code appears to be failing on frmTaskSelection.lbTasks.RowSource = TaskList. This is because RowSource expects an address
If you were looking to populate the values from a sheet called CourseSelection from A3 to Ax where x is the last used cell, then this code will work from any active sheet.
Please note thate I was unclear as to how you wanted to use further values from column F in addition to A2:A6. If you can provide further guidance/picture etc then the code below can be adapted to suit
Sub test()
Dim ws As Worksheet
Dim rng1 As Range
Set ws = Worksheets("CourseSelection")
Set rng1 = ws.Range(ws.[a3], ws.Cells(Rows.Count, "A").End(xlUp))
frmTaskSelection.lbTasks.RowSource = "'" & ws.Name & "'!" & rng1.Address
frmTaskSelection.Show
End Sub

Resources