Calling Worksheet functions from vba in foreign language versions of Excel - vba

The following code snipet run OK in an English Language verion of Excel, however when attempting to run this code in the same workbook in a Portuguese version of Excel it errors out.
' Add color bars on every other row - attempt to make list
' easier to read.
' "PlaceAt" is a worksheet range passed into the function
With Range(PlaceAt.offset(1, 0), PlaceAt.offset(i + 1, 7))
.FormatConditions.Add Type:=xlExpression, Formula1:="=MOD(ROW(),2)=1"
.FormatConditions(1).Interior.ColorIndex = 20
End With
I believe that the problem is, in Portuguese, the ROW function is spelled LIN (not sure what the MOD function would be) and that since the function is inserted using vba, Excel's translation function does not have the opportunity to translate the function names as it normally would when opening the document.
Any ideas?

Yes FormatConditions formulas must use the local format.
My workaround is to write the wanted formula into a cell an then get the FormulaLocal of this cell which should be the exact translation in your language:
Dim tmpCell As Range
Set tmpCell = Range("IV1")
tmpCell.Formula = "=mod(row(),2)=0"
.FormatConditions.Add(xlExpression, Formula1:=tmpCell.FormulaLocal)
Don't know if there is a cleaner solution, but if so I'd like to know, so please share...

I found a cleaner solution in a different question:
Names.Add "translate", RefersTo:="=MOD(ROW(),2)=1" ' Generic separator (,) and function name (ROW).
.FormatConditions.Add Type:=xlExpression, Formula1:=Names("translate").RefersToLocal ' Local separator (;) and function name (LIN).
Names("translate").Delete

Excel 2019 - 365 version still has this issue present. I found it occurring when using the VBA in conjunction with MS Excel Named Range Manager.
Example of error I am facing, which I fixed modifying Pragmateek's 2013 answer.
In the Named Range Manager exists a named range that uses the OFFSET function.
When calling this named range and using it in VBA to set another range variable USING the Application.Intersect method results in object errors because in VBA the MS Excel named range is acquired as a string. The intersect method then attempts to determine the intersect of this string and another range. This of course fails due to OFFSET working in English MS Excel versions, however not in Portuguese / Spanish .... And I imagine other languages.
Fixing this involved:
' Names!XXXXX = "=OFFSET('DummySheet'!$AO$7,0,0,1,'DummySheet'!$AM$2)""
Sub evaluate()
...
xAxis = Names!XXXXXX
Dim tempvar As Range: Set tempvar = Range("C1")
tempvar.Formula = xAxis ' set the range to hold the formula
tempvar.Formula = tempvar.FormulaLocal ' set the range to hold the formula in the local language
Set rng = Application.Intersect(tempvar.EntireColumn, yAxis.RefersToRange.EntireRow)
This worked well. However if ANYONE has a cleaner solution, please post suggestions. Cheers!

Related

Vlookup data in certain external excel file using a function

I use a lot of Vlookup to find prices in a certain file (c:/pricelist.xlsx), using the product code. I would like to simplify it by creating a new VBA function, but it does not work.
Function findprice(codes)
Dim price
Dim pricebook As Workbook
Dim pricesheet As Worksheet
Dim pricerange As Range
Dim coderange As Range
Set pricebook = Workbooks("c:\info\pricelist.xlsx")
Set pricesheet = pricebook.Sheets("Sheet2")
Set pricerange = pricesheet.Range("FF1:FF20000")
Set coderange = pricesheet.Range("H1:H20000")
findprice = Application.Index(pricerange, Application.Match(codes, coderange, 0))
End Function
I can't say for sure since your question didn't include any sample data or explanation of what/why you're trying to do, but I suspect you're over-complicating this is a few ways.
You don't need a custom function to call a worksheet function from the worksheet. Just use the worksheet functions instead.
In this case VLookup is easier and saves a step (as you suggested in your title.)
Instead of referring to huge ranges of two cells, just refer to the whole columns.
Referring to cells in another Excel file is just like any other formula that refers to another file. (More infor here)
You should be able to use something like this on the worksheet:
=VLOOKUP(A1,'c:\info\pricelist.xlsx'!$H:$FF,155,FALSE)
...where A1 is the value to match (ie., codes).
If you do need this is VBA for some reason, it's easy to adapt just like your sample function.
More Information:
Office Support : Create or change a cell reference
Office Support : Create an external reference (link) to a cell range in another workbook

Add-in function Range.Delete method fails

