Grab formulas from closed Excel file (not just values) - excel

I'm able to grab values from a closed workbook with the widely found GetValues function; it works great.
But sometimes I need to grab the formula of a cell from the closed workbook. I tried modifying GetValues to grab the cells formula but I'm getting errors.
How to get a formula (not simple value) of cells from a closed excel file?
With Sheets
For r = 2 To NewRowQty ' from second row to last row
For c = 1 To ThisColumnEnd ' out to EndColumn (from import dialogue box)
ThisCell = Cells(r, c).Address
ThisValue = GetValue(ThisPath, ThisFile, ThisSheet, ThisCell)
If ThisValue <> "0" Then
If c = 3 And r > 2 Then
Cells(r, c).Formula = GetFormula(ThisPath, ThisFile, ThisSheet, ThisCell)
Else
Cells(r, c) = ThisValue
End If
End If
Next c
Next r
End With
Calls these two functions, GetValue works fine, GetFormula won't grab the formula.
Private Function GetValue(p, f, s, c)
'p: path: The drive and path to the closed file (e.g., "d:\files")
'f: file: The workbook name (e.g., "budget.xls")
's: sheet: The worksheet name (e.g., "Sheet1")
'c: cell: The cell reference (e.g., "C4")
'Retrieves a value from a closed workbook
Dim arg As String
'Make sure the file exists
If Right(p, 1) <> "\" Then p = p & "\"
If Dir(p & f) = "" Then
GetValue = "File Not Found"
Exit Function
End If
'Create the argument
arg = "'" & p & "[" & f & "]" & s & "'!" & _
Range(c).Range("A1").Address(, , xlR1C1)
'Execute an XLM macro
GetValue = ExecuteExcel4Macro(arg)
End Function
Private Function GetFormula(p, f, s, c)
'p: path: The drive and path to the closed file (e.g., "d:\files")
'f: file: The workbook name (e.g., "budget.xls")
's: sheet: The worksheet name (e.g., "Sheet1")
'c: cell: The cell reference (e.g., "C4")
'Retrieves a value from a closed workbook
Dim arg As String
'Make sure the file exists
If Right(p, 1) <> "\" Then p = p & "\"
If Dir(p & f) = "" Then
GetFormula = "File Not Found"
Exit Function
End If
'Create the argument
arg = "'" & p & "[" & f & "]" & s & "'!" & _
Range(c).Range("A1").Address(, , xlR1C1).Formula
'Execute an XLM macro
GetFormula = ExecuteExcel4Macro(arg)
End Function
Update: Joel's first code post was the basis of what I ended up using so I marked that correct. Here's my actual implementation using a copy paste of entire row formulas. This is best because I don't know how many columns out may contains values or formulas, could be C or ZZ.
' silent opening of old file:
Application.EnableEvents = False
Set o = GetObject(FileTextBox.Text)
With Sheets
For r = 2 To NewRowQty ' from second row to last row
ThisCell = "A" & r
o.Worksheets(ThisRate).Range(ThisCell).EntireRow.Copy
Sheets(ThisRate).Range(ThisCell).PasteSpecial xlFormulas
Next r
End With
' Close external workbook, don't leave open for extended periods
Set o = Nothing
Application.EnableEvents = True

Why such convoluted code? The code you are using, for some reason, is invoking the Excel 4.0 backwards compatibility mode macro processor. I can't imagine why you would do that.
Here's a simple way to get the formula from cell Sheet1!A1 of c:\tmp\book.xlsx:
Dim o As Excel.Workbook
Set o = GetObject("c:\tmp\Book.xlsx")
MsgBox o.Worksheets("Sheet1").Cells(1, 1).Formula
Set o = Nothing ' this ensures that the workbook is closed immediately

