Cell Cross-Reference using VBA - vba

I have done some searching and cannot figure out the syntax for what I would like to accomplish. I would like to have a specific, static cell on a Summary sheet automatically update to a dynamic cell on a different worksheet. I started with the following code:
Sheets("Summary").Activate
Range("B1").Select
ActiveCell.FormulaR1C1 = "='Current Billing'!R[46]C"
Range("B2").Select
ActiveCell.FormulaR1C1 = _
"='Current Billing'!R[45]C[3]+'Current Billing'!R[45]C[4]"
After getting some advice on a different topic for this same workbook, I now know .Select is a no-no and should be avoided. I also know that my last row on the "Current Billing" worksheet will change when I copy the code to another workbook for billing that project. This has led to me to modify my code to make it more fool-proof and versatile.
I have figured out how to get the the cell value to insert from my "Current Billing" sheet to my "Summary" sheet. But if something changes on the "Current Billing" sheet, the "Summary" sheet will not automatically update. This is the code I have that sort of works:
Dim ws5 As Worksheet
'ws5 is "Current Billing"
Dim ws6 As Worksheet
'ws6 is "Summary"
Dim LRowB2 As Long
Dim LRowB3 As String
LRowB2 = ws5.Cells(Rows.Count, "B").End(xlUp).Row
LRowB3 = ws5.Cells(LRowB2, "B")
ws6.Cells(1, "B").Formula = LRowB3
I tried these two code sequences, but kept getting errors:
Dim LRowB4 As String
Dim LRowE2 As Long
Dim LRowF2 As Long
LRowB4 = "= & ws5 & ! & LRowB3"
ws6.Cells(2, "B").Formula = "=sum(& LRowE2 &,& LRowF2 &)"
In short, is there a way to mimic the function of the first code procedure, but have the stability and fool-proofness of the second procedure? And, is there a way to integrate a =Sum() formula into the second procedure so I can add two cells from "Current Billing" on the "Summary" page? Thanks for the help!

The syntax of the formula is similar to =SUM('Current Billing'!C9, 'Current Billing'!D9)
This will check for errors, and is a bit more dynamic
Option Explicit
Public Sub UpdateSummary()
Dim wsCB As Worksheet, lrCB As Long, cbCellC As Range, cbCellD As Range
Dim wsSummary As Worksheet, sumCell As Range
Set wsCB = ThisWorkbook.Worksheets("Current Billing")
Set wsSummary = ThisWorkbook.Worksheets("Summary")
lrCB = wsCB.Cells(wsCB.Rows.Count, "C").End(xlUp).Row
'Verify that we have the same last row for col C and D on sheet "Current Billing"
If wsCB.Cells(wsCB.Rows.Count, "D").End(xlUp).Row = lrCB Then
Set cbCellC = wsCB.Cells(lrCB, "C") '"Current Billing".Cell(lr, C)
Set cbCellD = wsCB.Cells(lrCB, "D") '"Current Billing".Cell(lr, D)
Set sumCell = wsSummary.Cells(2, "B") '"Summary".Cell(2, B)
sumCell = "Invalid 'Current Billing' values" 'Default (in case of errors)
'Check "Current Billing" cells for errors (#N/A, #VALUE!, #REF!, #DIV/0!, etc)
If Not IsError(cbCellC) And Not IsError(cbCellD) Then
'Verify that "Current Billing" cells are numbers
If IsNumeric(cbCellC) And IsNumeric(cbCellD) Then
Dim cbC As String, cbD As String
cbC = "'" & wsCB.Name & "'!" & cbCellC.Address(0, 0) 'Current Billing'!C9
cbD = "'" & wsCB.Name & "'!" & cbCellD.Address(0, 0) 'Current Billing'!D9
'Final format: =SUM('Current Billing'!C9, 'Current Billing'!D9)
sumCell.Formula = "=SUM(" & cbC & ", " & cbD & ")" 'Update Summary cell
End If
End If
End If
End Sub
If the formula will contain a division, verify that the divisor is not 0
But if you are using VBA for this, you can simplify the syntax: sumCell = cbCellC + cbCellD
Note:
String variables become more complex when we have to use quotes inside quotes:
Str with ""double quotes"" can be built like this
str = "Str with """"double quotes""""", or
str = "Str with " & Chr(34) & """double quotes""" & Chr(34), or
str = "Str with " & Chr(34) & Chr(34) & "double quotes" & Chr(34) & Chr(34)
This string str = "Str with 'double quotes'" is Str with 'double quotes'

