VBA: Limiting cell range with functions - vba

How do you write a function in VBA that lets the user enter a range as a parameter, and set the upper/lower bounds for that range (in case they enter a whole column)?
I have a function that looks at a cell and sees if it contains any words listed in a glossary (I just allow the user to select a column (range) that is the list of glossary terms. I currently use a for each cell in range loop to go through the range, but I don't want to waste steps going through ALL the cells in column A, even if I am checking first if Len(cell.value) <> 0.
I am guessing it's done with a select statement, but I'm now sure how to do that to a range that was passed as a parameter (I call it cell_range right now).
Any help would be greatly appreciated!
Added Info:
The data type of the range is of type string. It's a list of English words (glossary terms) and I am writing a function that will look at a cell and see if it includes any of the terms from the glossary. If it does, the code returns the glossary term plus the offset cell to the right (the translated term).
EDIT (06.20.11)
Finalized code thanks to experimentation and suggestions below. It takes a cell and looks for any glossary terms in it. It returns the list of terms, plus the translated terms (second column in glossary).
Function FindTerm(ByVal text As String, ByVal term_list As range) As String
Static glossary As Variant
Dim result As String
Dim i As Long
glossary = range(term_list.Cells(1, 1), term_list.Cells(1, 2).End(xlDown))
For i = 1 To UBound(glossary)
If InStr(text, glossary(i, 1)) <> 0 Then
result = (glossary(i, 1) & " = ") & (glossary(i, 2) & vbLf) & result
End If
Next
If result <> vbNullString Then
result = Left$(result, (Len(result) - 1))
End If
FindTerm = result
End Function

Why not limit your loop to the filled cells efficiently?
For Each c In Range("a:a").SpecialCells(xlCellTypeConstants)
....
Next c

To answer the direct question, you can't restrict what is passed as a parameter, but you can derive a new range from a passed range.
That said, looping through a range is very slow. There are may alternative methods:
Query based methods, as suggested by Remou
Copy the range to a variant array and loop through that
Dim vDat as variant
vDat = cell_range
vDat is now a two dimensional array
Use the built in search function Find
cell_range.Find ...
Use Application.WorksheetFunction.Match (and/or .Index .VLookup)
Which one best suits depend on the specifics of your case
Edit
Demo of the variant array approach
Function Demo(Glossary As Range, search_cell As Range) As String
Dim aGlossary As Variant
Dim aSearch() As String
Dim i As Long, j As Long
Dim FoundList As New Collection
Dim result As String
Dim r As Range
' put data into array
aGlossary = Range(Glossary.Cells(1, 1), Glossary.Cells(1, 1).End(xlDown))
' assuming words in search cell are space delimited
aSearch = Split(search_cell.Value, " ")
'search for each word from search_cell in Glossary
For i = LBound(aSearch) To UBound(aSearch)
For j = LBound(aGlossary, 1) To UBound(aGlossary, 1)
If aSearch(i) = aGlossary(j, 1) Then
' Add to found list
FoundList.Add aSearch(i), aSearch(i)
Exit For
End If
Next
Next
'return list as comma seperated list
result = ""
For i = 1 To FoundList.Count
result = result & "," & FoundList.Item(i)
Next
Demo = Mid(result, 2)
End Function

If you are confident there are no gaps:
''Last cell in column A, or first gap
oSheet.Range("a1").End(xlDown).Select
''Or last used cell in sheet - this is not very reliable, but
''may suit if the sheet is not much edited
Set r1 = .Cells.SpecialCells(xlCellTypeLastCell)
Otherwise, you may need http://support.microsoft.com/kb/142526 to determine the last cell.
EDIT Some notes on selecting the column
Dim r As Range
Dim r1 As Range
Dim r2 As Range
Set r = Application.Selection
Set r1 = r.Cells(1, 1)
r1.Select
Set r2 = r1.End(xlDown)
If r2.Row > Sheet1.Cells.SpecialCells(xlCellTypeLastCell).Row Then
MsgBox "Problem"
Else
Debug.Print r1.Address
Debug.Print r2.Address
End If
Set r = Range(r1, r2)
Debug.Print r.Address
However, you can also use ADO with Excel, but whether it will work for you depends on what you want to do:
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim s As String
Dim i As Integer, j As Integer
Dim a As String
''It does not matter if the user has selected a whole column,
''only the data range will be picked up, nor does it matter if the
''user has selected several cells, except when it comes to the HDR
''I guess you could set HDR = Yes or No accordingly.
''One cell is slightly more difficult, but for one cell you would
''not need anything like this palaver.
a = Replace(Application.Selection.Address, "$", "")
''This is not the best way to refer to the workbook
''you want, but it is very convenient for notes
''It is probably best to use the name of the workbook.
strFile = ActiveWorkbook.FullName
''Note that if HDR=No, F1,F2 etc are used for column names,
''if HDR=Yes, the names in the first row of the range
''can be used.
''This is the Jet 4 connection string, you can get more
''here : http://www.connectionstrings.com/excel
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;"";"
''Late binding, so no reference is needed
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
''So this is not very interesting:
strSQL = "SELECT * " _
& "FROM [Sheet1$" & a & "]"
''But with a little work, you could end up with:
strSQL = "SELECT Gloss " _
& "FROM [Sheet1$A:A] " _
& "WHERE Gloss Like '%" & WordToFind & "%'"
''It is case sensitive, so you might prefer:
strSQL = "SELECT Gloss " _
& "FROM [Sheet1$A:A] " _
& "WHERE UCase(Gloss) Like '%" & UCase(WordToFind) & "%'"
rs.Open strSQL, cn, 3, 3
''Pick a suitable empty worksheet for the results
''if you want to write out the recordset
Worksheets("Sheet3").Cells(2, 1).CopyFromRecordset rs
''Tidy up
rs.Close
Set rs=Nothing
cn.Close
Set cn=Nothing

Related

vba efficient loop to compare and do copy and paste for more than 10,000 values

I'd like to compare 2 time values and if they match, then paste the temperature value at that time, if there is one measurement missing at a particular point in time, then assign 0. This code currently works for 1000 values (takes less than 1 min), however for 10,000 values is takes more than an hour. How could the looping time be reduced?
Sub findMatching()
Dim CurrentLine As Integer, CurrentLine2 As Integer, CurrentLine3 As Integer
Dim MaxRows As Integer, MaxRows2 As Integer
MaxRows = 1000
MaxRows2 = 1000
CurrentLine = 1
For CurrentLine = 1 To MaxRows '-- Loop in A column (read data)
For CurrentLine2 = 1 To MaxRows2 '-- Loop in D column (compare data)
If Sheets(1).Cells(CurrentLine, 1) = Sheets(1).Cells(CurrentLine2,4) Then
'-- copying matching data
Sheets(1).Cells(CurrentLine, 2) = Sheets(1).Cells(CurrentLine2, 5)
CurrentLine = CurrentLine + 1
ElseIf Sheets(1).Cells(CurrentLine, 1) <> Sheets(1).Cells(CurrentLine2,4) Then
Sheets(1).Cells(CurrentLine, 2) = 0
End If
Next CurrentLine2
Next CurrentLine
End Sub
Code below relies on you being able to access the Scripting.Dictionary object. I use late binding, so you shouldn't need to add a reference.
You said that Range.Resize is killing you. Not too sure why that is, but I use it again in the code below. If you have performance issues, let me know.
Option Explicit
Private Sub findFirstMatching()
' Declared two constants because OP had done it that way in their post.
' Depending on use case, could get rid of second and just use the one
' But having two allows you to change one without the other.
Const READ_ROW_COUNT As Long = 10000 ' Used for columns A, B
Const COMPARISON_ROW_COUNT As Long = 10000 ' Used for columns D, E
' Change sheet name below to wherever the data is. I assume Sheet1 '
With ThisWorkbook.Worksheets("Sheet1")
Dim columnA() As Variant
columnA = .Range("A1").Resize(READ_ROW_COUNT, 1).Value2
Dim columnD() As Variant
columnD = .Range("D1").Resize(COMPARISON_ROW_COUNT, 1).Value2
Dim columnE() As Variant
columnE = .Range("E1").Resize(COMPARISON_ROW_COUNT, 1).Value2
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim rowIndex As Long
' Fill dictionary (array values as dictionary's keys, row numbers as dictionary's corresponding values)
' If there are duplicates in column D, the dictionary will only contain/return the row number of the FIRST instance/match
For rowIndex = LBound(columnD, 1) To UBound(columnD, 1)
If Not dict.Exists(columnD(rowIndex, 1)) Then
dict.Add columnD(rowIndex, 1), rowIndex
End If
Next rowIndex
Dim outputArray() As Variant
ReDim outputArray(1 To READ_ROW_COUNT, 1 To 1)
Dim rowIndexOfFirstMatch As Long
' Now loop through column A's values and check if it exists in dict
For rowIndex = LBound(columnA, 1) To UBound(columnA, 1)
If dict.Exists(columnA(rowIndex, 1)) Then
rowIndexOfFirstMatch = dict.Item(columnA(rowIndex, 1))
outputArray(rowIndex, 1) = columnE(rowIndexOfFirstMatch, 1)
Else
outputArray(rowIndex, 1) = "#N/A" ' Change to zero if needed.
End If
Next rowIndex
.Range("B1").Resize(READ_ROW_COUNT, 1) = outputArray
End With
End Sub
I tested the code on some dummy data that I generated on my end and to me it seems like the code should do what you've described (for each value in column A, column B in my output contains either #N/A or the value in column E if a match was found). If it still doesn't, let me know why/what's wrong.
Consider SQL if using Excel for PC as the Office application can interface to the JET/ACE SQL Engine (Windows .DLL files). Essentially, you need a conditional calculation across columns which can be handled with IIF (counterpart to ANSI SQL's CASE). For this set-based operation, 10,000 records is very quick to run. No loops needed for this solution.
Below assumes:
You run Excel 2007+ on PC with ODBC/OLEDB drivers installed.
Data begins at A1 with named columns. Ranges and fields can be changed as needed. Adjust columns and sheet name in SQL, leaving brackets [] and $)
An empty sheet named "RESULTS" exists.
SQL (embedded in VBA)
SELECT t.*, IIF(t.[TimeValue1] = t.[TimeValue2], t.[TemperatureValue], 0) As NewColumn
FROM [SheetName$] t
VBA
Sub RunSQL()
On Error GoTo ErrHandle
Dim conn As Object, rst As Object
Dim strConnection As String, strSQL As String
Dim i As Integer, fld As Object
Set conn = CreateObject("ADODB.Connection")
Set rst = CreateObject("ADODB.Recordset")
' ODBC AND OLEDB CONNECTIONS
' strConnection = "DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" _
' & "DBQ=" & ThisWorkbook.FullName & ";"
strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" _
& "Data Source='" & ThisWorkbook.FullName & "';" _
& "Extended Properties=""Excel 12.0 Xml;HDR=YES;"";"
strSQL = "SELECT t.*, IIF(t.timeValue1 = t.timeValue2, t.Temperaturevalue, 0) As NewColumn" _
& " FROM [SheetName$] t;"
' OPEN CONNECTION
conn.Open strConnection
rst.Open strSQL, conn
With ThisWorkbook.Worksheets("RESULTS")
' COLUMNS
For i = 1 To rst.Fields.Count
.Cells(1, i) = rst.Fields(i - 1).Name
Next i
' DATA
.Range("A2").CopyFromRecordset rst
End With
rst.Close: conn.Close
MsgBox "Successfully ran SQL query!", vbInformation
ExitHandle:
Set rst = Nothing: Set conn = Nothing
Exit Sub
ErrHandle:
MsgBox Err.Number & " - " & Err.Description, vbCritical, "RUNTIME ERROR"
Resume ExitHandle
End Sub

How can I go through all the formulas and array formulas of a worksheet without repeating each array formula many times?

I would like to write a VBA function, which outputs a list of all the single formulas and array formulas of a worksheet. I want an array formula for a range to be printed for only one time.
If I go through all the UsedRange.Cells as follows, it will print each array formula for many times, because it covers several cells, that is not what I want.
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
Print #1, St
Next
Does anyone have a good idea to avoid this?
You basically need to keep track of what you've already seen. The easy way to do that is to use the Union and Intersect methods that Excel supplies, along with the CurrentArray property of Range.
I just typed this in, so I'm not claiming that it's exhaustive or bug-free, but it demonstrates the basic idea:
Public Sub debugPrintFormulas()
Dim checked As Range
Dim c As Range
For Each c In Application.ActiveSheet.UsedRange
If Not alreadyChecked_(checked, c) Then
If c.HasArray Then
Debug.Print c.CurrentArray.Address, c.FormulaArray
Set checked = accumCheckedCells_(checked, c.CurrentArray)
ElseIf c.HasFormula Then
Debug.Print c.Address, c.Formula
Set checked = accumCheckedCells_(checked, c)
End If
End If
Next c
End Sub
Private Function alreadyChecked_(checked As Range, toCheck As Range) As Boolean
If checked Is Nothing Then
alreadyChecked_ = False
Else
alreadyChecked_ = Not (Application.Intersect(checked, toCheck) Is Nothing)
End If
End Function
Private Function accumCheckedCells_(checked As Range, toCheck As Range) As Range
If checked Is Nothing Then
Set accumCheckedCells_ = toCheck
Else
Set accumCheckedCells_ = Application.Union(checked, toCheck)
End If
End Function
The following code produces output like:
$B$7 -> =SUM(B3:B6)
$B$10 -> =AVERAGE(B3:B6)
$D$10:$D$13 -> =D5:D8
$F$14:$I$14 -> =TRANSPOSE(D5:D8)
I'm using a collection but it could equally well be a string.
Sub GetFormulas()
Dim ws As Worksheet
Dim coll As New Collection
Dim rngFormulas As Range
Dim rng As Range
Dim iter As Variant
Set ws = ActiveSheet
On Error Resume Next
Set rngFormulas = ws.Range("A1").SpecialCells(xlCellTypeFormulas)
If rngFormulas Is Nothing Then Exit Sub 'no formulas
For Each rng In rngFormulas
If rng.HasArray Then
If rng.CurrentArray.Range("A1").Address = rng.Address Then
coll.Add rng.CurrentArray.Address & " -> " & _
rng.Formula, rng.CurrentArray.Address
End If
Else
coll.Add rng.Address & " -> " & _
rng.Formula, rng.Address
End If
Next rng
For Each iter In coll
Debug.Print iter
'or Print #1, iter
Next iter
On Error GoTo 0 'turn on error handling
End Sub
The main difference is that I am only writing the array formula to the collection if the current cell that is being examined is cell A1 in the CurrentArray; that is, only when it is the first cell of the array's range.
Another difference is that I am only looking at cells that contain formulas using SpecialCells, which will be much more efficient than examining the UsedRange.
The only reliable solution I see for your problem is crosschecking each new formula against the ones already considered to make sure that there is no repetition. Depending upon the amount of information and speed expectations you should rely on different approaches.
If the size is not too important (expected number of records below 1000), you should rely on arrays because is the quickest option and its implementation is quite straightforward. Example:
Dim stored(1000) As String
Dim storedCount As Integer
Sub Inspect()
Open "temp.txt" For Output As 1
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
If(Not alreadyAccounted(St) And storedCount <= 1000) Then
storedCount = storedCount + 1
stored(storedCount) = St
Print #1, St
End If
Next
Close 1
End Sub
Function alreadyAccounted(curString As String) As Boolean
Dim count As Integer: count = 0
Do While (count < storedCount)
count = count + 1
If (LCase(curString) = LCase(stored(count))) Then
alreadyAccounted = True
Exit Function
End If
Loop
End Function
If the expected number of records is much bigger, I would rely on file storage/checking. Relying on Excel (associating the inspected cells to a new range and looking for matches in it) would be easier but slower (mainly in case of having an important number of cells). Thus, a reliable and quick enough approach (although much slower than the aforementioned array) would be reading the file you are creating (a .txt file, I presume) from alreadyAccounted.

Concatenating Variables Into String to be Set to a Range in VBA

I am having a problem with a particular line of code:
ActiveSheet.Range("A" & rowCount & ":" & Mid(alphabet, totHdrLngth, 1) & belowRowCount)
Where alphabet is a string containing uppercase letters A to Z.
I keep getting the following error:
Run-time error '5':
Invalid Procedure call or argument
I tried creating a String "inRange" and changing the code to this:
inRange = "A" & rowCount & ":" & Mid(alphabet, totHdrLngth, 1) & belowRowCount
curRange = ActiveSheet.Range(inRange)
But that did not help (as I thought it wouldn't). Any suggestions?
Although creating ranges like this is frowned upon in general, the way to do it is with the word SET (like #Gary McGill stated in the comments). Here is an example of how to do this:
Sub test()
Dim alphabet As String
Dim totHrdrLngth As Long
Dim belowRowCount As Long
Dim rowCount As Long
Dim inRange As Range
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
totHrdrLngth = 5
belowRowCount = 10
rowCount = 5
' Gives us A5:E10
Set inRange = Range("A" & rowCount & ":" & range2 & _
Mid$(alphabet, totHrdrLngth, 1) & belowRowCount)
End Sub
You are running this macro in the current range, so there should be no need to specify ActiveSheet.Range. I hope this helps get you toward what you are trying to achieve.
As far as I can tell, you're getting an error because your types don't match up. I imagine rowCount is an integer, as is belowRowCount. If you convert them to strings before concatenating them, you can fix it. str() will convert an integer to a string with a space before it, and LTrim() will remove the space. Try code as below:
Dim sRowCount As String
Dim sBelowRowCount As String
and later
sRowCount = LTrim(Str(RowCount))
sBelowRowCount = LTrim(Str(belowRowCount))
inRange = "A" & sRowCount & ":" & Mid(alphabet, totHdrLngth, 1) & sBelowRowCount
curRange = ActiveSheet.Range(inRange)
Hope this helps.

Searching two columns and returning value from third VBA

A colleague of mine has an excel spreadsheet made up of 3 columns and would like to make searching them easier.
What he has is two cells off to one side that he enters a value from column one into and a value from column two into. What he would like to do is search the spreadsheet for instances where value one and two exist in the same row within column one and two respectively and then return the value from column three that resides in the same row.
For example I have a table like the one shown below, so if he enters B and 2 into the cells BP is then returned to a third cell.
A 1 AP
B 2 BP
C 3 CP
Thanks
Let's create the following function in a new Excel module:
Function FindValue(rng1 As Range, rng2 As Range) As Variant
Dim varVal1 As Variant
Dim varVal2 As Variant
Dim rngTargetA As Range
Dim rngTargetB As Range
Dim lngRowCounter As Long
Dim ws As Worksheet
varVal1 = rng1.Value
varVal2 = rng2.Value
Set ws = ActiveSheet
lngRowCounter = 2
Set rngTargetA = ws.Range("A" & lngRowCounter)
Set rngTargetB = ws.Range("B" & lngRowCounter)
Do While Not IsEmpty(rngTargetA.Value)
If rngTargetA.Value = varVal1 And rngTargetB.Value = varVal2 Then
FindValue = ws.Range("C" & lngRowCounter).Value
Exit Function
End If
lngRowCounter = lngRowCounter + 1
Set rngTargetA = ws.Range("A" & lngRowCounter)
Set rngTargetB = ws.Range("B" & lngRowCounter)
Loop
' if we don't find anything, return an empty string '
FindValue = ""
End Function
The above function takes in two range values, so you can use it like you would use any other function in Excel. Using the example you provided above, copy those cells into cells A2:C5. Next, in cell A1 put A. In cell B1 put 1. In C1, put =FindValue(A1,B1). This will execute the code above and return a match if it finds it.
Furthermore, if you change the "input values" from cells A1 or B1, your answer will update accordingly.
If he can put up with another colum to the left of the ones mentioned above (which you can hide from normal view), you can do it without using any VBA.
Insert a colum to the left of the first one, and set it to =A1&B1, =A2&B2 etc. You can then use VLOOKUP(x,A1:Dn,4) - where x is the string ("A1", "B2", etc.) that he wants to look up, and n is the number of rows in the dataset.
Hope that helps.
Another possibility using ADO:
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim r1 As Range
Dim r2 As Range
Dim r3 As Range
strFile = ActiveWorkbook.FullName
''Note HDR=No, so F1,F2 etc is used for column names
''If HDR=Yes, the names in the first row of the range
''can be used.
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=No;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
Set r1 = Worksheets("Sheet11").Range("F1")
Set r2 = Worksheets("Sheet11").Range("F2")
Set r3 = Worksheets("Sheet11").Range("F3")
cn.Open strCon
''Case sensitive, one text (f1), one numeric (f2) value
strSQL = "SELECT F3 FROM [Sheet11$A1:C4] WHERE F1='" & r1.Value _
& "' AND F2=" & r2.Value
rs.Open strSQL, cn, 3, 3
''Copies all matches
r3.CopyFromRecordset rs

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, "=", "")