If you insist on running Excel 4 - style macros (obsolete in 1994!) you need to use the XLM function GET.FORMULA to retrieve the formula instead of the value as follows:
arg = "GET.FORMULA('" & p & "[" & f & "]" & s & "'!" & _
Range(c).Range("A1").Address(, , xlR1C1) & ")"
Note that the result will have formulas using R1C1 notation instead of A1 notation.
Converting back to A1 notation (if you really want to do that) is left as an exercise to the reader.

Related

Excel VBA For Loop writing Formula in cells depending on the number in the loop

I am trying to write a Forumla in cells (from cell A2 to AI, where I is the number in the For Loop).
The formula to be written must correspond to Cell M2 to MI, also where I is the number in the For Loop.
I am able to write the sheet-reference by itself:
ws.Cells(1 + I, KolonneForX + 1).Formula = " ='" & ws.Name & "'"
which gives me the formula ='Ark1' (by it self) in the cells i want.
And also the right name of cell to read:
ws.Cells(1 + I, KolonneForX + 2).Formula = "=CONCATENATE(""M"",TEXT(" & I & "+1,""0""))"
which gives me the formula MI (by it self, with the corresponing number for I) in the cells i want.
But when I try to put the two of them togheter, I can't get it to run and refer to the right cells.
This is the code I have been trying to run:
Sub OppretteKnutepunkt()
Dim ws As Worksheet
Set ws = Worksheets("Ark1")
Dim KolonneForX As Integer
For I = 1 To 5
ws.Cells(1 + I, KolonneForX + 2).Formula = "='" & ws.Name & "'!CONCATENATE(""M"",TEXT(" & I & "+1,""0""))"
Next I
End Sub
I want the Cell A2 to have the following formula:
='Ark1'!M2
I want the Cell A3 to have the following formula:
='Ark1'!M3
and so on.
Any suggestions?
You can achieve this without looping
Dim I as Long
I = 7
With ws.Range("A2:A" & I)
.Formula = "='" & .Parent.Name & "'!M2"
End With
With is a shorthand way of using the same prefix on a block of code. The VBA compiler prefixes everything that begins with a . with whatever is declared by the With block
The equivalent of the above would be:
ws.Range("A2:A" & I).Formula = "='" & ws.Range("A2:A" & I).Parent.Name & "'!M2"

Adding freshly created formula into new module

