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.
Related
From the below Image I want to compare Second Workbook(Records.xlsm) with First Workbook(HandBook.xlsm)
I want to check if Department ID and Course ID Combination is valid by comparing it with the first workbook(HandBook.xlsm) and highlight in yellow if the combination doesn't exist.
But When i tried to write the code,I was able to check only the first record, i.e in the below example Dept Id 3000 has three different course ID but when I try to compare it is validating only with the first record occurrence 3000-123 , if I try to put any other combination 3000-124 or 3000-125 it is highlighted as error which should not be the case.
Columns("B:B").Select
Range("B1").Select
ActiveCell.FormulaR1C1 = "=IF(OR(NOT(ISERROR(MATCH(RC[2],INDEX('[HandBook.xlsm]Dept-Course'!C2,MATCH(RC[1],'[HandBook.xlsm]Dept-Course'!C1,0),0),0)))),"""",""ERROR"")"
Selection.Copy
Range("A1").Select
Selection.End(xlDown).Select
Selection.End(xlDown).Select
Selection.End(xlDown).Select
Selection.End(xlUp).Select
ActiveCell.Offset(0, 1).Select
If ActiveCell.Row > 2 Then
Range(Selection, Selection.End(xlUp)).Select
End If
ActiveSheet.Paste
I prepared 2 solutions for you. The first doesn't require VBA. But it needs a helper column and this is why I think you won't like it. However, you may like to try. In the helper column enter this formula.
=SUMPRODUCT(('[082 STO 200829 Records.xlsm]Records'!$A:$A=C2)*('[082 STO 200829 Records.xlsm]Records'!$B:$B=D2))
The referenced workbook must be open at the time of writing the formula. After that it can be closed. The formula will return either 1 or 0 depending upon whether a match was found in the referenced file. Observe that column A:A in the referenced sheet holds data similar to C2 and B:B has that same relationship with D2. The result you can use to highlight cells using conditional formatting.
Select the first pair of Department/Course IDs on your Handbook sheet.
Create a New Rule to conditionally format these cells depending upon a formula. (In my worksheet that was C2:D2)
Insert this formula: =$E2=0 (In my example E:E is the helper column)
Choose the highlight you like.
Before you close the dialog correct the range to which the formula applies. The field originally shows just the selected cells. Extend the range all the way down your sheet. You might also have selected all to begin with but I prefer this way if the range is big and you don't want to drag the selection forever.
I prepared a VBA solution as well but I didn't much like that, either. It's a lot of code compared with your humble beginnings and that is before I got around to dealing with the screen flicker as the referenced file is opened and closed. I'm not sure I shall be able to deal with that entirely.
Therefore I abandoned that attempt when it was nearly done and now work on a solution that doesn't open the referenced workbook. I shall come back to publish it here later today.
Meanwhile I think the above solution has much to speak for it by way of simplicity. Bear in mind that you can have the helper column anywhere on the sheet, and you can hide it as well.
There are two parts of the code for this solution which must be placed exactly where they belong. The first part is an event procedure. It fires automatically when the user changes either the Department or the Course in the Handbook. This Change event will not be noticed anywhere in your workbook except in the worksheet concerned. Therefore the code must be in that tab's code module. That is an existing module, set up by Excel for this purpose.
The second part of the code deals with the external workbook which I identified as "Records.xlsm". Therefore I prefer it to be in a standard code module. That is a module you set up yourself. The default name will be Module1 but I (with the support of all but the most new newbies at programming) recommend to give a descriptive name. In my copy of the workbook I named it ADO_Conn for the ADODB Connection it contains.
In addition to the ADODB connection this part also contains various parameters which you may adjust to match your needs and liking. They take the shape of enumerations which offer an efficient way to allot names to numeric constants. I placed them here because some of them are used in both parts of the code. Their point is to let you make the code work differently without digging into the code itself. You just twiddle the knobs, as it were.
If you followed me thus far you may have noticed that there is no code for you to press a button or F5 so that it runs. The ADODB connection is called by the event procedure and the event procedure is triggered by the changes the user makes on the worksheet. The functionality is simple. When the user makes a change the macro looks for the combination of Department and Course and marks the cells if it isn't found. If the user thereupon changes the entry the process is repeated and the highlight may be removed. However, no change is triggered by a subsequent change in the Records. Such changes should be driven by change events in the Records workbook.
The more automation you want the more precise must be the setup. Start by copying part 2, here following, to a standard code module called ADO_Conn (if you like). Observe that the name avoids a space by substituting it with an underscore. This rule will also apply to the names of the two columns in Records that will be accessed. I renamed them as "Dept_ID" and "Course_ID". You can use different names, shift the columns to other locations, but you may not include any blanks in these names, nor should you change their sequence in the one place in the code where they are mentioned. If the names in the code differ from those in the workbook the workbook will still work but the code won't. Here is part 2.
Option Explicit
Enum Nwt ' worksheet Target ("Handbook" = ThisWorkbook)
' 082
NwtFirstDataRow = 2 ' change to suit
NwtDept = 3 ' Columns: 3 = C
NwtCourse ' if no value is assigned, [preceding + 1]
End Enum
Enum Nct ' search criteria: TriggerRng()
' 082
NctDept = 1 ' do not change (!!)
NctCourse
End Enum
Function HasMatch(Crits As Variant, _
SrcFile As String, _
SrcTab As String, _
SrcClms As String) As Boolean
' 082
Dim ConSpec As String
Dim Conn As Object ' late-bound ADODB.Connection
Dim Rs As Object ' late-bound ADODB.Recordset
Dim Query As String ' SQL query
Dim Sp() As String ' array of Clms
On Error GoTo ErrExit
' Create the record set and ADODB connection
Set Rs = CreateObject("ADODB.Recordset")
Set Conn = CreateObject("ADODB.Connection")
With Conn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & SrcFile & ";" & _
"Extended Properties=""Excel 12.0;" & _
"HDR=Yes;" & _
"IMEX=1"";"
.Open
End With
' create the SQL query string
Sp = Split("," & SrcClms, ",") ' first column index = 1
Query = "SELECT " & Sp(NctDept) & _
" FROM [" & SrcTab$ & "$]" & _
" WHERE " & Sp(NctDept) & " = " & Crits(1, NctDept) & _
" AND " & Sp(NctCourse) & " = " & Crits(1, NctCourse) & ";"
Rs.Open Query, Conn, 0, 1, 1 ' execute the query
' evaluate the retrieved recordset
HasMatch = Rs.EOF
ErrExit:
If Err Then
MsgBox "An error occurred during data retrieval:-" & vbCr & _
Err.Description, _
vbExclamation, "Error No. " & Err.Number
End If
Err.Clear
End Function
There are 2 sets of Department/Course ID numbers. The columns used in the Handbook sheet and an ID for each that the program itself uses. You can move the columns to where you want them. They don't have to stay together but I think the Department column must stay to the left of the Course column. Just change the numbers assigned to the names and the program will find them. You can also change the FirstDataRow for the Handbook sheet. But the the Records sheet only one header row is allowed - fixed, therefore not adjustable.
Here is the first part of the code. Paste it to the code module of the worksheet in Handbook where you want your entries checked.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' 082
' name the source workbook with its complete path (change to match your facts)
Const SrcPath As String = "D:\PVT Archive\Class 1\1-2020 (Jan 2022)\" ' end on \
Const SrcFile As String = "082 STO 200829 Records.xlsm"
Const SrcTab As String = "Records"
' match the column names in the workbook with the names used here.
' If they are changed assign names without spaces in them and
' maintain their logical sequence.
Const SrcClms As String = "Dept_ID,Course_ID"
Dim Matched As Boolean ' apply no highlight if True
Dim TriggerRng As Range ' the range that triggers action
Dim Crits As Variant ' search criteria
' don't react to changes in more than one cell
If Target.CountLarge > 1 Then Exit Sub
Set TriggerRng = Range(Cells(NwtFirstDataRow, NwtDept), _
Cells(Rows.Count, NwtDept).End(xlUp))
Set TriggerRng = Application.Union(TriggerRng, TriggerRng.Offset(0, NwtCourse - NwtDept))
If Not Intersect(Target, TriggerRng) Is Nothing Then
With Target
Set TriggerRng = Application.Union(Cells(.Row, NwtDept), _
Cells(.Row, NwtCourse))
Crits = TriggerRng.Value
If WorksheetFunction.CountA(TriggerRng) < 2 Then Exit Sub
End With
If Dir(SrcPath & SrcFile) = "" Then
' check if referenced workbook exists at the specified location
MsgBox "The workbook to be referenced" & vbCr & _
SrcFile & vbCr & "can't be found at" & vbCr & _
SrcPath & ".", _
vbInformation, "Data source not accessible"
Exit Sub
End If
With TriggerRng
If HasMatch(Crits, SrcPath & SrcFile, SrcTab, SrcClms) Then
.Interior.Color = vbYellow
Else
.Interior.Pattern = xlNone
End If
End With
End If
End Sub
There are 4 constants to be set by you. This must be done very precisely. You may also like to review the text of the messages, and I shall not mind if you improve them to better suit your needs. The rest of the code is intended to stay untouched. Whatever modifications you want must be done by using the parameters, unless you find flaws in the functionality, which I hope you will not.
SrcPath holds the path to the workbook Records. It must end on a backslash "". SrcFile holds the name of that file. This program doesn't mind if it's open or closed. SrcTab holds the name of the worksheet. I suspect that having a space in it might cause a problem. So, better avoid one. Finally, SrcClms gives the names of the column captions of the two columns in Records that we are concerned with here. Keep them aligned with what they really are, keep them free from blanks and keep their sequence aligned with the Enum Nct. Mind that ADO (ActiveX Data Object, btw) doesn't allow you to have more than 1 header row in the Records sheet. Not that it should make any difference in this particular application if there were more, unless the header rows contain potential matches. However, avoid having merged cells on that sheet anywhere.
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.
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.
I am trying to import a range of cells from a closed workbook.
I use the external reference link built into Excel:
='F:\UGR\JOB DATA SHEET\[JOB SHEETS 1-500.xlsx]JobNumber'!B4
='F:\UGR\JOB DATA SHEET\[JOB SHEETS 1-500.xlsx]JobNumber'!B5
...
Going down the column from B4:B23 and replicating that for columns B-Z.
This works if the sheet name doesn't change. But that file contains sheets for Jobs 1 - 500, each on their own sheet. I am trying to pull those columns of data for whatever JobNumber gets entered into cell "B7". So ideally it would look like this:
='F:\UGR\JOB DATA SHEET\[JOB SHEETS 1-500.xlsx]&B7&'!B4
='F:\UGR\JOB DATA SHEET\[JOB SHEETS 1-500.xlsx]&B7&'!B5
...
Etc.
I know this won't work without the Indirect function, but I need to have the other file open for that to work. This isn't practical given the number of users who are using this file for reference.
I found a macro in VBA that should do what I need, but I can't get it to work. Here is the base macro before I started messing around with it.
Function GetValue(Path, File, Sheet, Ref)
'Retrieves a value from a closed workbook
Dim Arg As String
'Make sure the file exists
If Right(Path, 1) <> "\" Then Path = Path & "\"
If Dir(Path & File) = "" Then
GetValue = "File not Found"
Exit Function
End If
'Create the argument
Arg = "'" & Path & "[" & File & "]" & Sheet & "'!" & Range(Ref.Range("A1").Address(, , xlR1C1))
'Execute XLM macro
GetValue = ExecuteExcel4Macro(Arg)
End Function
Any ideas on how to get it to work, or an alternative work around? I could also temporarily import the sheet to my other file and overwrite it when a new value is entered, thus importing another sheet from the other workbook, but that seems far more complex.
I am using Excel 2013.
UPDATE: I am closer to figuring it out but I cant get it to display anything but #Value errors. My formula looks like this in excel:
=GetValue(H11,H12,B7,B4)
Cell H11 = F:\UGR\JOB DATA SHEET\
Cell H12 = JOB SHEETS 1-500.xlsx
Cell B7 = The input cell where the user enters a JobNumber (aka sheet name).
Cell B4 = B4 (The cell I want to search on the external workbook)
Cell B4 is where I think the error lies. Will this macro be able to tell that it needs to search the external file at cell B4?
I figured it out. The macro cannot be launched from within the workbook itself, it must be done from VBA.
I have a variable called 'attached' that increments each time a certain condition is verified as true. When that condition is true, I'm trying to select a worksheet that was created earlier that has the name "Attachment(x)". 'x' is a number 2,3,4, etc. For instance, a worksheet might have the name "Attachment (3)". In my code, x will be the value that the incrementing variable 'attached' has achieved at that point.
What code will select the worksheet based upon the current value of the variable 'attached'? I declared the variable attached as a variant......Dim attached As Variant.
I just keep getting errors.
Thanks
To select the sheet, do this:
Worksheets("Attachment(" & x & ")").Select
If I understand correctly, your variable (named Attachment(#)) will decide which sheet to use (sheet named Attachment (#))?
I assume you're using some kind of loop that iterates through the #, so you could do something like this
For i = 1 to 100
myAttachment = "Attachment(" & i & ")"
myWorksheet = Worksheets("Attachment (" & i & ")")
next i