Use the Workbook_SheetChange event. It's an "event" action that is triggered when changes are made anywhere in your workbook. Within the event sub add code to update the value of your hard-coded/static cell with the value in your formula.
The Workbook_SheetChange sub must be in your workbook module (the default module name is "ThisWorkbook").
Here's an example of how you could use it. Just update the sheet names and range addresses for the variables myStaticCell and myFormulaCell.
In your ThisWorkbook Module:
Option Explicit
Public myVar As Variant ' stores last known value of static cell
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
' Define the locations of your formula cell and static cell.
Dim myStaticCell As Range
Dim myFormulaCell As Range
Set myStaticCell = Worksheets("Sheet2").Range("A2")
Set myFormulaCell = Worksheets("Sheet1").Range("A2")
' Ignore changes made directly to the static cell by comparing
' the sheet name and address where the workbook change
' occurred (variables Sh and Target which are built into the
' Workbook_SheetChange event) with the sheet name and address
' of your static cell.
If Sh.Name = myStaticCell.Parent.Name And _
Target.Address = myStaticCell.Address Then Exit Sub
' Save the value to a public variable so it can
' be checked the next time something changes.
myVar = myFormulaCell.Value
' If different, update the static cell with the
' value from your formula.
If myStaticCell <> myVar Then myStaticCell = myFormulaCell.Value
End Sub

Related

vba wildcard search on cell

I'm trying to find something with a wild card search in a cell value. If the value in sheet("FC")Range("I2:I" & LastRowC) - match with the Sheets("Instr"),Range("A130:A190"). means sheet Instr match if few characters match with the other range mentioned above then do something code.
eg in sheet Instr above range a cell value is "Ajith" and In sheet FC above mentioned range one of the cell value is "Aji" the code should identify it.
All the below steps are okay for me except the wild card search through the loop range , please go through the code and range (rename the sheets if necessary as below) and provide an update.
Sub Exception()
Dim mfc As Worksheet
Dim mfp As Worksheet
Dim mfo As Worksheet
Dim instr As Worksheet
Set mfc = Sheets("FC")
Set mfp = Sheets("FP")
Set mfo = Sheets("OSLR")
Set inst = Sheets("Instr")
Dim irng As Range
Dim icel As Range
Set irng = inst.Range("A130:A190")
Dim LastRowC As Long
LastRowC = mfc.Cells(Rows.Count, 1).End(xlUp).Row
Dim fcphr As Range
Dim fcphc As Range
Set fcphr = mfc.Range("I2:I" & LastRowC)
For Each icel In irng.Rows
For Each fcphc In fcphr.Rows
If icel.Value = "" Then
Exit For
End If
If fcphc.Value = "" Then
Exit For
End If
If fcphc.Value = icel.Value Then
msgbox fcphc
msgbox icel
'***(i need a wild card search for the above step)***
End If
Next fcphc
Next icel
End Sub
You could use the Like operator. For example:
If fcphc.Value Like "*" & icel.Value & "*" Then
If you wanted the comparison to work both ways:
If _
fcphc.Value Like "*" & icel.Value & "*" Or _
icel.Value Like "*" & fcphc.Value & "*" _
Then

VBA Worksheet Sub Create Named Range in Another Worksheet

