Why is cell "A1" being used in this GetValue function in VBA? - vba

I'm using this function to retrieve a value from a closed workbook. In this 8th line of this code, I don't understand why "A1" is being used. What exactly is happening in that entire 8th line? I'm confused by the xlR1C1 argument as well.
Private Function GetValue(path, file, sheet, ref)
Dim arg As String
If Right(path, 1) <> "\" Then path = path & "\"
If Dir(path & file) = "" Then
GetValue = "File Not Found"
Exit Function
End If
arg = "'" & path & "[" & file & "]" & sheet & "'!" & _
Range(ref).Range("A1").Address(, , xlR1C1)
GetValue = ExecuteExcel4Macro(arg)
End Function

Range().Range() Documentation here:
When applied to a Range object, the property is relative to the Range object. For example, if the selection is cell C3, then Selection.Range("B1") returns cell D3 because it’s relative to the Range object returned by the Selection property. On the other hand, the code ActiveSheet.Range("B1") always returns cell B1.
The code is using that second Range("A1") to ensure that if you have a ref range larger than one cell, it only returns the top left cell of that range. Also it would appear your other Sub called ExecuteExcel4Macro() requires an R1C1 type cell reference so the address is being converted into that type for passing the arg string into the Sub.

xlR1C1 is a reference style which is used to specify how formulas work. When using this style, your formulas will work and look very differently than you expect. The R1C1 specification, basically means the cells are referred to differently using row & column ordinals instead of letter names. For example, when using xlR1C1, you would access cell B2 by using =R2C2 (row2, column 2). Another Example, cell C10 could be referred to as =R10C3
As to whats happening on line 8... you are constructing a cell reference that looks like this: (Note that your cell reference will be different because it has a file path in it)
='[Myfilename.xlsx]Sheet1'!R1C1
You can use the debugger to view the string contained in the arg variable.

Adding Range("A1") to the code doesn't appear to do anything at all. Typing this into the immediate window results in some... unexpected results.
?Range("B3").Range("A1").Address
$B$3
Now, I would have expected that to return $A$1, but it seems that this part of the function will return the address of Range(ref).
Now, calling Range.Address with the ReferenceStyle argument would produce these results.
?Range("B3").Range("A1").Address(,,xlR1C1)
R3C2

Related

Range.SpecialCells: What does xlCellTypeBlanks actually represent?

