How do you instantiate an Excel VBA AddIn variable - excel

In the references window, you can browse to and set a reference to a custom AddIn. For purposes of discussion let's say it is named MyAddIn.xlam. I changed the project name to MyAddIn and it resides in the standard AddIns path. So I have successfully done that and then have defined an associated global variable as below in a different Excel workbook:
Private m_MyAddIn As MyAddIn.thisworkbook
How and when do I instantiate the variable I have defined? It would be simple if I could cast the associated Application.AddIns collection item to my specific AddIn but that isn't an option.
Within the MyAddIn code, I have tried defining a public property ParentWorkbook that just returns ThisWorkbook. I have also tried doing the same in a public class defined in MyAddIn. But I cannot figure out how to instantiate the variable.
Here is what I am trying to do:
MyAddIn raises a custom event MyEvent whenever a Workbook is opened or a new sheet is created
One or more custom xlsbs MyCustom1.xlsb, MyCustom2.xlsb will handle the event
Because the xlsbs are looking for an event, I need to be able to define a WithEvents MyAddIn variable in the xlsbs to be able to handle them
So for example, a PivotTable xlsb might do some custom actions based on PivotTable events. These could all be setup in the xlsb once it received notifications that a new file was opened. It could check to see if a PivotTable was present and skip all others, etc.
Why am I trying to do it this way?
I'm trying to avoid a bunch of chatter. If each xlsb uses the application object to check for open files, etc. they will all run every time a file is opened in Excel
My thought is that only MyAddIn will check that event and then raise the custom events as appropriate so the xlsbs.
I hope this helps.
Thanks for any ideas and/or suggestions.

I think you should trap App level Workbook.Activate events like explained here: https://www.exceltip.com/events-in-vba/how-to-create-application-level-events-in-excel-vba.html
Main points from that page:
Insert a class module. Name it as you want. I named it MyAppEvents.
Define an event variable of Application type with keyword WithEvents.
Initialize this event in class_initialize() subroutine.
Now define the events you want to use. From the top-left drop-down, select the event object.
You can then set wb = ActiveWorkbook and do what you want with it.

Related

Programmatically list Excel VBA UserForm Event Procedures

First of all, I already found out how to list default event names of Excel UserForm Controls using Typelib Info referenced as TLBINF32.DLL.
I shared it here.
It is a part of my ongoing project to create codeflow diagrams and possibly VBA code obfuscator early alpha version available on GitHub.
The issue now, is, with trying to list Event Procedures of the UserForm itself:
Unlike listing UserForm Controls using TLI-ClassInfoFromObject(UserForm1.CommandButton1), I found out the hard way that I need to use TLI-TypeLibInfoFromFile("FM20.dll") because if I use ClassInfoFromObject(UserForm1), there is an error.
I also found out that I can use TLI-ClassInfoFromObject(ThisWorkbook.VBProject.VBComponents("UserForm1").Designer).
Using FM20.dll and .Designer methods both get 16 event procedure names.
I can manage/understand up to this far.
Then I manually counted the UserForm's event procedure dropdown list in Excel VBA Editor and found that there are 22 UserForm event procedures.
But after I ran the following code using TLI, the resulting list contains 16 event procedures only, as shown in the first photo.
I am using MSForms class/object from FM20.dll.
The code is as follows: (Needs Reference to TypeLib Information library at C:\Windows\SysWow64\TLBINF32.DLL)
Sub listUFEvents()
Dim t As TLI.TLIApplication
Set t = New TLI.TLIApplication
Dim ti As TLI.TypeLibInfo
Set ti = t.TypeLibInfoFromFile("C:\Windows\SysWOW64\FM20.dll")
Dim cci As TLI.CoClassInfo
Dim mi As TLI.MemberInfo
Dim i As Integer
For Each cci In ti.CoClasses
If cci.Name = "UserForm" Then
For Each mi In cci.DefaultEventInterface.Members
i = i + 1
Debug.Print CStr(i), mi.Name
Next mi
End If
Next cci
End Sub
6 events were not listed including UserForm.Activate and UserForm.QueryClose etc.
My question is, why those 6 event procedures were not listed?
If these event procedures were not picked up from the TypeLib in FM20.dll, where do they come from?
Object Browser also shows only 16 procedures.
Or did I do something wrong in using TLI library?
I admit that I am pretty far from becoming/being an expert.
I am still trying to understand how TypeLib Info works.
How do I get all 22 event procedure names?
I need the event procedure names to differentiate user-defined procedures from the event procedures in a UserForm CodeModule in my project which is about listing the procedure calls in a VBA project.
I can't find anybody asking the same question anywhere either.
Edit:08FEB2021
Added project userform image + zoomed one, to clearly show and better explain the question
In the following zoomed photo,
A->, UserForm controls' default event procedures' names
B->, UserForm default event procedure names
C->, User-defined Sub+Function residing inside UserForm CodeModule
Above image is zoomed for better clarity.
***********************************************************
I want to separate B from C like I did with A.
***********************************************************
So that we can know that default event procedures were NOT called directly.
The image below is presented to get a better idea of why I am needing to list the names of the UserForm event procedures.
Apology
I apologize for being a bit political (if you wanna call a function name politics!).
I truly am sorry to have to involve a bit of politics here.
I am not asking for anything except your awareness. Give me a negative vote if you want, for doing what a man needs to do when his country needs him most.
I just want to do something, however small it may be, as a citizen of Myanmar.
Normally, I am not interested in politics but the current situation calls for me to let the world know that Myanmar is currently under siege by a Military coup which forcibly removed democratically elected government and currently oppressing its citizens' rights.
Please bear with me and I hope you all can understand and empathize with me.
Thanks for your kind understanding.
The TLBINF32.DLL does what it is designed (coded) to do, so there is nothing you can do to make it recognize the five events related to MSForm's multiple "open/close" steps, or the MSForm's own window's Resize events. If you want to use them in your code you can, for example, create string constants for the event names you need, or keep them in a lookup table in a worksheet, or in a text file you will read, or whichever other way that suites your project.
As for listing Excel UserForm Controls, you do not need TLBINF32.DLL at all because it's already built (obviously, since TLBINF32.DLL just taps into this) into the MSForm class. To test this, please create a form, name it, say, "Form1", throw in a few controls and run the following simple subroutine from anywhere within the VBA project:
Sub ExamineControls()
Dim ctl As Control
For Each ctl In Form1.Controls
Debug.Print ctl.Name, ctl.Top, ctl.Visible ' anything else you need?
Next
End Sub
It does not matter whether the Form1 is opened or not.
I hope this is helpful.