First, I would like to apologize for my bad language, I hope you'll understand my problem.
I looked after a way to get generic function in Excel and I found the add-in method. So I tried to use it in developping custom functions whitch may help me in my everyday work. I developed a first function which work. So I thought that my add-in programmation and installation was good. But when I try to implement worksheet interractions nothing appened.
My code has to delete rows identified by a special code in a cell of those ones. I get no error message and the code seems to be totally executed. I tried other methods like Cells.delete, Cells.select, worksheet.activate or range.delete but I encounter the same issue.
This is my function's code :
Public Function NotBin1Cleaning(rSCell As Range) As Integer
Dim sht As Worksheet
Dim aLine As New ArrayList
Dim iLine As Integer
Dim iCpt As Integer
Dim iFail As Integer
Dim i As Integer
Dim oRange As Object
Set sht = rSCell.Parent
iLine = sht.Cells.Find("*PID*").Row
For Each rCell In Range(sht.Cells(iLine, 1), sht.Cells(sht.Cells(iLine, 1).End(xlDown).Row, 1))
If sht.Cells(rCell.Row, 2) > 1 Then
iLine = rCell.Row
iCpt = iLine + 1
Do Until sht.Cells(iCpt, 2) = 1
If Not sht.Cells(iCpt, 1) = rCell Then Exit Do
iCpt = iCpt + 1
Loop
If sht.Cells(iCpt, 1) = rCell Then
sht.Range(sht.Cells(iLine, 1), sht.Cells(iCpt - 1, sht.Cells(iCpt, 1).End(xlToRight).Column)).Delete xlUp
iFail = iFail + 1
End If
End If
Next
NotBin1Cleaning = iFail
End Function
it's the line:
sht.Range(sht.Cells(iLine, 1), sht.Cells(iCpt - 1, sht.Cells(iCpt, 1).End(xlToRight).Column)).Delete xlUp
which isn't producing any effect.
I would be really thankful for your help.
This issue is described on the Microsoft support site as part of the intentional design
section below, more detail here (emphasis mine)
A user-defined function called by a formula in a worksheet cell cannot change the environment of Microsoft Excel. This means that such
a function cannot do any of the following:
Insert, delete, or format cells on the spreadsheet.
Change another cell's value.
Move, rename, delete, or add sheets to a workbook.
Change any of the environment options, such as calculation mode or screen views.
Add names to a workbook.
Set properties or execute most methods.
The purpose of user-defined functions is to allow the user to create a
custom function that is not included in the functions that ship with
Microsoft Excel. The functions included in Microsoft Excel also cannot
change the environment. Functions can perform a calculation that
returns either a value or text to the cell that they are entered in.
Any environmental changes should be made through the use of a Visual
Basic subroutine.
Essentially, this means that what you're trying to do won't work in such a concise manner. The limitation, as I understand from further reading, is because Excel runs through cell equation/functions several times to determine dependencies. This would lead to your function being called two or more times. If you could delete rows, there is the potential of accidentally deleting more then twice the numbers of rows intended, due to the excess number of runs.
However, an alternative could be to have the function output a unique string result that shouldn't be found anywhere else in your workbook (maybe something like [#]><).
Then you can have a sub, ran manually, which finds all instances of that unique string, and deletes those rows. (Note: if you included any of the typical wildcard symbols in your string, you will have to precede them with a ~ to find them with the .Find method.) You can even set up the sub/macro with a shortcut key. Caution: if you duplicate a shortcut key Excel already uses, it will run the macro instead of the default. If there will be other users using this workbook, they could experience some unexpected results.
If you decide to go this route, I would recommend using this line:
Public Const dummy_str = "[#]><" ' whatever string you decided on.
in your module with your code. It goes outside any functions or subs, so it'll be global, and then you can refer to the const just as you would any other string variable.
When you write:
sht.Range(sht.Cells(iLine, 1),....
This first parameter should be the row number, but you're refering to a Cell instead. You should change sht.Cells(iLine, 1) for iLine.
BUT
Instead of all this, its easier to use the method Row.Delete:
Rows(iLine).EntireRow.Delete

Using Excel Formula functions (ERF, ERFC) in Excel VBA code?

I'm trying to use the error function and the complimentary error function in my program. Neither are working. I keep getting the error Compile Error: Sub or Function not defined. However, if I go into a cell and manually try to use the error function, it works.
Why is that? How can I use it in my actual code?
The error function is ERF(x) and the complimentary error function is ERFC(x).
Here's an example of things that don't work:
Sub SeriouslyWHYIsntThisWorking()
x = 3
Range("A1") = Erf(x)
End Sub
Even this doesn't work:
Sub PleaseWork()
Range("A1") = Erfc(1)
End Sub
But if I went into Excel and typed =ERF(3) or =ERFC(1) into a cell, it'll work.
I'm very new to this and probably missing something incredibly simple. Help would be greatly appreciated!
Do you have the Analysis Toolpak for VBA add-in installed/referenced? (Look for atpvbaen.xls)
The ERF function is part of that add-in, and there are two versions of it (one for use in Excel functions, and one for VBA), and you'll need the right one set up and referenced by your project to be usable.
The add-ins are standard from MSFT, but not always set up by default. If you can use it in Excel normally, then you've already set up at least the Excel version. So using it all like it looks like you want to do will require the add-in, regardless of how you implement/use that function. Meaning, if you want to share this with anyone else, they will need the add-in installed/activated.
To link together this answer with the others provided (which are equally accurate and correct), either setting a cell value with
Range("A1").value = Application.WorksheetFunction.ERF(x)
or setting a cell formula with
Range("A1").Formula = "=Erfc(" + x + ")"
will require the end-user using the add-in.
To use a worksheet formula in vba, you need to put Application.WorksheetFunction. in front of it.
Some functions do have vba equivalents, but (as far as I know) not in the case of erf and erfc
Try this:
Sub ThisShouldWorkNow()
x = 3
formula = "=Erfc(" + x + ")"
Range("A1").Formula = formula
End Sub
Totally untested, since I don't have Excel on my Linux machine... But I think I'm getting the point across -- you need to use the .Formula property of the Range object.
There's more information here:
http://msdn.microsoft.com/en-us/library/office/gg192736.aspx

Using VBA for Word, how do I create a range of table cells?

I'm trying to learn how to handle Range objects in Word VBA with regards to MS Word tables.
Using the Range object help, it would seem I can create a range of cells as long as the cells are contiguous, however I cannot seem to get the syntax for specifying the Start and End points of the range using cells.
For example:
Set rngCells = myTable.Range(Start:=<cell>, End:=<cell>)
I'm not sure what to put in for to indicate the cell to start or the cell to end with. Can someone give me a clue? :)
Edit: I've already created the table from scratch -- I'm trying to use a range of cells for some of the rows in the middle to apply formatting to them. In particular, I'm trying to see if this can be done without using Selection.
I found the answer I was looking for:
Set myCells = ActiveDocument.Range(Start:=ActiveDocument.Tables(1).Cell(1, 1).Range.Start, _
End:=ActiveDocument.Tables(1).Cell(1, 1).Range.End)
I did not realize the Range object was from the Document object, not the Table object.

How to get/set unique id for cell in Excel via VBA

I want to have/define a unique id for each data row in my Excel data sheet - such that I can use it when passing the data onwards and it stays the same when rows are added/deleted above it.
My thoughts are to use the ID attribute of Range (msdn link)
So, I have a user defined function (UDF) which I place in each row that gets/sets the ID as follows:
Dim gNextUniqueId As Integer
Public Function rbGetId(ticker As String)
On Error GoTo rbGetId_Error
Dim currCell As Range
'tried using Application.Caller direct, but gives same error
Set currCell = Range(Application.Caller.Address)
If currCell.id = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.id = Str(gNextUniqueId)
End If
rbGetId = ticker & currCell.id
Exit Function
rbGetId_Error:
rbGetId = "!ERROR:" & Err.Description
End Function
But this fails at the line mentioned with
"Application-defined or object-defined error"
I thought perhaps its one of those limitations of UDFs, but I also get the same error if I try it from code triggered from a ribbon button...
Any other suggestions on how to keep consistent ids - perhaps I should populate the cells via my ribbon button, finding cells without IDs and generating/setting the cell value of those...
EDIT:
As Ant thought, I have the sheet protected, but even in an unlocked cell it still fails. Unprotecting the sheet fixes the problem.... but I have used "Protect UserInterFaceOnly:=True" which should allow me to do this. If I manually allow "Edit Objects" when I protect the sheet it also works, but I don't see a programmatic option for that - and I need to call the Protect function in AutoOpen to enable the UserInterfaceOnly feature...
I guess I need to turn off/on protect around my ID setting - assuming that can be done in a UDF... which it seems it cannot, as that does not work - neither ActiveSheet.unprotect nor ActiveWorkbook.unprotect :(
Thanks in advance.
Chris
Okay...
It does appear that if the sheet is locked, macros do not have write access to low-level information such as ID.
However, I do not think it is possible to unprotect the sheet within a UDF. By design, UDFs are heavily restricted; I think having a cell formula control the sheet protection would break the formula paradigm that a cell formula affects a cell only.
See this page on the Microsoft website for more details.
I think this limits your options. You must either:
give up sheet protection
give up the UDF, use a Worksheet_Change event to capture cell changes and write to ID there
use a UDF that writes the ID into the cell value, rather than save to ID
The UDF approach is fraught with problems as you are trying to use something designed for calculation of a cell to make a permanent mark on the sheet.
Nonetheless, here's an example of a UDF you can use to stamp a "permanent" value onto a cell, which works on unlocked cells of a protected sheet. This one only works for single cells (although it could be adapted for an array formula).
Public Function CellMark()
Dim currCell As Range
Set currCell = Range(Application.Caller.Address)
Dim myId As String
' must be text; using .value will cause the formula to be called again
' and create a circular reference
myId = currCell.Text
If (Trim(myId) = "" Or Trim(myId) = "0") Then
myId = "ID-" & Format(CStr(gNextUniqueId), "00000")
gNextUniqueId = gNextUniqueId + 1
End If
CellMark = myId
End Function
This is quite flawed though. Using copy or the fillbox will, however, retain the previous copied value. Only by explicitly setting cells to be a new formula will it work. But if you enter in the formula into the cell again (just click it, hit ENTER) a new value is calculated - which is standard cell behaviour.
I think the Worksheet_Change event is the way to go, which has much more latitude. Here's a simple example that updates the ID of any cell changes. It could be tailored to your particular scenario. This function would need to be added to every Worksheet the ID setting behaviour is required on.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim currCell As Range
Set currCell = Target.Cells(1, 1)
Dim currId As String
currId = currCell.ID
If Trim(currCell.ID) = "" Then
Target.Parent.Unprotect
currCell.ID = CStr(gNextUniqueId)
Target.Parent.Protect
gNextUniqueId = gNextUniqueId + 1
End If
End Sub
Last note; in all cases, your ID counter will be reset if you re-open the worksheet (at least under the limited details presented in your example).
Hope this helps.
Concur with Ant - your code works fine here on Excel 2003 SP3.
I've also been able to use:
Set currCell = Application.Caller
If Application.Caller.ID = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.ID = Str(gNextUniqueId)
End If
Aha! I think I have it.
I think you're calling this from an array formula, and it only gets called ONCE with the full range. You can't obtain an ID for a range - only a single cell. This explains why Application.Caller.ID fails for you, because Range("A1:B9").ID generates an Application-defined or object-defined error.
When you use Range(Application.Caller.Address) to get the "cell" you just defer this error down to the currCell.ID line.
I think we may have a few issues going on here, but I think they are testing issues, not problems with the code itself. First, if you call the function from anything other than a Cell, like the immediate window, other code, etc. Application.Caller will not be set. This is what is generating your object not found errors. Second, if you copy/paste the cell that has the function, they you will by copy/pasting the ID too. So wherever you paste it to, the output will stay the same. But if you just copy the text (instead of the cell), and then paste then this will work fine. (Including your original use of Application.Caller.)
The problem is with Application.Caller.
Since you are calling it from a user defined function it is going to pass you an error description. Here is the remark in the Help file.
Remarks
This property returns information about how Visual Basic was called, as shown in the following table.
Caller - Return value
A custom function entered in a single cell - A Range object specifying that cell
A custom function that is part of an array formula in a range of cells - A Range object specifying that range of cells
An Auto_Open, Auto_Close, Auto_Activate, or Auto_Deactivate macro - The name of the document as text
A macro set by either the OnDoubleClick or OnEntry property - The name of the chart object identifier or cell reference (if applicable) to which the macro applies
The Macro dialog box (Tools menu), or any caller not described above - The #REF! error value
Since you are calling it from a user defined function, what is happening is Application.Caller is returning a String of an error code to your range variable curCell. It is NOT causing an error which your error handler would pick up. What happens after that is you reference curCell, it's not actually a range anymore. On my machine it tries setting curCell = Range("Error 2023"). Whatever that object is, it might not have an ID attribute anymore and when you try to set it, it's throwing you that object error.
Here's what I would try...
Try removing your error handler and see if VBA throws up any exceptions on Range(Application.Caller.Address). This won't fix it, but it could point you in the right direction.
Either through logic or Application.ActiveCell or however you want to do it, reference the cell directly. For example Range("A1") or Cells(1,1). Application.Caller.Address just doesn't seem like a good option to use.
Try using Option Explicit. This might make the line where you set curCell throw up an error since Range(Application.Caller.Address) doesn't look like it's passing a range back, which is curCell's datatype.
I have found that if I protect the sheet with "Protect DrawingObjects:=False", the UDF can set the Id. Strange.
Thanks for all the help with this.