The Range.SpecialCells method can be used to return a Range object meeting certain criteria. The type of criteria is specified using an xlCellType constant.
One of those constants (xlCellTypeBlanks) is described as referring to "Empty cells" with no further elaboration.
Does anyone know what definition of "Empty" this method uses? Does it include cells with no values/formulas but various other features (data validation, normal formatting, conditional formatting, etc)?
That type includes the subset of cells in a range that contain neither constants nor formulas. Say starting with an empty sheet we put something in A1 and A10 and then run:
Sub ExtraSpecial()
Set r = Range("A:A").SpecialCells(xlCellTypeBlanks)
MsgBox r.Count
End Sub
we get:
Formatting and Comments are not included. Also note that all the "empty" cells below A10 are also ignored.
Papalew's response noted that "xlCellTypeBlanks" excludes any cells not within a specific version of the "used range" that's calculated in the same way as the special cell type "xlCellTypeLastCell". Through testing I've discovered that "xlCellTypeLastCell" returns the last cell of the "UsedRange" property as of the last time the property was calculated.
In other words, adding a line that references "UsedRange" will actually change the behavior of the SpecialCells methods. This is such unusual/unexpected behavior that I figured I'd add an answer documenting it.
Sub lastCellExample()
Dim ws As Worksheet
Set ws = Sheets.Add
ws.Range("A1").Value = "x"
ws.Range("A5").Value = "x"
ws.Range("A10").Value = "x"
'Initially the "UsedRange" and calculated used range are identical
Debug.Print ws.UsedRange.Address
'$A$1:$A$10
Debug.Print ws.Range(ws.Range("A1"), _
ws.Cells.SpecialCells(xlCellTypeLastCell)).Address
'$A$1:$A$10
Debug.Print ws.Cells.SpecialCells(xlCellTypeBlanks).Address
'$A$2:$A$4,$A$6:$A$9
'After deleting a value, "UsedRange" is recalculated, but the last cell is not...
ws.Range("A10").Clear
Debug.Print ws.Range(ws.Range("A1"), _
ws.Cells.SpecialCells(xlCellTypeLastCell)).Address
'$A$1:$A$10
Debug.Print ws.Cells.SpecialCells(xlCellTypeBlanks).Address
'$A$2:$A$4,$A$6:$A$10
Debug.Print ws.UsedRange.Address
'$A$1:$A$5
'...until you try again after referencing "UsedRange"
Debug.Print ws.Range(ws.Range("A1"), _
ws.Cells.SpecialCells(xlCellTypeLastCell)).Address
'$A$1:$A$5
Debug.Print ws.Cells.SpecialCells(xlCellTypeBlanks).Address
'$A$2:$A$4
End Sub
The definition does indeed contain the idea of having nothing in the cell, i.e. it excludes any cell that contains either:
a numerical value
a date or time value
a text string (even an empty one)
a formula (even if returning an empty string)
an error
a boolean value
But it also excludes any cell that’s not within the range going from A1 to the last used cell of the sheet (which can be identified programmatically through ws.cells.specialCells(xlCellTypeLastCell), or by using the keyboard Ctrl+End).
So if the sheet contains data down to cell C10 (i.e. Ctrl+End brings the focus to cell C10), then running Range("D:D").specialCells(xlCellTypeBlanks) will fail.
NB The range A1 to LastCellUsed can sometimes be different from the used range. That would happen if some rows at the top and/or some columns at on the left never contained any data.
On the other hand, cells that fit the empty definition above will be properly identified no matter any of the followings:
size or colour of font
background colour or pattern
conditional formatting
borders
comments
or any previous existence of these that would later have been cleared.
A bit beside the main subject, let me ask a tricky question related to how the term BLANK might be defined within Excel:
How can a cell return the same value for CountA and CountBlank?
Well, if a cell contains ' (which will be displayed as a blank cell), both CountA and CountBlank will return the value 1 when applied to that cell. My guess is that technically, it does contain something, though it is displayed as a blank cell. This strange feature has been discussed here.
Sub ZeroLengthString()
Dim i As Long
Dim ws As Worksheet
Set ws = ActiveSheet
ws.Range("A2").Value = ""
ws.Range("A3").Value = Replace("a", "a", "")
ws.Range("A4").Value = """"
ws.Range("A6").Value = "'"
ws.Range("A7").Formula= "=if(1=2/2,"","")"
ws.Range("B1").Value = "CountA"
ws.Range("C1").Value = "CountBlank"
ws.Range("B2:B7").FormulaR1C1 = "=CountA(RC[-1])"
ws.Range("C2:C7").FormulaR1C1 = "=CountBlank(RC[-2])"
For i = 2 To 7
Debug.Print "CountA(A" & i & ") = " & Application.WorksheetFunction.CountA(ws.Range("A" & i))
Debug.Print "CountBlank(A" & i & ") = " & Application.WorksheetFunction.CountBlank(ws.Range("A" & i))
Next i
End Sub
In this example, both lines 6 & 7 will return 1 for both CountA and CountBlank.
So the term Blank doesn’t appear to be defined a unique way within Excel: it varies from tool to tool.

How to create a VBA formula that takes value and format from source cell

In Excel's VBA I want to create a formula which both takes the value from the source cell and the format.
Currently I have:
Function formEq(cellRefd As Range) As Variant
'thisBackCol = cellRefd.Interior.Color
'With Application.Caller
' .Interior.Color = thisBackCol
'End With
formEq = cellRefd.Value
End Function`
This returns the current value of the cell. The parts that I have commented out return a #VALUE error in the cell. When uncommented it seems the colour of the reference is saved however the Application.Caller returns a 2023 Error. Does this mean that this is not returning the required Range object?
If so how do I get the range object that refers to the cell that the function is used? [obviously in order to set the colour to the source value].
Here's one approach showing how you can still use ThisCell:
Function CopyFormat(rngFrom, rngTo)
rngTo.Interior.Color = rngFrom.Interior.Color
rngTo.Font.Color = rngFrom.Font.Color
End Function
Function formEq(cellRefd As Range) As Variant
cellRefd.Parent.Evaluate "CopyFormat(" & cellRefd.Address() & "," & _
Application.ThisCell.Address() & ")"
formEq = cellRefd.Value
End Function
This is the solution I found to the above question using Tim William's magic:
Function cq(thisCel As Range, srcCel As Range) As Variant
thisCel.Parent.Evaluate "colorEq(" & srcCel.Address(False, False) _
& "," & thisCel.Address(False, False) & ")"
cq = srcCel.Value
End Function
Sub colorEq(srcCell, destCell)
destCell.Interior.Color = srcCell.Interior.Color
End Sub
The destCell is just a cell reference to the cell in which the function is called.
The interior.color can be exchanged or added to with other formatting rules. Three extra points to note in this solution:
By keeping the value calculation in the formula this stops the possibility for circular referencing when it destCell refers to itself. If placed in the sub then it continually recalculates; and
If the format is only changed when the source value is changed, not the format as this is the only trigger for a UDF to run and thus change the format;
Application.Caller or Application.ThisCell cannot be integrated as when it refers to itself and returns a value for itself it triggers an infinite loop or "circular reference" error. If incorporated with an Address to create a string then this works though as per Tim William's answer.

Write a VLOOKUP as a string in a cell with dynamic path retrieved through GetoOpenfilename

I am trying to write a VLOOKUP in a cell as a string, with VBA. This means that I do not want the result to appear in the cell as a value, but I want the whole VLOOKUP expression instead (For this example : "VLOOKUP(C6,'[path_to_file.xlsm]OTD Table!$B:$F,4,0)"). The challenge is that the range argument of the VLOOKUP is a concatenation of a path (path_to_file.xlsm) that the user selects with a GetOpenFilename, and a string that specifies the tab in which the lookup table is located ("OTD Table!$B:$F,4,0").
The issue I am getting is very interesting :
When I print my expression in a Msgbox, the expression appears correctly. However, when I write it in a cell, the path mysteriously appears incorrectly.
Sub macro()
dim data_file_new as String
data_file_new = CStr(Application.GetOpenFilename(FileFilter:="Excel Workbooks (*.xls*),*.xls*", Title:="Select new data file")) ' The user selects the file
str_ = "=VLOOKUP(C6," & "'[" & data_file_new & "]OTD Table!$B:$F,4,0)" ' This will display the expression correctly
cells(1,10)="=VLOOKUP(C6," & "'[" & data_file_new & "]OTD Table!$B:$F,4,0)"' This will not display the same thing as in the messagebox above
end Sub
I hope one of you guys can make sens of this !
Because you're dropping a formula into a cell that you want to display as straight text, you have to be explicit with Excel and tag the text string to prevent interpreting it as a formula. The simplest way to do this is pre-pend the string with a single-quote "'".
Sub macro()
Dim data_file_new, str_ As String
str_ = "'=VLOOKUP(C6,'["
data_file_new = CStr(Application.GetOpenFilename(FileFilter:="Excel Workbooks (*.xls*),*.xls*", Title:="Select new data file")) ' The user selects the file
str_ = str_ & data_file_new & "]OTD Table!$B:$F,4,0)" ' This will display the expression correctly
ActiveSheet.Cells(1, 10).Value = str_
End Sub
Yeah either you'll need to set the string to add a single quote, or you'll need to change the numberformat of the cell to text (Cells(1,10).NumberFormat = "#")
Either of those should work.

How do I change the text of a cell without changing the formula?

I am new to VBA. This question has been asked and answered here but the answer seems way too hackish. Is there a better way to change the value of a cell without changing the underlying formula in it? I've tried target.text="changed" but that gives me an error of "Object Required"
The reason I ask is that in a UDF you can change the display of the cell but the formula remains there. How can I do this outside of a UDF?
EDIT:
In the example below, myudf and my_udf appear to do the same thing and everyone is telling me to just do NumberFormat. The problem is that Numberformat will change the cell permanently. If you entered "=my_udf()" in A2 then just go back and type some random text there. Unless you go back and manually re-format the cell (or enter in an excel built-in function), it will display "ThisThatThere".
Module1
Function myudf()
myudf = "ThisThatThere"
End Function
Function my_udf()
my_udf = "Temporary"
End Function
ThisWorkBook:
Sub Workbook_SheetChange(ByVal sh As Object, ByVal target As Range)
If target.HasFormula Then
If LCase(target.Formula) Like "=my_udf(*" Then
target.NumberFormat = "0;0;0;""ThisThatThere"""
End If
End If
End Sub
It is easy if the formula returns a number value rather than a text value .Place a formula in a cell, select it and run:
Sub ChangeText()
Dim DQ As String, mesage As String
DQ = Chr(34)
mesage = DQ & "override" & DQ
ActiveCell.NumberFormat = mesage & ";" & mesage & ";" & mesage & ";"
End Sub
By itself, a UDF , like a non-VBA worksheet formula, can only return a value to a cell.................the best the value can do it to affect conditional formatting.