Rubberduck VBA Code Inspections: Member 'x' has a 'VB_VarHelpID' attribute with value '-1', but no corresponding annotation

I am developing an Excel VBA project using the "Worksheet abstraction\Worksheet Proxy" technique, described in the There is no worksheet article and followed-up in my question here. My VBA code is structured in the MVP design pattern and I have written as much OOP code as possible. Rubberduck's "Code Inspections" feature has been of great help along the way, however in more recent versions (I think since v2.4.1.4*** but can't exactly put my finger on the exact version) I started to consistently get a couple of "Rubberduck Opportunities" and "Code Quality Issues" warnings that I can't quite make sense of.
The first one, as mentioned in the title, is the Member 'x' has a 'VB_VarHelpID' attribute with value '-1', but no corresponding annotation Rubberduck Opportunity. I get this one whenever I am declaring an event-exposing Worksheet (or other event-exposing object, i.e. a CommandButton) inside a "WorksheetProxy" class. Both lines in the below code would trigger this error:
' This code sits in my ProcessMasterProxy class
Private WithEvents sheet As Worksheet
Private WithEvents buttonHideOwnerToAvailability As CommandButton
Then I would get the same error in my "Presenter" class whenever I declare an instance of an event-exposing "SheetProxy" class, or an event-exposing UserForm:
' Here I am declaring an instance of the ProcessMasterProxy class from the above snippet
Private WithEvents assetPrx As ProcessMasterProxy
Private WithEvents view As ChecklistPopup
Rubberduck's Code Inspections offers me two actions for such errors:
1. "Add attribute annotation" which inserts the '#MemberAttribute VB_VarHelpID, -1 line above the declaration AND
2. "Remove attribute" which seems to do nothing, the error remains after clicking it.
I would like to know what implications does the "Add attribute annotation" fix bear on my VBA project and whether I should apply it or rather look to change something in my code to avoid getting the error altogether.
Similarly, I am concerned about a related error I get in the "Code Quality Issues" section: To the variable 'sheet' of declared type 'EXCEL.EXE:Excel.Worksheet' a value is set assigned with the incompatible declared type 'ProcessMgmt.ProcessMaster' which is related to the following code in the "ProcessMasterProxy" class from the first snippet above:
Private WithEvents sheet As Worksheet
Private WithEvents buttonHideOwnerToAvailability As CommandButton
Private Sub Class_Initialize()
***Set sheet = ProcessMaster***
Set buttonHideOwnerToAvailability = ProcessMaster.btnHideAssetOwnerToAvailability
End Sub
with "ProcessMaster" being the name of the actual Excel Worksheet and the above code sitting inside its corresponding "SheetProxy" Class.
The only option offered by Code Inspections for this error is "Ignore once" and again I would like to know whether it is safe to do that. I have had this errors appear in a couple of projects before, yet everything worked fine there. However, in my most recent project I started randomly getting a “Run-time Error 35010” when opening the workbook, where I have the following code in my Workbook_Open event:
Private pres As Presenter
If pres Is Nothing Then
Set pres = New Presenter
End If
Could this problem be related to any of the above two Code Inspections suggestions/errors?
The role of these inspections is to surface hidden attributes that the VBE might be adding. Often these attributes affect how a class can be used (VB_Exposed, VB_PredeclaredId), or how a member behaves (VB_UserMemId, etc.). But they are also completely invisible in the editor, and without a corresponding annotation/comment, it's often impossible to tell their presence.
In this particular case Rubberduck is informing you1 that there's a hidden VB_VarHelpId attribute on the WithEvents variable.
By adding the corresponding annotation comment, we're making hidden code visible and modifiable in the editor: change the annotation's argument value, and Rubberduck inspections will now say attributes and annotations aren't synchronized, which means the hidden code says one thing but the visible comment is saying another.
Removing the attribute should have had the effect of exporting the module to a temporary file, modifying that file to remove the hidden attribute, and reimporting the modified module into the project. Note that because document modules (e.g. ThisWorkbook, or sheet modules) cannot be imported into the project that way, this doesn't work in document modules, so Rubberduck shouldn't warn about desynchronized annotations/attributes in these modules. If the quickfix didn't do anything, please report a bug, because this should definitely work! (edit: confirmed working as intended with build 2.4.1.5229)
Bottom line, "Rubberduck Opportunities" inspections are just that: opportunities to leverage Rubberduck-specific features (like annotation comments managing hidden attributes): they aren't indicative of anything wrong with the code.
The "incompatible type" inspection result is a known issue: at the moment Rubberduck is not seeing the Worksheet interface of worksheet modules (nor the Workbook interface of ThisWorkbook), and this is causing a number of false positives around document modules. Until Rubberduck can tell that ProcessMaster is not just a ProcessMaster object but also a Worksheet (we'll have a fix for that in 2.5.x), it's probably safe to ignore that inspection when it's complaining about document modules and MSForms interfaces (which have the same underlying issue).
The idea behind this inspection, is that often VBA will only realize at run-time that object types aren't compatible, but (assuming it sees all interfaces of all types) Rubberduck can tell you about a problem at design-time, well ahead of execution.
As for the intermittent error, I suspect it's because you are accessing objects before they are actually available to use. The Initialize handler of your class runs at the New Presenter statement, before the call returns with the reference to assign pres.
I'd try to see if moving the code from the Initialize handler and into some parameterized initializer procedure would fix it.
If pres Is Nothing Then
Set pres = New Presenter
pres.Initialize ProcessMaster
End If
Where this Initialize procedure might look like this (untested):
Public Sub Initialize(ByVal masterSheet As Worksheet)
Set sheet = masterSheet
Set buttonHideOwnerToAvailability = masterSheet.Buttons("btnHideAssetOwnerToAvailability")
End Sub
The idea being, to inject the worksheet dependency rather than couple it to the presenter class (that's a very good thing!), but mostly to let the class instance initialize itself fully before we start accessing the host document (which has just opened and may have some asynchronous initializations that have yet to complete... just thinking out loud here, this may or may not be what's actually going on).
1 The severity level of each individual inspection can be configured in the settings.

Do I have to create a class to handle application-level events?

This article about
"Using events with the Application object" states:
Before you can use events with the Application object, you must create a class module and declare an object of type Application with events.
In my workbook in ThisWorkbooks module I have the following code
Option Explicit
Private WithEvents App As Excel.Application
Private Sub Workbook_Open()
Set App = Application
End Sub
Private Sub App_SomeEvent()
'This event fires without problems
End Sub
There is no problem with the code, as far as I have tested. However the above mentioned article made me think:
I know ThisWorkbook is a class module, same as sheet modules, but can I actually use them for this purpose?
Is there anything I have to keep in mind when not creating a new class module, or is the article wrong in that I have to create a class module?
It is absolutely possible to let ThisWorkbook module handle Application events.
What you need to keep in mind, if ever, is it violates single responsibility principle. Handling Application events is not the responsibility of a Workbook. Creating a dedicated class to handle Application events for specific purpose may help to keep the architecture clean.
However, from the stand point of the feature itself, there is no problem.

How to get a reference to the selected object in an Office Add-in

I'm creating an Office Add-in for PowerPoint (desktop and online) that essentially adds custom navigation to objects, similar to hyperlinks. I'd like to get a reference to the currently selected object and then expose a UI for the user to enter navigation details, which are then persisted via the document.settings API.
I can register for the documentSelectionChanged event, but the object passed to the handler only provides a reference to the event type and document.
Office.context.document.addHandlerAsync('documentSelectionChanged', onDocumentSelectionChanged);
I'm assuming I need to then query for the selection using getSelectedDataAsync(), but there doesn't appear to be a CoercionType enum value for something like a PowerPoint shape object.
Is it possible to obtain a serializable reference to the current object?

Application.FileDialog vs GetOpenFilename: What is the difference?

I'm trying to teach myself VBA during the short holiday break.
For my first module, I want to create a module that does the following:
Displays dialog box. Prompts user to select CSV files.
Open the CSV files via loop.
Summarize the CSV files based from their data.
Anyway, for number1, I search and found two methods in examples online:
Application.FileDialog and GetOpenFilename. Hm, I was wondering, what is the difference between the two (limitation, capabilities, speed, etc.) Is there a significant advantage of one method over another?
Thanks.
Application.FileDialog is umbrella property to provide you with several types of standard file dialogs: (1) file picker, (2) folder picker, (3) open file dialog and (4) "Save As" dialog. After getting FileDialog object, you can customize it further and call Show() (in some cases followed by Execute()) to display it and get user action.
Application.GetOpenFilename method displays dialog for getting file name (also see cases 1, 3, 4 above) allowing you to fast-preset selected set of its properties, namely
file filter, index of initial file filter, dialog title, action button text (e.g. "Open" or "Save") and option, whether multiple files can be selected at once (MultiSelect). But you have no control beyond these presets.
So basically former one gives you more control over the dialog window while the latter is "faster" to adopt in standard simple one-off scenarios.
Sticking with fast approach, you might want to prefer GetOpenFilename(), but if you wish to create library for larger application which will for example always use fie dialog with some custom standards (custom file filters or custom window custom title specific for your application), you might prefer custom method which tweaks FileDialog according to your standards and displays it.
Also please note that both above methods present you with dialog customized for Microsoft Office. With a bit of googling you can find your way to use standard dialogs from Microsoft Windows, for example folder picker dialog.
Here are differences I have sorted out, starting from the idea FileDialog gives an object to set up and retrieve results from, while GetOpenFileName gives a one-and-done function.
a. File Dialog has more options. These include:
pre-setting a filename for when the dialog opens (InitialFileName property)
changing the names of the button (ButtonName property)
changing how the files are shown (InitialView property)
an Execute method to actually open or save the file that's been selected.
b. GetOpenFilename goes with GetSaveAsFilename.
The difference between these two is also confusing at first, until you realize that you can't change the button caption (unless possibly on a Mac). That means you can change the title, but the button will still say "Open" or "Save". If you are picking files for other reasons, FileDialog lets you set the button to a custom string.
c. File Dialog Types. While FileDialog leaves more to the user, it has basic setups by use of the single fileDialogType argument:
set fd = Application.FileDialog(msoFileDialogFilePicker)
set fd = Application.FileDialog(msoFileDialogFileOpen)
set fd = Application.FileDialog(msoFileDialogSaveAs)
Each gets you the same object (see the next bullet), but with different properties. Most important is the DialogType property, which is then read-only, and can be changed only via a new set statement. This property determines what happens with .Execute: open, save, or for msoFileDialogPicker, an error. Other properties are set to match the initializing fileDialogType argument, but can generally be changed before you call .Show.
d. File Dialog quirks. With the FileDialog object, it turns out that you have just one per application. That means if you try to think ahead with set fdo = Application.FileDialog(msoFileDialogOpen) to open files and then set fdsa = Application.FileDialog(msoFileDialogSaveAs) to save files, hoping to keep both for later use, you'll soon learn that fdo and fdsa refer to the same object, now with the msoFileDialogSaveAs settings. The lesson is to set the parameters just before you use it.
e.) Getting the results. For the GetOpenFilename and GetSaveAsFilename methods, since they operate as functions, you get the selection as a return value. The return value may be a single selection, an array, or False if the user clicked Cancel. For the FileDialog object, in contrast, you get results through a SelectedItems collection as a property of the object. If a user clicked Cancel, the count will be 0, and the .Show method also returns -1. The latter is used in the pattern if fd.Show then fd.Execute, often in a with block.
Based on these, I think I'll stick with the file dialog. It can also pick folders, so it's probably a good one to be familiar with.
Application.FileDialog is an object. GetOpenFilename is a property. If you call GetOpenFilename, Excel owns the dialog object, configures it, displays it, and gives you the return value. If you use Application.FileDialog, you configure it, display it, and get the return value from the object. Outside of that, they're exactly the same - it's just a matter of who owns and controls the underlying FileDialog.

Resources