I have a private sub that needs to create named ranges within another worksheet. It needs to stay a worksheet function, as it is a Worksheet_Change sub. I have successfully been able to set a range variable equal to a range on another sheet with this line:
Set rng2 = Sheets("Lists").Range(Sheets("Lists").Cells(2, Col), Sheets("Lists").Cells(Unique, Col))
However, when I put rng2 into the other portion of my code, it simply refers to the correct range within the Active Sheet.
Here is what I have tried:
ActiveWorkbook.Names.Add Name:="Level" & Col, RefersTo:= _
"= " & Sheets("Lists").Range(Sheets("Lists").Cells(2, Col), Sheets("Lists").Cells(Unique, Col)).Address & ""
and:
ActiveWorkbook.Names.Add Name:="Level" & Col, RefersTo:= _
"=" & rng2.Address & ""
The bottom function works when it is within a module stored inside the workbook as a whole, but again, does not work within a worksheet sub.
I have also tried Sheets("Lists").rng2.Address in the bottom attempt.
To have the address include the sheet's name, you have to set the external parameter:
rng2.address(external:=True)
Your RefersTo string needs to be something like "=Lists!A1". So all it's missing is the reference to the lists worksheet.
Try something like this:
Dim wsLists As Worksheet
Set wsLists = ThisWorkbook.Worksheets("Lists")
With wsLists
Set rng2 = .Range(.Cells(2, Col), .Cells(Unique, Col))
ThisWorkbook.Names.Add Name:="Level" & Col, RefersTo:="=" & rng2.Address(external:=True)
End With

Macro to iterate through all rows in all columns of a worksheet and compare a cell to copy the row to a text file

I am writing a VB excel macro which iterates through all the worksheets in a opened workbook.
In each worksheet, there are few "Y" stored in column K. I want to compare the values in K column to "Y" and if it is equal, then the columns A,B,D,H of the same row should get insert to a text file in the format tab delimited.
This is the code which i have tried. In this code i am inserting only column K to the text file. But I also want to insert the values of A,B,D,H and K column values to the text fil separated by a tab.
Please help me out.
Code which I tried is
Sub Button3_Click()
Dim fso, myfile, I As Integer, mycount As String, x As String
Dim curCell As Range
Dim sh As Worksheet
x = "Y"
Set fso = CreateObject("Scripting.FileSystemObject")
Set myfile = fso.CreateTextFile("d:\RECP_IMP_COLUMNS.txt", True)
myfile.WriteLine ("Work Sheet Names are as follows")
For Each sh In ActiveWorkbook.Worksheets
For Each curCell In Sheet4.Range("K1:K300").Cells
If (curCell.Value = x) Then
myfile.WriteLine (curCell)
End If
Next curCell
Next
myfile.Close
End Sub
You can use the .Offset() method of Range object and vbTab constant to create tab delimited spaces
The .Offset(rows, columns) takes 2 parameters. Number of rows up or down of the current cell. To navigate up you give it a negative value to go down a positive one. Same applies to columns except that you use a negative to move left and a positive to go right.
vbTab creates a tab between two objects in the context of
"this is " & vbtab & " tab delimited"
I have modified your comparison method a bit and added the StrComp() function. It is more reliable and you can specify the comparison parameters.
Please see how to compare strings in VBA for more details about the = operator in string comparison.
I have changed your Sheet4.Range("K1:K300") as this is a bit misleading and wrong. You iterate in a loop through all sheets but only used Sheet4 for each loop. So all your results printed in the text file would be duplicate of Sheet4 columns K.
the _ underscore at the end of lines splits code into more lines so you can squeeze in more and make code more readable and clear.
Sub Button3_Click()
Dim fso, myfile, I As Integer, mycount As String, x As String
Dim curCell As Range
Dim sh As Worksheet
x = "Y"
Set fso = CreateObject("Scripting.FileSystemObject")
Set myfile = fso.CreateTextFile("d:\RECP_IMP_COLUMNS.txt", True)
myfile.WriteLine ("Work Sheet Names are as follows")
For Each sh In ActiveWorkbook.Worksheets
For Each curCell In sh.Range("K1:K300").Cells
If StrComp(curCell, x, vbTextCompare) = 0 Then
myfile.WriteLine curCell.Offset(0, -10) & vbTab & _
curCell.Offset(0, -9) & vbTab & _
curCell.Offset(0, -7) & vbTab & _
curCell.Offset(0, -3) & vbTab & _
curCell
End If
Next curCell
Next
myfile.Close
End Sub
Hope this helps :)

