How do I add a User Defined Function to Excel? - vba

I have a simple stored procedure that returns a Description and a Name when you give it a ID. I need to enable this inline in multiple Excel Sheets. Something like =ItemLookup('12345') that would then return the aforementioned info.
I have not done a lot with Excel programming and am simply wondering what my options are for tackling this. Is this a VBA thing or should this be an external DLL that I COM register? Both felt like overkill but then I realized I had no idea if they were. I really wanted to use VSTO for this but it sounds like that is not possible for Cell level UDF's without having to modifiy each Workbook with some VBA.

The best way to add UDF functions to Excel is with Excel-DNA (which is a free, open-source library I develop), and any of the .NET languages - VB.NET, C# and F# are all fine.
To get started you make a new 'Class Library' project in Visual Studio (any edition), install the 'Excel-DNA' package from the NuGet package manager, and add your code:
Public Module MyDataAccessFunctions
<ExcelFunction(Description:="Gets the Item from the database")>
Public Function ItemLookup(code As String) As String
' Here you have to do some work to get the data
Return "Hello " & code
End Function
End Module
Pressing F5 builds and starts Excel, and you're done - try putting =ItemLookup("Paladin") into a cell.
The resulting add-in is a single .xll file, which you can copy and use on any machine that has .NET without any installation or admin permissions. It works with old Excel versions too.
The best place for support (including absolute beginners' questions) is the Excel-DNA Google group.

You can use Excel to create a VBA UDF pretty easily, just hit alt+f8, right click your project in the project hierarchy on the left of the screen, and click add module.
Here is a quick Hello World function you can just paste into the module, then click play (or alt+f8 from worksheets)
sub test()
msgbox "helloworld"
end sub
If it was me, I would probably just create a list of the file paths that need to be searched. Then create a VBA macro that opens them in excel, searches them for the key, and returns other information from the row the key was found on.
You can open files with the 'Application.Open' method, simply pass in
the file path as an argument. 'Application.Open' returns a workbook
object.
Each workbook will have several worksheets, you can access them
through the workbook's 'Worksheet' property
Getting each used cell in a workbook can be done via looping through the 'UsedRange'
property in each worksheet
Get the value of a cell for comparison from the cell's 'value' property
Cells also have a 'row' property so you can find other items on the same row
If you're used to VBA you could get this running in less than an hour. But since you're just starting out it'll probably take a 3+ hours since you'll have more research/debugging

Related

Running functions from Excel Add-Ins in vba, is there an easier way than using Run "mySub"?

I'm trying to create my first Excel Add-In for Excel 2010. Most of it is working, it's running from the ribbon buttons, but I'm having trouble addressing the Add-In's subroutines from my workbook's VBA code.
According to this answer on SO, it should be possible to simply use the syntax:
mySub
or
Call mySub
But this causes the error "Sub or Function not defined". I've only managed to run them this way:
Run "mySub"
or
Application.Run("myAddIn.xlam!mySub")
Is there a way to include the Add-In so I can address it the easy way?
The Add-In is already checked in the Tools->Add-Ins list, and has a unique name (CalcFunctions) which is different from its file name (CalculationFunctions.xlam). The Add-In file is on a different disk and I'm working on a server, but I don't expect that that matters.
(Posted on behalf of the OP).
Turns out I'm just dumb. I only set a reference to the Add-In in the regular Excel window. I assumed that was what people meant by Tools->References->Add-Ins (I use Excel in Dutch and there's no "Tools" menu). Anyway, the solution was setting a reference in that menu in the VBA editor window.
Here's how to add a reference in VBA:
ThisWorkbook.VBProject.References.AddFromFile refPath
With refPath being the full path to the file.
To be able to add references you need to have permission to edit the VBA project. You can enable this in excel settings->trust center->macro settings. If you're on a company pc it's likely the administrator has to set these settings for you.

Prevent Word macro in Normal.dotm for some templates

