Application.OnTime in Add-In Workbook_Open - vba

I've built a custom Excel add-in and I'm currently trying to figure out a way to prompt users via VBA when new versions of the add-in are available.
I tried just using the workbook_open event to check for the latest version and then prompt the user with a userform, but I discovered that when Excel loads an add-in that trigger a userform, Excel stops loading the workbook the user actually tried to open and reloads the add-in. So while the userform works like I wanted, the user gets a blank (read no sheets) Excel shell with a loaded add-in.
So I considered using Application.OnTime to postpone the VBA until after the add-in and target file were both open. I got the impression both here and here that this is possible, but I am only able to make it work in an .xlsm file and not a .xlam file.
Here's the code I'm testing with:
Sub Workbook_Open()
Application.OnTime Now() + TimeValue("00:00:05"), "Test_Addin.xlam!Versioning.Notify_User"
End Sub
And in a regular code module:
Sub Notify_User()
MsgBox "Hello"
End Sub
So, my question: Am I doing something wrong here?
I'm wondering if there's something about how an add-in is loaded/designed that keeps it from allowing this type of action to be performed.
Alternatively, is there a different way to do this that you can think of?
I read an interesting blog post (and comments) by Dick Kusleika on this topic, but it sounded like some people put a version check in each sub procedure... I have a lot of procedures, so this doesn't sound like a good alternative, although I may have to resort to it.

Well, time is of the essence so I resorted to the least desirable option: a check at the beginning of each procedure.
To the interested parties, here's how I did it:
Somewhere towards the beginning of each sub procedure I put this line of code:
If Version_Check Then Call Notify_User
And in my Versioning module I put this function and procedure:
Function Version_Check() As Boolean
Installed = FileDateTime(ThisWorkbook.FullName)
Available = FileDateTime("\\NetworkDrive\Test_AddIn.xlam")
If Installed < Available Then Version_Check = True
End Function
Sub Notify_User()
Update_Check.Show
End Sub
So each time a procedure is run this code checks for a version on our corporate network with a modified datetime greater than the datetime of the installed add-in.
I'm still very open to alternative ways of checking for new versions so feel free to post an answer.

Related

Excel Add-In - Alternative to ActiveMenuBar.Reset