Using VLookup in a macro

I'm new to VBA but I'm hooked! I've created a workbook that tracks overtime in 2 week blocks with one 2-week block per worksheet. The macro I'm trying to debug is designed to carry any changes made in a worksheet over to following worksheets. The trick is that the data in one row may be in a different row in following worksheets so I trying to use VLookup in a macro to keep it accurate.
Sub CarryForward()
Dim Answer As String
Answer = MsgBox("This should only be used for a PERMANENT crew change." & vbNewLine & "If you are adding a new person to the list," & vbNewLine & "please use the Re-Sort function." & vbNewLine & "Do you want to continue?", vbExclamation + vbYesNo, "Caution!")
If Answer = vbNo Then
Exit Sub
End If
Application.ScreenUpdating = False
Dim ActiveWorksheet As String
ActiveWorksheet = ActiveSheet.Name
For i = (ActiveSheet.Index + 1) To Sheets("DATA").Index - 1
For x = 5 To 25
Dim a As String
Dim b As String
a = "B" & x
b = "C" & x
ActiveSheet.Range(b).Value = Application.WorksheetFunction.VLookup(a, Sheets(ActiveWorksheet).Range("B5:C25"), 2, False)
Next x
Range("A3").Select
Next i
Sheets(ActiveWorksheet).Select
Application.CutCopyMode = False
Range("A3").Select
Application.ScreenUpdating = True
End Sub
I'm pretty sure it's just a syntax error in the VLookup line of code. A lot of the help posted comes close to what I'm looking for, it just doesn't get me over the finish line.
Any help would be appreciated!
It is a little unclear what you are trying to do, but reading between the lines I think
you want to lookup the value contained in cell named by a?
and put the result on sheet index i?
Also, there is a lot of opportunity to improve your code: see imbedded comments below
Sub CarryForward()
Dim Answer As VbMsgBoxResult ' <-- Correct Datatype
Answer = MsgBox("This should only be used for a PERMANENT crew change." & vbNewLine & _
"If you are adding a new person to the list," & vbNewLine & _
"please use the Re-Sort function." & vbNewLine & _
"Do you want to continue?", _
vbExclamation + vbYesNo, "Caution!")
If Answer = vbNo Then
Exit Sub
End If
Application.ScreenUpdating = False
' Dim ActiveWorksheet As String <-- Don't need this
'ActiveWorksheet = ActiveSheet.Name <-- use object variables
Dim wbActive As Workbook ' <-- don't select, use variables for sheet objects
Dim shActive As Worksheet
Set wbActive = ActiveWorkbook
Set shActive = ActiveSheet
'Dim a As String ' <-- no point in putting these inside the loop in VBA. And don't need these anyway
'Dim b As String
Dim SearchRange As Range
Set SearchRange = shActive.Range("B5:C25") ' <-- Use variable to hold range
Dim shDest As Worksheet
Dim i As Long, x As Long '<-- dim all your variables
For i = (shActive.Index + 1) To wbActive.Worksheets("DATA").Index - 1 ' <-- qualify references
Set shDest = wbActive.Sheets(i)
For x = 5 To 25
'a = "B" & x <-- no need to create cell names
'b = "C" & x
' I think you want to lookup the value contained in cell named by a?
' and put the result on sheet index i?
' Note: if value is not found, this will return N/A. Add an error handler
wbActive.Sheets(i).Cells(x, 3).Value = Application.VLookup(shActive.Cells(x, 2).Value, SearchRange, 2, False)
Next x
'Range("A3").Select
Next i
'Sheets(ActiveWorksheet).Select ,-- don't need these
'Application.CutCopyMode = False
'Range("A3").Select
Application.ScreenUpdating = True
End Sub
I suspect you would want to replace the vlookup statement to be something like
Application.WorksheetFunction.VLookup(ActiveWorksheet.Range(a).value, ActiveWorksheet.Range("B5:C25"), 2, False)
at the moment it looks like you're just doing a vlookup against some strings B5, B6, B7 etc instead of values in those cells