I have a Normal.dotm file that contains an AutoNew macro.
This macro is automatically executed each time a new document is created using any other template.
Is there any way I can prevent this automatic behavior for a specific template?
I have a Word VSTO add-in running, so I can hook into Word's events, but so far I havn't found a way to prevent this.
I do know that I can prevent macro execution when using templates programmatically, for example like this:
' Disable auto-macros before opening document
wordApplication.WordBasic.DisableAutoMacros(1)
' Open document
newWordDocument = wordApplication.Documents.Open(template.FullName, ConfirmConversions:=False, [ReadOnly]:=True, AddToRecentFiles:=False, Revert:=True)
' Re-enable auto-macros
wordApplication.WordBasic.DisableAutoMacros(0)
But this solution doesn't work when the user uses a Word template from Windows explorer or the Open-dialog in Word, since in those cases I can't execute code before it's too late already.
Or can I?
I hope someone has a trick for me :-)
-
Edit: While trying different solutions, I discovered something that might help others in similar situations, though unfortunately it doesn't help me.
It seems that if a template contains a module containing an AutoNew (or AutoOpen for that matter), that local macro is executed instead of the one in Normal.dotm.
Example:
Normal.dotm contains the following macro:
Sub AutoNew()
MsgBox "Normal.dotm"
End Sub
Test.dotm contains the following macro:
Sub AutoNew()
MsgBox "Test.dotm"
End Sub
When executing Test.dotm the message "Test.dotm" is displayed, while the message "Normal.dotm" is not displayed.
If the AutoNew macro is removed from the Test.dotm template, the message "Normal.dotm" is indeed displayed.
So it is possible to easily override the auto-macros.
The local versions of AutoNew and AutoOpen can even be empty subs that do nothing. It still works.
This is not possible in my case though, since the template I use is generated by code, and cannot contain macros (because adding macros to templates programmatically requires the user to manually activate the option "Trust access to the VBA project object model", and that's something I cannot ask my customers to do for all users. It's also a security risk.)
Based on the workaround described in the "Edit" part of the question - providing a template with "empty" Auto-macros - it's possible to use the Open XML SDK to create a template and add the VBA project to it in order to provide this functionality. This approach avoids the user needing to allow access to the VBA project on his installation. The only "macro security" that could be triggered is that for not allowing macros to run. But since the client uses macros, anyway, this should not be a major obstacle.
The simplest method is to create as much of the basic template as possible in the Word UI and use this as a starting point.
Since you're unfamiliar with the Open XML SDK, the next step would be to create one (or more) templates in the Word UI using the basic template as the starting point, saving under a different file name.
You can then use Open XML SDK Productivity Tool to see the code required to generate any one of these files, as well as, using the Compare tool, the code for converting the basic template to the derived version. This should give you a decent start with the SDK and it's object model. Once you get a feel for how the Open XML SDK works, if you're familiar with Word's object model, using that provided by the SDK is relatively straight-forward as an effort was made to make it correspond as closely as possible to the "COM" object model.
The VBA project can be added later, but you can also include it in the basic template. That would be the simplest approach.
Include this "starting point" basic template as part of your solution, installing it as part of the solution.
Within the AutoNew macro you can check the AttachedTemplate property. Only if it is a template where you want to apply the cleaning you can execute the respective macros.
Sub AutoNew()
If ActiveDocument.AttachedTemplate <> "Normal.dotm" Then
Exit Sub
End If
' rest of the macro
End Sub
If you don't control the Normal.dotm you can put an empty AutoNew macro in your own templates. As Word only executes the auto macro in the closest context, the macro in the Normal.dotm file would not be executed.
If you don't control the other templates either, you can tell your users to hold down the SHIFT key while creating a document. This prevents the execution of the auto macro.
Probably it is best, however, if you ask the owner of the other system to find another solution that does not rely on polluting the Normal.dotm file.

Normal.dotm equivalent in Excel for referencing the same single VBA code