Problem: After an upgrade from Office 2010 to 2013, the Essbase Excel Add-in ("essexcln.xll", which Oracle ended support for in 2013) causes the focus to always return to a window with an active connection, when there is more than 1 window open. If the Essbase Add-In is loaded at startup, Excel also freezes. Note that Smartview has replaced this Add-In but for other reasons I need to continue using it. I can manually go to File > Options > Add-ins > Manage Excel Add-Ins and manually check/un-check when Essbase is causing these errors, but I'd rather do that with a quick keyboard shortcut.
Workaround: Create a custom add-in to quickly toggle the Essbase Add-In installed property to load and unload it. Maybe an add-in is overkill - but I don't really use a PERSONAL.XLSB, and I'd like this functionality to be available at all times.
Problem 2: When the Essbase add-in is unloaded, the "Add-in" Menu bar still shows a custom command: "About Oracle Essbase Spreadsheet Add-in." "Essexcln.xll" is notoriously buggy, and this "About Oracle Essbase Spreadsheet Add-in" can linger on even when the add-in is unchecked manually. My solution is to use ActiveMenuBar.Reset - only after I've unloaded the add-in. I don't want to reset if I've just toggled Installed to True.
Is there an alternative to ActiveMenuBar.Reset? This feels like a hack - kind of like using ActiveCell or ActiveSheet - but I don't want to manually check/un-check the Addin, which may or may not clear the "About Essbase..." anyway.
Notes: Yes, maybe it's not the most efficient to loop through Add-ins, but there's so few of them that I don't really care. I'd much rather avoid using ActiveMenuBar.
Sub Toggle_Essbase_AddIn()
Dim x As AddIn
Dim installed As Boolean
For Each x In Application.AddIns
If x.Name = "essexcln.xll" Then
' Get initial installed status
installed = x.installed
' Toggle
x.installed = Not x.installed
' Reset menu bar if Essbase was initially installed
If installed Then
ActiveMenuBar.Reset
End If
Exit Sub
End If
Next x
End Sub
As it stands, your code is incredibly inefficient, to the point that it's tough to explain why. Looking at the core of your procedure (after removing wasted space) we have:
Sub Toggle_Essbase_AddIn()
Dim x As AddIn, installed As Boolean
If x.Name = "essexcln.xll" Then
installed = x.installed
x.installed = Not x.installed
If installed Then
ActiveMenuBar.Reset
End If
Exit Sub
End If
End Sub
First off, this line does absolutely nothing:
x.installed = Not x.installed
...since it's after the value of installed has been set.
I can only assume that your intention was to toggle the value that's being passed to variable installed?
It will save you a lot of confusion if you get a little more creative with your variable names, instead of risking a word that, for all intensive purposes, could be considered Reserved. Without picking apart semantics, it is best not to name your variables with words that VBA wants to use for other things, and this is a perfect example why. Even if VBA doesn't get confused, you might, and you have an almost infinite number of other, more meaningful names to pick from.
Assuming that the intention was to toggle the value that's being passed to variable installed then you could have gone:
If x.Name = "essexcln.xll" Then
installed = not x.installed
If installed Then
ActiveMenuBar.Reset
End If
Exit Sub
End If
Preferably you should use Exit commands sparingly, or not at all -- only when there isn't an option to leave the loop/sub/if "naturally" but I'm not going into that now since it's irrelevant in a moment.
...or better yet:
installed = Not ( x.Name = "essexcln.xll" )
If installed Then
ActiveMenuBar.Reset
End If
...or even shorter:
If Not ( x.Name = "essexcln.xll" ) then ActiveMenuBar.Reset
...so now your whole sub would be:
Sub Toggle_Essbase_AddIn()
Dim x As AddIn
Dim installed As Boolean
For Each x In Application.AddIns
If Not ( x.Name = "essexcln.xll" ) then ActiveMenuBar.Reset
Next x
End Sub
...but you're still being unnecessarily inefficient.
There is no reason to loop through the AddIns. A quick Google of Application.Addins shows that it's easiest to refer to the Add-In by it's name.
I've never used Essex, but another quick Google to find the add-in documentation tells me that the name of the add-in is "Oracle Essex".
Therefore, one line replaces your entire procedure:
If Not AddIns("Oracle Essbase").Installed Then ActiveMenuBar.Reset
(or I'm not sure if it was intended to NOT be NOT because your code was unclear) - but this is far more efficient and does the equivalent of your code - as long as you're sure the add-in actually exists (not the same as installed, Google it), then this does the same thing as your entire procedure.
If you're not sure if the add-in exists, then Google saves the day again with a link to a Stack Overflow question.
Since I don't have the add-in and you haven't included any specific example of the problem, I can't say for sure if this answers the question completely, but I assure you that taking some extra time on one's own due diligence in coding will safe a lot of effort for you - and others - in the end.
Heads up, I had a lot to write here to get to "one line" so I can't guarantee there were no oversights, but the lesson here is more about research-before-coding.
I suggest you study the documentation about add-ins and toolbars at MSDN as well as documentation specific to the 3rd party add-in at Oracle (via the links above, and sub-links from those pages) and I am confident that with some effort your solution will become clear.
If not, I suggest you add more information to your question. ...and please, don't attempt to write any "add-ins for add-ins"!
Good Luck (and welcome to Stack Overflow!)
...and after all that, I notice that you added a link to an image instead of adding an image, so it would have been more noticeable. I can't tell without clicking the toolbar but I'm pretty sure that's not a menu bar command. That's a ribbon group, and it's empty.

Closing any open userform

After a good search and some (over)thinking I came to the conclusion that I have no answer on what seems to be a simple question.
I have an excel document with many (20+) userforms in it. If you press a button (that is not in the userform, but just on the excel sheet) to start over again it should close any userform that's open at that moment.
I tried with unload me but of course I got an error when there wasn't any open userform.
Then I tried to add on error resume next thinking it would skip the line if there was no userform and therefore not giving an error but just continue what I want it to do. (opening a new userform).
It did indeed not give me the error anymore but it doesn't close any open userform as well (when there is one open).
So here I am, hoping someone here can help me as I don't know what to do. I could list up all of the userforms I suppose but it should be possible to go faster and automatically I suppose?
Some more info: It is never possible to have more than one userform open at the same time. // The button I want to create closes all the userforms if there are any and leads the user back to the main menu.
Thanks in advance!
KawaRu
Try calling the following when you want to unload all forms
Sub UnloadAllForms(Optional dummyVariable As Byte)
'Unloads all open user forms
Dim i As Long
For i = VBA.UserForms.Count - 1 To 0 Step -1
Unload VBA.UserForms(i)
Next
End Sub
This is hopefully the worst code that I have written in the last 5 years, but it will close any breathing form in Excel that you may have (and will kill any variables etc) :
Public Sub CloseAll()
End
End Sub
Use with caution!
From the MSDN End Statement:
The End statement stops code execution abruptly, without invoking the Unload, QueryUnload, or Terminate event, or any other Visual Basic code.
Code you have placed in the Unload, QueryUnload, and Terminate events offorms and class modules is not executed.
Objects created from class modules are destroyed, files opened using the Open statement are closed, and memory used by your program is freed.
Object references held by other programs are invalidated.
The End statement provides a way to force your program to halt. For normal termination of a Visual Basic program, you should unload all forms.
Your program closes as soon as there are no other programs holding references to objects created from your public class modules and no code executing.

Call to a different macro at a certain line

Is it possible to use the call feature to call a macro at a specific line? I ask as the macro i am coding does this - First it runs half of the code, then based upon user choices it opens up a custom UserForm, and runs the UserForm command buttons. After this I need a command button to go back to the code the line after the UserForm was used. The UserForm works as intended and many different stats calculations can be run. It has a button that returns the user back to the rest of the code, but I can't get that to work.
I have tried using both GoTo statements and the Call feature (The most promising solution) but have had no success so far.
There are several ways to achieve it, I will show one of them.
You can modify your procedure (macro) asking for a value as optional.
Sub MyMacro(Optional Answer As Boolean)
If Answer = True then
'Do some stuff
Else
'Do some stuff
End If
End Sub
As Answer is optional you can call your Macro with or without arguments.
Suppose you call your Macro from your user form:
Call MyMacro(True)
Hope this give you some hints.
You can use VBA for this, but I would recommend using the Windows Task Scheduler. I think that will be a lot better, all things considered.
https://www.digitalcitizen.life/how-create-task-basic-task-wizard

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.

Input values into an Input Box that was called by another Macro

I am writing a VBA macro that calls some macros from other Excel workbooks. These Macros are protected and I do not have the ability to see or modify their code. In one of the macros, an InputBox is called.
Is there a way to automatically trigger the OK button so that the InputBox does not load up (or pops up and then closes without prompting the user)? I can live with the default value for the input box to be used (though I'd also like to modify it if possible).
Please let me know if more information is needed - thanks in advance.
Have a look at example:
Sub MyMacro()
Call MacroInDifferentWorkbook
'here code to catch InputBox
End Sub
If you're trying to do that in that way, the answer is NO, you can't catch InputBox from MacroInDifferentWorkbook. It was well explained here:
Simulating Multithreading in VBA using Excel
Multithreaded VBA – An Approach To Processing Using VBScript