I've just created a brand new macro. Took function down below from internet (all credits goes to trumpexcel.com), code down below
Function CONCATENATEMULTIPLE(Ref As Range, Separator As String) As String
Dim Cell As Range
Dim Result As String
For Each Cell In Ref
Result = Result & Cell.Value & Separator
Next Cell
CONCATENATEMULTIPLE = Left(Result, Len(Result) - 1)
End Function
Then I proceed to extract data from various columns and into the one (my table is 20 rows x 10 columns)
Sub conact_data()
Dim i As Integer
For i = 2 To Cells(Rows.Count, "A").End(xlUp).Row
Cells(i, "M").Value = Cells(i, "A").Value & " " & _
Cells(i, "B").Value & " / " & Cells(i, "D").Value & "; "
Next i
End Sub
Thanks to that I've got combined data from column A, B and D, so its 20 rows. All I want to do now is to concatenate data from M2:M21 using CONCATENATEMULTIPLE function therefore I try various approach (I want this huge line in P2 cell) like :
Cells(2, 16).Value = CONCATENATEMULTIPLE (M2:M21, " ")
or
Range("P2") = "CONCATENATEMULTIPLE (M2:M21, " ")"
I don't really know how to apply that
Secondly, I'd like withdraw the Cells(i, "B").Value as percentage. Can I do that in one line like Cells(i, "B").NumberFormat="0.00%".Value (which is not working for me obviously) else I need to copy column B into another column with number format and then combine the new column, properly formatted instead of column B?
Thanks in advance
Percent format: Range("B" & i).NumberFormat = "0.00%"
CONCATENATEMULTIPLE
In VBA, CHR(32) = " "
In Excel, CHAR(32) = " "
With that being said...
'Value
Range("P2").Value = CONCATENATEMULTIPLE(Range("M2:M21"), CHR(32))
'Formula
Range("P2").Formula = "=CONCATENATEMULTIPLE(M2:M21, CHAR(32))"
You should really qualify all of your ranges with a worksheet
Say your workbook has 10 sheets. When you say Range("P2"), how do we (VBE) know what sheet you mean? Objects need to be properly qualified. Sometimes this is not a huge issue, but when you are working across multiple sheets, not qualifying ranges can lead to some unexpected results.
You can qualify with a worksheet a few ways.
Directly: ThisWorkbook.Sheets("Sheet1").Range("P2").Copy
Or use a variable like so
Dim ws as Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
ws.Range("P2").Copy
Now there is no room for ambiguity (potential errors) as to the exact location of Range("P2")
First of all, remove your ConcatenateMultiple() code, and instead use Excel worksheet function CONCAT(), which takes a range and a delimiter as parameters.
Here is how you can handle the percentage issue and supply a default for non-numeric items. I've also cleaned up the way you reference your data range.
Sub concat_data()
Dim rngRow As Range, vResult As Variant
Const DEFAULT = 0 'Can also be set to a text value, eg. "Missing"
For Each rngRow In [A2].CurrentRegion.Rows
If IsNumeric(rngRow.Cells(, 4)) Then vResult = rngRow.Cells(, 4) * 100 & "%" Else vResult = DEFAULT
Range("M" & rngRow.Row) = rngRow.Cells(, 1) & rngRow.Cells(, 2) & "/" & vResult & ";"
Next
[M2].End(xlDown).Offset(1).Formula = "=CONCAT(M2:M" & [M2].End(xlDown).Row & ",TRUE,"" "")"
End Sub
I'm not a fan of hard-coding range references, like the [A2] or Range("M"), but will leave that for another time.

Excel VBA macro reading one column with differing text

I was tasked with creating a code that will check to see if internal hyperlinks in an excel spreadsheet worked. This code first changes the formulas that were on the spreadsheet and makes them actual hyperlinks (they were originally formulas linking the locations together). The problem that I have now is that I want to create hyperlinks ONLY if Column S has text. If it doesn't, I don't want the "E-COPY" text to be displayed. All of the text in Column S varies (not one line has the same characters), which is why I'm drawing a blank is to how I tell the program to only continue if it has any text, not anything specific. I am working with Excel 2016.
Also, I am doing this to 71935 and counting rows; is there a limit to how many it can go through? If so, what can I do about it?
Thank you!
Sub CreateHyperlinks()
Dim FN As Variant
Dim Path As Variant
Dim count As Variant
Sheets(1).Activate
count = WorksheetFunction.CountA(Sheets(1).Range("A:A"))
For i = 2 To count
If Range("AM" & i).Value = "Yes" And Columns("S") = Then
Range("E" & i).Value = ""
Path = Sheets(1).Range("R" & i).Value
FN = Sheets(1).Range("S" & i).Value
Sheets(1).Range("E" & i).Select
Selection.ClearFormats
Selection.Hyperlinks.Add Anchor:=Selection, Address:=Path & FN, TextToDisplay:="E-COPY"
Range("AM" & i).Value = " "
End If
Next i
End Sub
If you just need to check for any content in ColS then:
If Range("AM" & i).Value = "Yes" And Len(Range("S" & i).Value) > 0 Then
Few things:
'make a reference to the sheet you're working with
Dim ws As Worksheet
Dim wb As Workbook
Set wb = Excel.Application.ThisWorkbook
Set ws = wb.Worksheets(1)
'gets the absolute last row with data in it // ignores empty cells
count = ws.UsedRange.Rows.Count
personally, i hate working with named ranges, so i would suggest setting range references like so
what you wrote
Path = Sheets(1).Range("R" & i).Value
what i believe it should look like
Path = ws.Cells(i, 18).Value
if you want to test the type when working with variants, try this:
'tests the type associated with the variant. an 8 = string
If VarType(ws.Cells(i, 19).Value) = 8 Then
'do your thing
'tests if the value is null
ElseIf VarType(ws.Cells(i, 19).Value) = 0 Then
'do your other thing
here's a list of the vartype enumeration to help you out.
hope it helps!

