My goal is to list the procedures exposed by the compiled VBProject, as visible in the Excel Macros menu or when typing a cell formula which makes appear availble UDF. A Solution that list any member (Enum, Sub, Function, etc.) of a CodeModule or of the full VBProject would be ideal.
I am not looking for a code parsing solution.
I could find old mentions of a way to do so in VB with the VBIDE library, which ( should / used to ) expose a VBIDE.Member class (accessible via the CodeModule.Members property) with those interesting properties:
Member.Name As String
Member.Type As VBIDE.vbext_MemberType
Member.Scope As VBIDE.vbext_Scope
Here is a bigger list, probably exhaustive:
Bindable
DisplayBind
StandarMethod
Browsable
HelpContextID
StaticCategory
Hidden
Type
Code
Location
Name
UIDefault
Collection
PropertyPage
VBE
DefaultBind
RequestEdit
Description
Scope
For a reason I don't understand, I cannot access them via VBA and the VBIDE library, they are not even listed as hidden members in the Object Browser.
Is there a way to get them?
Rubberduck (previously known as Mat's Mug here on Stack Overflow) aka Mathieu Guindon suggests it is possible though: https://rubberduckvba.wordpress.com/2016/03/24/a-reflection-on-vba-reflection/
Another idea: how to use Reflection with VBA?
Again, he suggests it is possible: https://rubberduckvba.wordpress.com/2019/04/10/whats-wrong-with-vba/
Note: the Rubberduck COM Add-In that integrates with the VBE, is able to do it (using .NET as I understand it)
Related
While trying to define an Any() function like python's, I found that I couldn't name anything "Any".
Attempting to name a Function, Sub, Const, or variable any will throw a syntax error and the VBA IDE will highlight it.
I know any not a particularly great name, but why is it throwing a syntax error? The only reason I could think of was that it might be a reserved keyword, but it's not.
VBA (Visual Basic for Applications) is not VB.NET, even though they share the same "Visual Basic" monikor and a similar syntax. (The linked documentation is for VB.NET, not VBA.)
VB6 and VBA in Microsoft Office (eg. Access, Excel) handles Any as a reserved word, and it cannot be used as an identifier:
You might also encounter errors if you use a reserved word to name a control, an object, or a variable. The error messages you receive don't necessarily tell you that a reserved word is the cause of the problem.
In VB.NET, however, there is no problem using Any as a variable name or other identifier:
Dim Any as String = "Hello world!" 'works just fine in VB.NET
To get an updated information to this question and comments, Microsoft states on their own page at https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/error-messages/as-any-is-not-supported-in-declare-statements:
The Any data type was used with Declare statements in Visual Basic 6.0
and earlier versions to permit the use of arguments that could contain
any type of data. Visual Basic supports overloading, however, and so
makes the Any data type obsolete.
Many software products have VBA incorporated into them. This includes Microsoft Office of course, but there are many 3rd-party products which have chosen to include VBA as well. A quick Google search turned up names like AutoCad, WordPerfect, PowerTerm, and ScriptWorx.
If one wishes to use #If/#EndIf directives to design a Sub or Function to work in multiple environments, how can VBA detect which one it's currently running in..??
This is a bit different than detecting if a product is installed, in which case CreateObject() and an ErrorHandler could be used. Also, checking the References collection would not be definitive, since a VBA project might have multiple references to products other than the current one. For instance, an Access project might have a reference to Excel, and vice-versa (as many of mine do).
Say, I want to write a function that returns the name of the current file. Here's some pseudo-code using Access and Excel as examples.
Public Function CurrentFilename() As String
#If EnvironmentName="Access" Then
CurrentFilename = Access.Application.CurrentProject.Fullname
#ElseIf EnvironmentName="Excel" Then
CurrentFilename = Excel.Application.ActiveWorkbook.Fullname
#Else
MsgBox "Current VBA software environment is not recognized."
#End If
End Function
Is this possible..? If so, what's the magic constant or function to replace 'EnvironmentName'..?
I believe your best bet will be the Application.Name property. I can't confirm it works for all VBA implementations, but it appears to be consistent with MS products.
From the MSDN reference: https://msdn.microsoft.com/en-us/library/office/aa221371(v=office.11).aspx
(Questions with an answer of NO are still useful; they're just not the solution to the problem. Answers say, no, there is no built-in, you have to implement the dialog for yourself...)
In VBA, (ms-word, or ms-excel, but seems like a generic operation) is there any way to simply a provide a collection to a built in dialog in order to prompt the user to select a value from a list of values?
I can't believe there's not a built in method to do this, it seems like a such a generic operation that could be coded once and everybody would re-use it. I can certainly hand code it, but why bother if it's already in the vba libraries someplace.
I've searched for a solution, but it does appear that the standard answer is to hand code it.
My aproach would be to create a Form, add a ListBox, Ok, Cancel and the ShowModal property.
To use it first set the ListBox RowSource according to what you need:
https://msdn.microsoft.com/en-us/library/office/ff196460.aspx
Then make it visible, manage Ok/Cancel and then use the ItemsSelect property (multiselect is possible):
https://msdn.microsoft.com/en-us/library/office/ff823015.aspx
Yup, no such thing.
Hand-code it, and keep it as part of your VBA "toolbox" - make yourself an add-in that other VBA projects can reference, so you can reuse the code without having to rewrite it every time.
Then export the code modules from your host document, upload them to a GitHub repository, and share your solution with the world so the next person looking for it doesn't need to implement it from scratch again.
The VBA standard library is rather limited, and beyond MsgBox there isn't much available in terms of built-in UI. That's just how it is.
The description
I am writing a couple of Excel UDFs in COM Servers. I'd like to get the standard help (Insert Function dialog) that you get when you press fx. Yes, I can see my COM Server listed in among the Category drop down, but
I also see Equals, GetHashCode, GetType, and ToString (which are fairly undesirable to expose to the Excel user),
selecting my COM Server brings up the *Function Arguments*[1] dialog with no argument information and no description of the function.
Here is the lameness that I get:
Insert Function dialog http://www.iwebthereforeiam.com/files/Insert%20function%20dialog.gif
Excel Function Arguments dialog http://www.iwebthereforeiam.com/files/Function%20Arguments%20dialog.gif
The question
Are there .NET attributes I could put on the methods to pass this through to Excel?
Can I provide a description of the function?
Can I provide a description of the parameters?
Can I provide a category name for my functions, so that I get something better than just the ProgID?
(I see that it looks sadly easy to do in ExcelDNA, but I am not going that route. Emulating govert's code [custom attributes, a loader of some sort, etc.] looks like it would be pretty hard.)
Additional background
If you have not done work with Excel + COM Servers before, here are some useful resources to get up to speed:
Previous StackOverflow questions:
How to get COM Server for Excel written in VB.NET installed and registered in Automation Servers list?
How Add a COM-Exposed .NET Project to the VB6 (or VBA) References Dialog?
Other resources:
Writing user defined functions for Excel in .NET
Build and Deploy a .NET COM Assembly
Writing Custom Excel Worksheet Functions in C#
Edit 2009-10-20 14:10
I tried out calling Application.MacroOptions in a Sub New().
No Sub New()
Semi-acceptable: Function is listed under category ProgID.
Shared Sub New()
Not acceptable: build-time error.Cannot register assembly "...\Foo.dll".
Exception has been thrown by the target of an invocation.
Sub New()
Not acceptable: category is not listed in Insert Function dialog.
I suspect this is a problem both for MacroOptions and for the more involved route recommended by Charles.
Edit 2009-10-20 14:55
On the plus side, Mike's recommendation to create an interface to implement did kill off the annoying extra methods that were exposed.
Edit 2009-10-20 15:00
This Microsoft article from early 2007 (via Mike's link) seems a rather complete answer on the topic:
Automation Add-ins and the Function
Wizard
Each Automation Add-in has its own
category in the Excel Function Wizard.
The category name is the ProgID for
the Add-in; you cannot specify a
different category name for Automation
Add-in functions. Additionally, there
is no way to specify function
descriptions, argument descriptions,
or help for Automation Add-in
functions in the Function Wizard.
1 Huh, a StackOverFlow bug. It looks like you cannot italicize a string inside an explicit HTML ul-list?
Some of this is easy to correct, other parts of it is rather hard. All of it is do-able, though, if you are willing to put the time in.
You wrote:
I also see Equals, GetHashCode,
GetType, and ToString (which are
fairly undesirable to expose to the
Excel user)
Yes, agreed, this definitely undesirable, but it can be prevented. This is occurring because your class is inheriting from 'System.Object', as all .NET classes do, and your default interface that is exposed to COM is including these members. This occurs, for example, if you use the 'ClassInterfaceAttribute', using the setting 'ClassInterfaceType.AutoDual'.
E.g. in C#:
[ClassInterface(ClassInterfaceType.AutoDual)]
In VB.NET:
<ClassInterface(ClassInterfaceType.AutoDual)>
The use of 'ClassInterfaceType.AutoDual' should be avoided, however, in order to prevent the members inherited from 'System.Object' from being exposed (as well as to prevent potential versioning issues in the future). Instead, define your own interface, implement the interface in your class, and then mark your class with the 'ClassInterface' attribute with a value of 'ClassInterfaceType.None'.
E.g., using C#:
[ComVisible(true)]
[Guid("5B88B8D0-8AF1-4741-A645-3D362A31BD37")]
public interface IClassName
{
double AddTwo(double x, double y);
}
[ComVisible(true)]
[Guid("010B0245-55BB-4485-ABAF-46DF4356DB7B")]
[ProgId("ProjectName.ClassName")]
[ComDefaultInterface(typeof(IClassName))]
[ClassInterface(ClassInterfaceType.None)]
public class ClassName : IClassName
{
public double AddTwo(double x, double y)
{
return x + y;
}
}
Using VB.NET:
<ComVisible(True)> _
<Guid("5B88B8D0-8AF1-4741-A645-3D362A31BD37")> _
Public Interface IClassName
Function AddTwo(ByVal x As Double, ByVal y As Double) As Double
End Interface
<ComVisible(True)> _
<Guid("010B0245-55BB-4485-ABAF-46DF4356DB7B")> _
<ProgId("ProjectName.ClassName")> _
<ComDefaultInterface(GetType(IClassName))> _
<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassName
Implements IClassName
Public Function AddTwo(ByVal x As Double, ByVal y As Double) As Double _
Implements IClassName.AddTwo
Return x + y
End Function
End Class
By making use of the 'ClassInterfaceAtribute' with a value of 'ClassInterfaceType.None', the inherited 'System.Object' memebers are excluded, because the class's interface is not made COM-visible. Instead, only the implemented interface ('IClassName' in this example) is exported to COM.
The above is also making use of the 'ComDefaultInterfaceAttribute'. This is not very important, and does nothing if you implement only one interface -- as in this example -- but it is a good idea in case you add an interface later, such as IDTExtensibility2.
For more detail on this, see:
(1) Managed Automation Add-ins by Andrew Whitechapel.
(2) Writing Custom Excel Worksheet Functions in C# by Gabhan Berry.
Ok, now to the hard part. You wrote:
Selecting my COM Server brings up the
Function Arguments[1] dialog with no argument information and no
description of the function.
Can I provide a description of the
function?
Can I provide a description of the
parameters?
Can I provide a category name for my
functions, so that I get something
better than just the ProgID?
The easiest approach here is to make use of the Application.MacroOptions method. This method enables you to provide a description of the function and specify which category under which you want it to be displayed. This approach does not allow you to specify any information for the functions parameters, unfortunately, but techniques that allow you to do so are very complicated, which I'll get to later. [Correction: The 'Application.MacroOptions' method only works for UDFs created via VBA and cannot be used for automation add-ins. Read on for more complex approaches to handle registration of UDFs containe in an automation add-ins -- Mike Rosenblum 2009.10.20]
Note that the help files for Excel 2003 and help files for Excel 2007 state that a string can be provided to the category argument in order to provide a custom category name of your choice. Beware, however, that the help files for Excel 2002 do not. I do not know if this is an omission in the Excel 2002 help files, or if this is a new capability as of Excel 2003. I'm guessing the latter, but you would have to test to be sure.
The only way to get your parameter information into the Function Wizard is to use a rather complex technique involving the 'Excel.Application.ExecuteExcel4Macro' method. Be warned though: many Excel MVPs have struggled with this approach and failed to produce a result that is reliable. More recently, though, it appears that Jan Karel Pieterse (JKP) has gotten it worked out and has published the details here: Registering a User Defined Function with Excel.
Skimming that article you'll see that it is not for the faint of heart. Part of the problem is that he wrote it for VBA / VB 6.0 and so all that code would have to be translated to VB.NET or C#. The key command, however, is the 'Excel.Application.ExecuteExcel4Macro' method, which is exposed to .NET, so everything should work fine.
As a practical matter, however, I vastly prefer using the 'Excel.Application.MacroOptions' approach because it is simple and reliable. It does not provide parameter information, but I have not yet had a strong need to motivate me to take on the 'ExecuteExcel4Macro' approach.
So, good luck with this, but my advice would be to utilize the 'MacroOptions', unless you are being paid by the hour. ;-)
-- Mike
Follow-up to Hugh's Replies
I tried out calling
Application.MacroOptions in a Sub
New().
No Sub New() Semi-acceptable: Function
is listed under category ProgID.
Shared Sub New() Not acceptable:
build-time error. Cannot register
assembly "...\Foo.dll". Exception has
been thrown by the target of an
invocation.
Sub New() Not acceptable: category is
not listed in Insert Function dialog.
I suspect this is a problem both for
MacroOptions and for the more involved
route recommended by Charles.
You can't use shared (aka "static") classes or constructors when exposing your classes to COM because COM has no knowledge of this concept and so it cannot compile -- as you found out! You might be able to apply a 'COMVisibleAttribute' with a value of 'False' to the shared constructor, to at least allow it to compile. But this wouldn't help you in this case anyway...
Trying to register your automation add-in via the automation add-in itself might prove tricky. I realize that this is desirable in order to keep it as a single, stand-alone component, but it might not be possible. Or at least this won't be easy.
The issue is that automation add-ins are demand loaded. That is, they are not really there until Excel attempts to access the first worksheet function from your automation add-in. There are two issues related to this:
(1) If you put your registration code within the constructor for your class, then, by definition, your function wizard information cannot exist until the function has been called for the first time.
(2) Your constructor might be executing when Excel is not ready to accept automation commands. For example, an automation add-in is typically demand-loaded when the user begins to type in the name of one of the user-defined functions (UDFs) defined in the automation add-in. The result is that the cell is in edit-mode when your automation add-in first loads. If you have automation code within your constructor during edit mode, many commands will fail. I do not know if the 'Excel.Application.MacroOptions' or 'Excel.Application.Excel4Macro' methods have a problem with this, but many commands will choke when trying to execute while the cell is in edit mode. And if the automation add-in is being loaded for the first time because it is being called while the Function Wizard is open, I have no idea if these methods can work right.
There is no easy solution to this if you wish to have your automation add-in to be completely stand-alone with no other support. You can, however, create a managed COM add-in that will register your automation add-in for you via 'Excel.Application.MacroOptions' or the 'Excel.Application.Excel4Macro' approach when Excel starts up. The managed COM add-in class can be in the same assembly as that of your automation add-in, so you still only need one assembly.
By the way, you could even use a VBA workbook or .XLA add-in to do the same -- use the Workbook.Open event in VBA to call the registration code. You just need something to call your registration code when Excel starts up. The advantage to using VBA in this case is that you could utilize the code from the Jan Karel Pieterse's Registering a User Defined Function with Excel article as-is, without having to translate it to .NET.
On the plus side, Mike's
recommendation to create an interface
to implement did kill off the annoying
extra methods that were exposed.
lol, I'm glad something worked!
This Microsoft article from early 2007
(via Mike's link) seems a rather
complete answer on the topic:
Automation Add-ins and the Function
Wizard
Each Automation Add-in has its own
category in the Excel Function Wizard.
The category name is the ProgID for
the Add-in; you cannot specify a
different category name for Automation
Add-in functions. Additionally, there
is no way to specify function
descriptions, argument descriptions,
or help for Automation Add-in
functions in the Function Wizard.
This is a limitation for the 'Excel.Application.MacroOptions' approach only. (My apologies, I had forgotten about this limitation of the 'Excel.Application.MacroOptions' method with respect to automation add-ins when I wrote my original answer, above.) The more-complex 'Excel.Application. ExecuteExcel4Macro ' approach, however, absolutely does work for automation add-ins. It should also work for .NET ("managed") automation add-ins as well, because Excel has no idea whether it is loading a COM automation add-in created via VB 6.0/C++ versus a managed COM automation add-in created using VB.NET/C#. The mechanics are exactly the same from the COM side of the fence because Excel has no idea what .NET is, or that .NET even exists.
That said, the 'Excel.Application.Excel4Macro' approach would definitely be a lot of work...
You could either use one of the .Net Excel systems such as ExcelDNA or ADDIN Express, or try to adapt one of the VBA/VB6 solutions: look at Laurent Longre's FunCustomise
http://xcell05.free.fr/english/index.html
or Jan Karel Pieterse's article at http://www.jkp-ads.com/Articles/RegisterUDF00.asp
which uses a function overloading hack.
I am new to MFC and I need to build a multi-language application that should be able to change the language at runtime.
AFAIK the common way for internationalization with MFC is to create resource-only DLLs. But there seems to be no simple way (that means, load DLL, call some function, and MFC updates all stuff automatically or something like that) to switch resource-DLLs at runtime, right?
So I will have to update all controls and so on manually. I already managed to load strings from the string-table of a DLL but since captions of controls like buttons are stored in the corresponding dialog (if I trust my resource-hacker :)) I thought there must be a way to load them and avoid storing an additional string in the string-table manually.
Or is there another way I don't know about?
If it makes any difference...I have to use MS embedded visual c++ 4
I work on a large localized MFC project. Here is our strategy:
A dictionary of key -> localized string, specific to each language. There are a few ways to implement this, more later.
Control IDs or captions in the dialog resource are set to the key used to look up the translation
Create a base CDialog, CFormView, etc and in at init call ::EnumChildWindows. In the callback, look up the translation and replace the control's caption with the translation.
For your dictionary, you can go a few ways.
If you want to rely on the built-in localized resource selection and string tables, you have to somehow match the control to the string ID. You can carefully ensure that the control ID matches the string ID, or you can ASCII-encode the ID in the caption and then use atoi to parse the int value.
You can forgo the built-in localized string table deal and maintain your own string -> string dictionary for each language. This lets you set the caption to the non-localized string in the resource which makes layout easier (although you'll still need to test in all languages.) It will require you to do your own "dependency injection" to make sure you load up the right dictionary. You want to be able to release updated/additional languages without rebuilding the core binaries.
If you don't want to require a restart of the application (by far the easiest solution, and the one you should use IMO), you can use resource dll's and recreate the main windows when the user switches languages. That way MFC will recreate the menus etc. in the new language. New dialogs will be displayed in the new language already anyway, from the moment you've switched the resource handle around.
I'm not sure how this relates to the embedded world, my experiences are from the desktop MFC.