Excel Macro, inserting internationally valid formula during run-time - vba

I've got an Excel spreadsheet, with a Macro, that inserts a conditional formatting, like this:
Selection.FormatConditions.Add Type:=xlExpression, Formula1:="=UND($A3=""" & lastName & """; $B3=""" & firstName & """)"
As you can see, I've used the German formula for "AND" (i.e. "UND"), and obviously, this code doesn't work as soon as I use it on a French or English version of Excel.
Usually formulas are localized automatically, but how can I insert a formula during run-time that will work on ALL versions?

Ok, thanks for helping me with this, you've helped me crack this one.
It is indeed not possible to just use English. One can use English when operating on a formula, eg. by setting coding Range("A1").formula="AND(TRUE)", but this does not work with FormatConditions.
My solution is a function that writes a formula temporarily to a cell, reads it through the FormulaLocal property, and returns the localized formula, like so:
Function GetLocalizedFormula(formula As String)
' returns the English formula from the parameter in the local format
Dim temporary As String
temporary = Range("A1").formula
Range("A1").formula = formula
Dim result As String
result = Range("A1").FormulaLocal
Range("A1").formula = temporary
GetLocalizedFormula = result
End Function
The returned formula can be used on FormatConditions, which will be re-localized or un-localized when the document is later opened on a different-language version of Excel.

I just found a very elegant solution to the problem in a German Excel forum. This doesn't write to a dummy cell but rather uses a temporary named range. I used the original idea (credit to bst) to write a translating function for both directions.
Convert localized formula to English formula:
Public Function TranslateFormula_LocalToGeneric(ByVal iFormula As String) As String
Names.Add "temporaryFormula", RefersToLocal:=iFormula
TranslateFormula_LocalToGeneric = Names("temporaryFormula").RefersTo
Names("temporaryFormula").Delete
End Function
Convert English formula to localized formula:
Public Function TranslateFormula_GenericToLocal(ByVal iFormula As String) As String
Names.Add "temporaryFormula", RefersTo:=iFormula
TranslateFormula_GenericToLocal = Names("temporaryFormula").RefersToLocal
Names("temporaryFormula").Delete
End Function
This is very handy if you need to deal with formulas in conditional formatting, since these formulas are always stored as localized formulas (but you could need their generic version, e.g. to use Application.Evaluate(genericFormula)).

Store (a trivial version of) the formula in a (hidden) cell in your workbook.
Then when you open the workbook that formula will be translated automatically by excel for the user.
Now you just have to dissect this formula in your script (find the opening bracket "(" and take the past left of that:
Use something like:
strLocalizedFormula = Mid(strYourFormula, 2, InStr(1, strYourFormula, "(") - 2)
where strYourFormula will be a copy from the formula from your worksheet.
I hope this works as I only use an English environment.
Also from reading this:
http://vantedbits.blogspot.nl/2010/10/excel-vba-tip-translate-formulas.html
I am thinking you should (only) be able to use the english version of a cell formula from VBA.

Maybe try this (untested as I only have English version insatlled)
Write your international version of the formula to an out of the way cell using Range.Formula . Then read it back from Range.FormulaLocal, and write that string to the FormatConditions

I know this thread is ages old, and someone may have found an elegant solution, but I just had the same problem where I needed to apply conditional formatting without modifying the sheet, creating temporary cell contents or named ranges. All users use English language versions of Excel, so the functions used in the formulas are the same, but the regional settings vary by location, and therefore also the parameter separater; In Norwegian, it's ";" instead of ",", much like the rest of Europe, I guess.
For example, I needed to automatically create conditional formatting, using Excel formula for the following criterion:
.FormatConditions.Add xlExpression, Formula1:="=AND(ISNUMBER(B" & I & "),B" & I & ">=" & Ul1 & ")"
Where "Ul1" is a value defined in a previous step, and it's not important for the solution.
However, I needed to be able to run this on computers with both Norwegian and English settings
I and found a very short and simple solution from Andrew Pulsom here: https://www.mrexcel.com/board/threads/french-vba-vs-english-vba.729570/. He just made the parameter separator into a variable:
If Application.International(xlDecimalSeparator) = "," Then
Sep = ";"
Else
Sep = ","
End If
Cl1 = "=AND(ISNUMBER(B" & I & ")" & Sep & "B" & I & "<" & Ul1 & ")"
Worked like a charm for me :)
I know that this only solves part of the problem, but I assume that this could apply to many international companies which use English Office installations with local regional settings.

Thanks everyone! I found the post very useful.
My solution is a combination of others, I add it in case somebody finds it useful.
Dim tempform As String
Dim strlocalform1 As String
Dim strlocalform2 As String
' Get formula stored in WorksheetA Cell O1 =IFERROR(a,b)
tempform = Worksheets("Sheet").Range("O1").Formula
' Extract from the formula IFERROR statement in local language.
strlocalform1 = Mid(tempform, 2, InStr(1, tempform, "(") - 1)
' Extract from the formula separator , (comma) in local settings.
strlocalform2 = Mid(tempform, InStr(1, tempform, "a") + 1, 1)
' Add formula in local language to desired field.
pvt.CalculatedFields.Add Name:="NewField", Formula:="=" & strlocalform1 & "FORMULA" & strlocalform2 & ")"
Hope this helps!

Please refer to the link for more explanation: https://bettersolutions.com/csharp/excel-interop/locale-culture.htm
CultureInfo baseCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(xlapp.LanguageSettings.LanguageID(Office.MsoAppLanguageID.msoLanguageIDUI));
// do something
System.Threading.Thread.CurrentThread.CurrentCulture = baseCulture;

Related

Excel vba formula string: array formula - how to simulate Ctrl+Shift+Enter press

I have a worksheet with data and some columns which I fill with formulas via vba. The formula I struggle with is an array formula that looks like this:
Workbooks(job_file).Worksheets(1).Cells(h + b, 195).Formula = _
"{=IF(MAX(IF(B2:M2>$FY" & currentRow & ",$B$1:$M$1))=0,0," & _
"MAX(IF(sheet1!B2:M2>$FY" & currentRow & "," & _
"sheet1!$B$1:$M$1)))+1}"
It's supposed to be an array formula, so that's why I put {} there. However, when run it simply display the formula's text in a cell, without calculating it. I have to manually remove the brackets, and then press Ctrl+Shift+Enter myself.
Is there any way to avoid it? I have a great many rows and I can't ctrlshiftenter each.
I tried running it without brackets, it works, but gives a #VALUE! error, which can also be fixed by applying Ctrl+Shift+Enter.
To create an array formula with a simulated ctrl+shift+enter (aka CSE), use the Range.FormulaArray Property instead of the Range.Formula Property and let Excel add the 'curly braces'.
with Workbooks(job_file).Worksheets(1)
.Cells(h + b, 195).FormulaArray = _
"=IF(MAX(IF(B2:M2>$FY" & currentRow & ",$B$1:$M$1))=0,0," & _
"MAX(IF(sheet1!B2:M2>$FY" & currentRow & ", sheet1!$B$1:$M$1)))+1"
end with
I noticed in your formula that you use B2:M2 and sheet1!B2:M2. Shouldn't they both be sheet1!B2:M2?
There are some considerations.
Runtime Error: 1004 - Too long. There is a reduced character limit of 255 for FormulaArray but there are work-arounds.
Runtime Error: 1004 - Broken String. Remember that all quotes within a quoted string must be doubled up. This is easily one of the most common causes of errors when trying to write a formula into a cell through VBA. Hint: TEXT(,) can be used instead of "" so you don't have to type """" for a zero-length string.
FormulaArray accepts both xlR1C1 and xlA1 style formulas. If you can wrap your head around xlR1C1 style formula syntax, it is generally easier to construct a concatenated formula string in xlR1C1 since you can use digits to represent column numbers instead of trying to convert column ordinals to a column letter. However, do not try to mix-and-match xlA1 and xlR1C1 range references in the same formula; they must all be one style or the other.
If you are having trouble formulating a string that will be accepted as a formula, put a tick (e.g. ' ) in front of the first equals sign then run the code and return to the worksheet to see what was put in. Make modifications on the worksheet until you have a working formula then transfer those modifications to the VBA code.
As a note, it looks like this can be done without an array formula, like so:
Workbooks(job_file).Worksheets(1).Cells(h + b, 195).Formula = _
"=IF($FY" & currentRow & ">MAX(B2:M2),0," & _
"MAX(INDEX((B2:M2>$FY" & currentRow & ")*$B$1:$M$1,)))+1"

Excel VBA: Reformatting date to very short date with two-digit-years does not work

I have a list of dates exported from another application. Initially they are formatted as Standard, without any special or automatic conversion, in the format YYYY.MM.DD.
Previously I just replaced the dots with slashes and Excel recognized the dates as dates, automatically formatting them to the appropriate locale DD.MM.YYYY, which worked fine.
Now, I just wanted to remove the first two digits of the year (new format DD.MM.YY), but it does not work in any way I've tried. What I have done so far:
currentCell.Value = CDate(currentCell.Value): This switches the format to date, but the standard long formatting still applies.
currentCell.NumberFormat = "dd.mm.yy;#" As seen from recording a macro and changing the value manually. Does not seem to make any difference in output format.
currentCell.Value = FormatDateTime(currentCell.Value, vbShortDate): Does not work, as Short Date is DD.MM.YYYY, not DD.MM.YY in the current locale.
format(date, "dd.mm.yy") as suggested by Tom Preston and msaint: Does not change the output compared to the NumberFormat version.
Another strange thing I've observed: After applying the date change per VBA, manual change of date format via the GUI is not possible anymore/seems to not have any effect.
What could be the cause of this and how could I achieve the really short date in VBA?
Try
format(date, "dd.mm.yy")
This will give you answer in string format if that is acceptable?
To give this some context:
Sub Datings()
Dim d As Date
d = Now
Debug.Print d, Format(d, "dd/mm/yy")
End Sub
Convert the text date to something recognised by Excel as a date (replace the . with / as you did originally, then just update the numberformat in the cell.
Sheet1.Range("A1") = Replace(Sheet1.Range("A1"), ".", "/")
Sheet1.Range("A1").NumberFormat = "dd.mm.yy"
After the suggestion to add more surrounding code by tom preston, I've tried Darren Bartrup-Cook's example code on the first cell as a very basic test. It did not work, so I used breakpoints to skip through all code before and after this piece of code.
This revealed that all the time, it was working correctly, but later other code reverted the formatting by calling Cells(1,1).Value = "" & Cells(1,1).Value on the correctly formatted cells. I did not notice it because in the GUI it was still shown as Format: Date, although internally it was overwritten.
The complete solution was therefore as simple as using
Range("A1:A" & lastRow).Replace What:=".", replacement:="/", LookAt:=xlPart
Sheet1.Range("A1:A" & lastRow).NumberFormat = "dd.mm.yy"
and not touching column A later on.
So, lesson learned: use many breakpoints to narrow down the problem and check that you do not shoot yourself in the foot later on.
Try this:
Option Explicit
Sub ReformatDate()
Dim C As Range
Dim V As Variant
For Each C In Selection
With C.Offset(0, 1)
.NumberFormat = "dd.mm.yy"
If IsDate(C) Then
.Value = C
Else
V = Split(C.Text, ".")
.Value = DateSerial(V(0), V(1), V(2))
End If
End With
Next C
End Sub
If your system recognizes the value as a date, then just reformat
If your system does NOT recognize the value as a date, then convert it
Split it using the dot as a delimiter
Convert the parts to a real date
write it back and format accordingly
You'll need to make appropriate modifications if you want it to overwrite, instead of putting the value in the next column; and if you want to use a more automatic method of selecting the range to convert.

Writing formula into an Excel Range with Option Strict On

Is it possible to write formulas across a range in Excel from VB.Net? I'm using a String array to hold a list of formulas that I would like to apply to an Excel range, instead of looping through and writing them one at a time.
This line is what I am attempting to use to write to a range:
xlWorkSheet.Range("AF" & intCurrentRow.ToString & ":AG" & intCurrentRow.ToString).Formula = formulas
The formula is being written to Excel, but Excel is actually displaying the formula, instead of the calculated value. If I write each formula to each cell like this:
xlWorkSheet.Range("AF" & intCurrentRow.ToString).Formula = formulas(0)
xlWorkSheet.Range("AG" & intCurrentRow.ToString).Formula = formulas(1)
It works perfectly fine and Excel displays the calculated values as it should. Almost seems like I'm missing a step but I haven't been able to find anything in my research.
As soon as I hit post I figured out the problem. Instead of using the .Formula property of an Excel.Range, you have to use the .FormulaArray property instead.
xlWorkSheet.Range("AF" & intCurrentRow.ToString & ":AG" & intCurrentRow.ToString).FormulaArray = formulas

Excel VBA - using left function with find

I'm trying to use the worksheet function "left" in conjunction with "find" to determine a string variable in VBA. However, excel interprets the find function as a VBA function, which yields an error message when resolving.
Below my code:
...
Else
MSa = MSa & Left(Range("D22"), Find(".", Range("D22")) - 1) & " " & Range("D25").Value & "."
Range("Q7").Value = MSa
End If
...
where MSa is a string. Can you please advise as to how I can best solve this problem?
Many thanks in advance.
The short answer is to use Application.WorksheetFunction.Find. However I wouldn't use that. Read below.
So on Sheet1 I enter abc.def into cell A1.
In another cell I enter =LEFT(A1,FIND(".",A1)-1) and get the value abc.
To call the same functions in VBA I would write
variable = Left(Sheet1.[A1],Application.WorksheetFunction.Find(".",Sheet1.[A1])-1)
Calling Excel worksheet functions from VBA does have some disadvantages though (it's harder to debug and slower). It's better to use the native VBA function InStr which works much like the worksheet function Find. The parameters are in a different order, but the results are the same. So I would write
variable = Left(Sheet1.[A1],InStr(Sheet1.[A1],".")-1)
The InStr method is the equivalent of the worksheet function Find, to use it:
MSa = MSa & Left(Range("D22"), InStr(Range("D22"), ".") - 1) & " " & Range("D25").Value & "."
In order to use the worksheet function Find you need to fully qualify it as follows;
Application.WorksheetFunction.Find
Using Find on its on and VBA will assume that it is a VBA function. Same goes for match.

When writing string to a cell, VBA inserts extra single quotation marks

I am trying to write this formula using VBA:
ActiveCell.Value = "=f(R[-1]C[0],Sheet1!" & ColumnLetter & i & ")"
Where ColumnLetter is some variable letter which my macro computes earlier, and f is some function, and i is some number.
The problem is that when I run this, the cell is given this instead: (if ColumnLetter = F, i = 16):
=f(R[-1]C[0],Sheet1!'F16')
but I want:
=f(R[-1]C[0],Sheet1!F16)
Why is VBA or Excel putting those single quotation marks around F16? It does not insert these extra quotation marks if I do not include R[-1][0] as an argument in my formula, but I need to include this.
Help much appreciated!
Its the combination of R1C1 and A1 addressing. You need to pick one method and use it for both parts.
Note that if you type =f(R[-1]C[0],Sheet1!F16) into a cell you will get an error for the same reason.
You say you need to use R1C1 style for the first address, but (assuming this is because you don't want
absolute address) you can use .Offset instead
ActiveCell.Value = "=f(" & ActiveCell.Offset(-1, 0).Address(False, False) _
& ",Sheet1!" & ColumnLetter & i & ")"
The Apostrophe means to Excel that it should interpret it as text.
Write it to ActiveCell.Formula. That way it is recognized as Formula.