Using Dictionary (hashing) in VBA

I have an excel workbook with multiple sheets.
I have to get counts of certain entries by using filters(i'm searching text instead of using filters here)
The "Main" sheet is where the count is updated. The strings are searched from other sheets in the workbook
The cells where the count should be updated varies.
The search criteria,keyword,sheet,range, etc is given in the sample code which I have posted.
Example from code:
In Cell, AE43, the count is updated only when the sheet "TT" meets the criteria mentioned.
So, similarly I'll have to use the same kind of code 30+ times for different cells to get the data.
So instead of typing the code for similar search, I want to know whether we can use "Dictionary" function (hashing in other languages) here, so that a cell can be updated automatically if it meets the criteria.
Sub WBR()
Dim wf As WorksheetFunction
Set wf = Application.WorksheetFunction
With ActiveWorkbook.Worksheets("TT") 'no of tickets processed - summary
[AE43] = wf.CountIfs(.Range("I:I"), "<>Duplicate TT", _
.Range("G:G"), "<>Not Tested", _
.Range("U:U"), "Item")
End With
With ActiveWorkbook.Worksheets("TT") 'not tested tickets - summary
[AE44] = wf.CountIfs(.Range("G:G"), "Not Tested")
End With
With ActiveWorkbook.Worksheets("TT") 'Tickets moved back- outdated OS and App Versions - summary
[AE45] = wf.CountIf(.Range("I:I"), "Outdated App Version") + wf.CountIf(.Range("I:I"), "Outdated OS")
End With
Here's a basic example which should get you started.
Sub showing how to call the code:
Sub Tester()
With ThisWorkbook.Sheets("Main")
.Range("A1") = GetCount("TT", False, "A:A", "Blue")
.Range("A2") = GetCount("TT", False, "A:A", "Blue", "C:C", "Red")
.Range("A3") = GetCount("TT", True, "A:A", "Blue", "C:C", "Red")
End With
End Sub
Generalized version of your use cases:
'If addValues is True and there are >1 set of criteria then
' sum up a bunch of COUNTIF(), else use COUNTIFS() so all
' criteria are applied at the same time
Function GetCount(shtName As String, addValues As Boolean, _
ParamArray crit()) As Long
Dim sht As Worksheet, f As String, num As Long, i As Long
Set sht = ThisWorkbook.Sheets(shtName)'<< counting things on this sheet
num = UBound(crit)
If num = 1 Or addValues Then
f = "COUNTIF(" & crit(0) & ",""" & crit(1) & """)"
End If
If num > 1 Then
If addValues Then
'already got the first pair: add the rest
For i = 2 To num Step 2
f = f & " + COUNTIF(" & crit(i) & ",""" & crit(i + 1) & """)"
Next i
Else
f = "COUNTIFS("
For i = 0 To num Step 2
f = f & crit(i) & ",""" & crit(i + 1) & """"
If i <> num - 1 Then f = f & ","
Next i
f = f & ")"
End If
End If
If f <> "" Then
Debug.Print f
GetCount = sht.Evaluate(f) '<<do not use Application.Evaluate here
Else
GetCount = -1 '<< something went wrong...
End If
End Function
Debug output:
COUNTIF(A:A,"Blue")
COUNTIFS(A:A,"Blue",C:C,"Red")
COUNTIF(A:A,"Blue") + COUNTIF(C:C,"Red")
Probably could use some error-handling and if there are other use cases you'll need to add those in.

How to loop through rows and columns and Concatenate two text cells in Excel VBA?

I am fairly new to Excel Macros and I am looking for a way to loop through the row headings and columns headings and combine them into one cell for each row and column heading until I have combined all of them.
An example of the First Column cell would be "Your Organizations Title"
An Example of the First Row Cell Would be "22. Cheif Investment Officer"
An example of the first combined cell that I want on a new sheet would be this: "22. Chief Investment Officer (Your Organization's Title)
I then want the combined cells on the new sheet to offset one column to the right until it has iterated through all of the rows and columns.
I have just joined the forum and it will not let me post images or I would have. Perhaps this gives a better idea, here is my code now:
Sub Fill()
' Select cell A2, *first line of data*.
Set title = Sheets("Compensation, 3").Range("B6:B500")
Set descr = Sheets("Compensation, 3").Range("C5:AAA5")
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(title.Value)
Do Until IsEmpty(descr.Value)
ActiveCell.Offset(0, 1).Formula = _
"=title.value & "" ("" & descr.value & "")"""
Set descr = descr.Offset(0, 1)
Loop
Set title = title.Offset(1, 0)
Loop
End Sub
When I run it goes puts this into the active cell:
=title.value & " (" & descr.value & ")"
It does not recognize the variables and come up with the NAME error. It also goes into an infinite loop with no output besides the one cell.
Edit:
I cannot answer my own question because I am new to the forum, but using a combination of your answers I have solved the problem!
Here is the finished code:
Sub Fill()
' Select cell A2, *first line of data*.
Set title = Sheets("Compensation, 3").Range("B6")
Set descr = Sheets("Compensation, 3").Range("C5")
offsetCtr = 0
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(title.Value)
Do Until IsEmpty(descr.Value)
ActiveCell.Offset(0, offsetCtr).Formula = title.Value & " (" & descr.Value & ")"
offsetCtr = offsetCtr + 1
Set descr = descr.Offset(0, 1)
Loop
Set descr = Sheets("Compensation, 3").Range("C5")
Set title = title.Offset(1, 0)
Loop
End Sub
Thank you so much!
Option Explicit
Sub GenerateAndPasteFormulaForTitleAndDescription( _
ByVal titlesRange As Range, ByVal descriptionRange As Range, _
ByVal startCellOnDestination As Range)
Dim title As Range
Dim descr As Range
Dim offsetCtr As Long
Dim formulaTemplate As String
Dim newFormula As String
formulaTemplate = "=CONCATENATE([1], '(', [2], ')')"
startCellOnDestination.Worksheet.EnableCalculation = False
For Each title In titlesRange.Cells
For Each descr In descriptionRange.Cells
If title.Value <> "" And descr.Value <> "" Then
newFormula = Replace(formulaTemplate, "[1]", _
title.Address(External:=True))
newFormula = Replace(newFormula, "[2]", _
descr.Address(External:=True))
newFormula = Replace(newFormula, "'", Chr(34))
startCellOnDestination.Offset(0, offsetCtr).Formula = newFormula
offsetCtr = offsetCtr + 1
End If
Next
Next
startCellOnDestination.Worksheet.EnableCalculation = True
End Sub
Here is how to call the above procedure
GenerateAndPasteFormulaForTitleAndDescription _
Sheets("Compensation, 3").Range("B6:B500"), _
Sheets("Compensation, 3").Range("C5:AAA5"), _
Sheets("new sheet").Range("B5")
EDIT: The code loops through combination of title and description, checks if both of them aren't empty and creates a formula. It pastes the formula into the start cell (Sheets("new sheet").Range("B5") in this case) and moved ahead and pastes the next formula in the column next to it
Basically, you are trying to use VBA objects in worksheet functions. It doesn't quite work that way.
Try replacing
"=title.value & "" ("" & descr.value & "")"""
with
=title.value & " (" & descr.value & ")"

Resources