VBA - Excel create a hyperlink using the sheets command and cells method

Q1: The routine below does not work.
Sub test()
TXT = Sheets("INDEX").Cells(2, 1).Value
AKO = Sheets("TOBESEEN").Cells(1, 1).Address
Sheets("INDEX").Hyperlinks.Add Anchor:=Sheets("INDEX").Cells(1, 2), Address:="",
SubAddress:=AKO, TextToDisplay:=TXT
End Sub
Q2. Is there a place where I can see ALL the properties of the cell? When I type the "dot" after cells, VBA DOES NOT GIVE any options.
Like
sheet1.cell(1,2).VALUE
sheet1.cell(1,2).ADDRESS
sheet1.cell(1,2).?
I suspect my problem is related to the definition of AKO, but I am not sure what the correct property is (if not ADDRESS)
Thank you
Q1: Try to use this code:
Sub test()
TXT = CStr(Sheets("INDEX").Cells(2, 1).Value)
AKO = Sheets("TOBESEEN").Cells(1, 1).Address
Sheets("INDEX").Hyperlinks.Add _
Anchor:=Sheets("INDEX").Cells(1, 2), _
Address:="", _
SubAddress:=Sheets("TOBESEEN").Name & "!" & AKO, _
TextToDisplay:=TXT
End Sub
Q2: you can use F2 help. There you can see a list of properties and methods of any object.
When you create a hyperlink in a document, you need to give the "full" address of the cell you are linking to (sheet name and cell address), not just the .Address (which is the absolute address on a given sheet, but doesn't include the sheet information). To get the full address (including the sheet) of a cell, you need to add an additional parameter to the Address():
.Address(External:=True)
which will give you the full address, including workbook and sheet name. Unfortunately, if you create this link and then change the name of the workbook (maybe because you had not saved it up to this point), the link will break.
It is therefore (slightly) more robust to create a link that doesn't include the workbook name. The following routine does this for you. Note that is usually a good idea to split your code into small self-contained functions that do one thing particularly well - you can re-use your code more easily, and the main program becomes easier to read and maintain. I suggest therefore that you create the following function:
Sub addLink(target As Range, location As Range, text As String)
' create a hyperlink with text "text"
' in the cell "location"
' pointing to the range "target"
Dim linkAddress As String
linkAddress = target.Address(external:=True) ' this is the "full" address
'remove everything between brackets - this is the workbook name:
bLeft = InStr(1, linkAddress, "[") ' location of left bracket
bRight = InStr(bLeft, linkAddress, "]") ' location of right bracket
linkAddress = Left(linkAddress, bLeft - 1) + Mid(linkAddress, bRight + 1)
' now create the link:
location.Parent.Hyperlinks.Add _
anchor:=location, _
Address:="", _
SubAddress:=linkAddress, _
TextToDisplay:=text
End Sub
You can call this from your code above as follows:
Dim TXT As String, AKO As Range, LOC As Range ' define types when possible
TXT = Sheets("INDEX").Cells(2, 1).Value ' I called this text
Set AKO = Sheets("TOBESEEN").Cells(1, 1) ' I called this target (note - need Set)
Set LOC = Sheets("INDEX").Cells(1, 2) ' I called this location
addLink AKO, LOC, TXT
As for the second part of your question - "I can't see the properties of Cells()". Yes, that is annoying. It happens that Cells() is a lot like a Range object; you can see in the above that I was able to do Set AKO = Sheets("TOBESEEN").Cells(1,1) which demonstrates this. You can declare a variable as being of the type Range, and then tooltips will work for you:
Dim r As Range
r.
and as you type r. you will see a list of the properties of Range (which are also the properties of Cells):
It is annoying that Cells doesn't just do this by itself. It is annoying that Address() doesn't have an option to include the sheet name and not the workbook name. Excel is full of annoyances. In fact, there is even a book called "Excel Annoyances". And more could be written, I'm sure…
At least there are usually VBA tricks to work around these things and get the job done.