How do I get a range's address including the worksheet name, but not the workbook name, in Excel VBA?

If I have a Range object--for example, let's say it refers to cell A1 on a worksheet called Book1. So I know that calling Address() will get me a simple local reference: $A$1. I know it can also be called as Address(External:=True) to get a reference including the workbook name and worksheet name: [Book1]Sheet1!$A$1.
What I want is to get an address including the sheet name, but not the book name. I really don't want to call Address(External:=True) and try to strip out the workbook name myself with string functions. Is there any call I can make on the range to get Sheet1!$A$1?
Only way I can think of is to concatenate the worksheet name with the cell reference, as follows:
Dim cell As Range
Dim cellAddress As String
Set cell = ThisWorkbook.Worksheets(1).Cells(1, 1)
cellAddress = cell.Parent.Name & "!" & cell.Address(External:=False)
EDIT:
Modify last line to :
cellAddress = "'" & cell.Parent.Name & "'!" & cell.Address(External:=False)
if you want it to work even if there are spaces or other funny characters in the sheet name.
Split(cell.address(External:=True), "]")(1)
Ben is right. I also can't think of any way to do this. I'd suggest either the method Ben recommends, or the following to strip the Workbook name off.
Dim cell As Range
Dim address As String
Set cell = Worksheets(1).Cells.Range("A1")
address = cell.address(External:=True)
address = Right(address, Len(address) - InStr(1, address, "]"))
The Address() worksheet function does exactly that. As it's not available through Application.WorksheetFunction, I came up with a solution using the Evaluate() method.
This solution let Excel deals with spaces and other funny characters in the sheet name, which is a nice advantage over the previous answers.
Example:
Evaluate("ADDRESS(" & rng.Row & "," & rng.Column & ",1,1,""" & _
rng.Worksheet.Name & """)")
returns exactly "Sheet1!$A$1", with a Range object named rng referring the A1 cell in the Sheet1 worksheet.
This solution returns only the address of the first cell of a range, not the address of the whole range ("Sheet1!$A$1" vs "Sheet1!$A$1:$B$2"). So I use it in a custom function:
Public Function AddressEx(rng As Range) As String
Dim strTmp As String
strTmp = Evaluate("ADDRESS(" & rng.Row & "," & _
rng.Column & ",1,1,""" & rng.Worksheet.Name & """)")
If (rng.Count > 1) Then
strTmp = strTmp & ":" & rng.Cells(rng.Count) _
.Address(RowAbsolute:=True, ColumnAbsolute:=True)
End If
AddressEx = strTmp
End Function
The full documentation of the Address() worksheet function is available on the Office website: https://support.office.com/en-us/article/ADDRESS-function-D0C26C0D-3991-446B-8DE4-AB46431D4F89
I found the following worked for me in a user defined function I created. I concatenated the cell range reference and worksheet name as a string and then used in an Evaluate statement (I was using Evaluate on Sumproduct).
For example:
Function SumRange(RangeName as range)
Dim strCellRef, strSheetName, strRngName As String
strCellRef = RangeName.Address
strSheetName = RangeName.Worksheet.Name & "!"
strRngName = strSheetName & strCellRef
Then refer to strRngName in the rest of your code.
You may need to write code that handles a range with multiple areas, which this does:
Public Function GetAddressWithSheetname(Range As Range, Optional blnBuildAddressForNamedRangeValue As Boolean = False) As String
Const Seperator As String = ","
Dim WorksheetName As String
Dim TheAddress As String
Dim Areas As Areas
Dim Area As Range
WorksheetName = "'" & Range.Worksheet.Name & "'"
For Each Area In Range.Areas
' ='Sheet 1'!$H$8:$H$15,'Sheet 1'!$C$12:$J$12
TheAddress = TheAddress & WorksheetName & "!" & Area.Address(External:=False) & Seperator
Next Area
GetAddressWithSheetname = Left(TheAddress, Len(TheAddress) - Len(Seperator))
If blnBuildAddressForNamedRangeValue Then
GetAddressWithSheetname = "=" & GetAddressWithSheetname
End If
End Function
rngYourRange.Address(,,,TRUE)
Shows External Address, Full Address
The best way I found to do this is to use the following code:
Dim SelectedCell As String
'This message Box allows you to select any cell on any sheet and it will return it in the format of =worksheetname!$A$X" where X is any number.
SelectedCell = Application.InputBox("Select a Cell on ANY sheet in your workbook", "Bookmark", Type:=8).Address(External:=True)
SelectedCell = "=" & "'" & Right(SelectedCell, Len(SelectedCell) - Len("[" & ActiveWorkbook.Name & "]") - 1)
'Be sure to modify Sheet1.Cells(1,1) with the Sheet and cell you want to use as the destination. I'd recommend using the Sheets VBA name.
Sheet1.Cells(1, 1).Value = SelectedCell
How it works;
By Clicking on the desired cell when the message box appears. The string from "Address(External:=True)" (i.e ['[Code Sheet.xlsb]Settings'!$A$1) is then modified to remove the full name of the worksheet([Code Sheet.xlsb]).
Using the previous example it does this by taking the "Len" of the full length of;
[Code Sheet.xlsb]Settings'!$A$1 and subtracts it with the Len of ([Code Sheet.xlsb] -1). leaving you with Settings'!$A$1.
SelectedCell = "=" & "'" & Right(SelectedCell, Len(SelectedCell) - Len("[" & ActiveWorkbook.Name & "]") - 1)
The Code then its and "='" to insure that it will be seen as a Formula (='Settings'!$A$1).
Im not sure if it is only on Excel on IOS but for some reason you will get an Error Code if you add the "='" in any other way than "=" & "'" as seen bellow.
SelectedCell = "=" & "'" & Right....
From here all you need is to make the program in the Sheet and cell you want your new formula in.
Sheet1.Cells(1, 1).Value = SelectedCell
By Opening a new Workbook the full Code above will work as is.
This Code is Especially useful as changing the name of the workbook or the name of the sheet that you are selecting from in the message box will not result in bugs later on.
Thanks Everyone in the Forum before today I was not aware that External=True was a thing, it will make my coding a lot easier. Hope this can also help someone some day.
Why not just return the worksheet name with
address = cell.Worksheet.Name
then you can concatenate the address back on like this
address = cell.Worksheet.Name & "!" & cell.Address
Dim rg As Range
Set rg = Range("A1:E10")
Dim i As Integer
For i = 1 To rg.Rows.Count
For j = 1 To rg.Columns.Count
rg.Cells(i, j).Value = rg.Cells(i, j).Address(False, False)
Next
Next
For confused old me a range
.Address(False, False, , True)
seems to give in format TheSheet!B4:K9
If it does not why the criteria .. avoid Str functons
will probably only take less a millisecond and use 153 already used electrons
about 0.3 Microsec
RaAdd=mid(RaAdd,instr(raadd,"]") +1)
or
'about 1.7 microsec
RaAdd= split(radd,"]")(1)
[edit on 2009-04-21]
As Micah pointed out, this only works when you have named that
particular range (hence .Name anyone?) Yeah, oops!
[/edit]
A little late to the party, I know, but in case anyone else catches this in a google search (as I just did), you could also try the following:
Dim cell as Range
Dim address as String
Set cell = Sheet1.Range("A1")
address = cell.Name
This should return the full address, something like "=Sheet1!$A$1".
Assuming you don't want the equal sign, you can strip it off with a Replace function:
address = Replace(address, "=", "")