Just curiosity.
The only way I know so far is to create an add-in with code, put it in some trusted directory and hope it opens when you need it. The drawback is that it sometimes does not open together with application (e.g. I have a custom UDF in the add-in, I use it in the worksheet and an error is what I get, because the addin hasn't started). For this I have a button on my ribbon which calls a sub in the addin which does nothing, but then the addin is activated the UDF works.
Is there any other efficient way to reference code in another workbooks, like in Word we have normal.dotm template?
Indeed, Excel DOES have a common code file, similar in concept to Word's normal.dotm. It is called Personal.xlsb. I use it myself for common functions that I need for several linked yet independent spreadsheets.
Using Personal.xlsb has some disadvantages too, so you'll have to decide if that works better than the Add-in approach. Note that Personal.xlsb works best when its just one person needing common functions across spreadsheets; its not well suited for multi-user access to the spreadsheets in an enterprise environment.
Some useful links are below to get started. Also just google search "excel Personal.xlsb" and you will find a lot more information:
http://www.rondebruin.nl/win/personal.htm
http://chandoo.org/wp/2013/11/18/using-personal-macro-workbook/
To create an equalevant to normal.dot in Excel do this (at least ver. 2016):
Record a macro from the Developer tab (you likely have to enable this tab first)
This will create the file %appdata%\Microsoft\Excel\XLSTART\PERSONAL.XLSB which is Excel's equalevant to normal.dot
Now unhide the hidden workbook called "PERSONAL.XLSB"
Press Alt+F8 or Alt+F11 to edit the VBA code
Extra: VBA example for SaveAs:
Application.Dialogs(xlDialogSaveAs).Show

Excel VBA - Run macro on Application open

I've got this Design Template I want to apply to the application by default whenever it's opened. But a document needs to be open in order to apply a Design template. So I have to target some handlers whenever a new or existing workbook is opened/created.
So which approach can I take here?
I think you can use the Workbook_open event. It sounded like that would be sufficient for the behavior that you want, but since you want your macros to be global, you might need to make them into an add in for them.
The add-in approach is best for applying to existing workbooks.
If you're looking to have a standard design template applied to each new workbook, though, I suggest you create a template.
To do this:
Create a new empty workbook
Apply the design template
Save the file as Book.xlt (for Excel 2003; Book.xltx for versions 2007, 2010) in the following folder: %appdata%\Microsoft\Excel\XLSTART\
Each time you create a new workbook (by opening Excel or hitting the "New" button), it'll be generated from this template file.

XLAM / XLA Addins: is there a better way?

This post is about installing XLAM's without creating links. (Everyone hates links). Consider the trivial addin:
Public Function UDF_HELLO(x)
UDF_HELLO = "Hello " & x
End Function
Put this code and nothing else into a Module and save as "Hello.xlam" on the Desktop (and NOT in the default excel addins folder). Next, while HELLO.XLAM is still open, create a new XLSX workbook with the formula
=UDF_Hello("world")
in cell A1, which simply displays "Hello world" in that cell. Save the workbook and exit Excel. Now, if you reopen the workbook without the XLAM, Excel will complain about "links to other sources ...". Whether you click "Update" or "Don't Update", Excel will mangle the formula in cell A1 like this:
='C:\Documents and Settings\tpascale\Desktop\Hello.xlam'!UDF_Hello("world")
Very often this "forced-linkage" is NOT desirable. In my computing environment there is a lot of ad-hoc analysis and it makes no sense to impose an install regimen on every XLAM we throw together to solve the problem of the day. I just want to hand out XLAM files to users and let those users open them when they need them, WITHOUT having to worry about the slightest mis-step causing their formulas to get mangled.
QUESTION:
Is there a way to instruct Excel to NEVER construct external links for UDFs, and simply to use UDFs if they're loaded and return #VALUEs otherwise ?
I don't know of a way around this with .xla/.xlam add-ins.
But this issue does not occur with .xll add-ins.
These can be created in C using the Excel 2010 SDK, or in managed languages like VB.NET or C# using the free Excel-DNA library.
(Disclaimer: I'm the developer of Excel-DNA. This issue is one of the reasons I went with the .xll interface for making managed UDF add-ins.)
You can have them open the .xla file and have an Auto_Open procedure install the add-in.
http://www.vbaexpress.com/kb/getarticle.php?kb_id=693
After excel closes you can have the add-in uninstall itself.
oAddIn.Installed = False
You can give your add-in a setting for the user to not uninstall after every use by using a worksheet named something then have cell A1 equal to true or false.
I haven't tested this but hopefully it works for you.
This should work to resolve your issue though it does not instruct Excel regarding external links. I have tested it myself by creating the XLAM, saving it to my desktop, installing it in the Excel add-ins and then using it on a new workbook.
Steps:
Once you have saved the add-in, close it.
Go to Excel Options-->Add-Ins
In the Manage drop-down select Excel Add-ins and press 'Go'
In the 'Add-Ins'dialogue that appears click 'Browse' and navigate to
the add-in you just created. Select it and hit 'Ok'
If prompted to save the add-in in the add-ins folder, select 'No'.
Selecting 'Yes' may cause an error if the add-in file suffix does
not match the version of Excel being used.
Your add-in should appear in the 'Add-Ins available' scrollbox,
check its box and hit 'Ok'
Your add-in should now be active whenever you open Excel.
Test this by opening a new workbook and try using your UDF.
Best,
I usually solve this problem by:
Saving an XLA/XLAM file (outside Personal folder, of course)
Connect to it in Tools - Addins
Write pseudo macros in your current Excel file that links to those macros / functions in the XLA/XLAM file.
See the detailed instructions in my reply here.