Excel 2016 crashes when using Debug.Print Tab() in Workbook_Open() event handler - vba

Just something weird I noticed, tested on 2 computers with similar config (Windows 10 64-bit up-to-date with Excel 2016 32-bit), one was a clean install :
Simply create a new .xlsm workbook, and place the following in the ThisWorkbook.cls class module:
Private Sub Workbook_Open()
Debug.Print Tab(10); "Hello!"
End Sub
Save. Close. Open (if opens in PROTECTED VIEW mode, Enable editing, close and re-open). Excel crashes.
Minimal, Complete, Verifiable example.
Now the big question: why?
I tried to delay the problematic Debug.Print Tab(10); "Hello!" by placing it in a Sub which I call a few sec after the Open event with Application.OnTime Now() + (1/3600/24) * 5, "SubName", still crashing. I call it manually, it works just fine.
MSDN says nothing about it: Tab Function
Edit 19/04/2021
First, I have to say that I am a bit stunned : I asked this question on Apr 19 '18 at 7:42 (2018-04-19 07:42:36Z). There was not at this time, and there still is not, any reference to this bug on Stack Overflow. I have not encountered this bug since. And today, exactly 3 yers later to the day, I stumbled on it again. Fate is quite amazing.
Anyway, it appears this bug occurs as soon as a single Tab character is attempted to be printed in the VBE Immediate window with the Debug.Print and method without the VBE window loaded (meaning not opened once in the current Excel instance). That's why it was easier encountering it during an Workbook_Open event... But if you place the following sub in a workbook, close it, and run it from the Developper tab > Macro button without opening the VBE, Excel will also crash:
Private Sub test_MRE()
Debug.Print Tab
End Sub
So one must be very careful not to send Empty variables to the console (or you will probably look for hours before finding out what's happening, speaking knowingly...), because this will also cause Excel to crash (the , being a Tab and noting being sent before it):
Private Sub test_MRE()
Dim Var1 'Variant/Empty
Debug.Print Var1, "hello"
End Sub
I managed in my project to make the error we can hear before Excel crashes to appear, and it is an Automation Error, if it helps someone to better understand what's really happening:

Related

How to ask for confirmation before allowing a Word document to close?

I work with 2 different keyboard layouts (qwerty and azerty), and am constantly switching between the two using Alt-Shift. Sometimes when I reach for Ctrl-Z to undo, I get instead Ctrl-W which closes the document. Generally speaking I would in these situations get a Save Prompt, which would alert me to my mistake. However, I am now using OneDrive, with Autosave on the documents I'm working on. As a result, there is never this save prompt. The document is instantly closed and, worse, the undo that I was trying to effect is no longer present when the document is re-opened.
I would like to use VBA attached to the Normal Template, such that, on closing any document created with the VBA template, I will check to see if Autosave is turned on, and if it is, confirm before closing with a messagebox.
Obviously if the user doesn't confirm (e.g. chooses Cancel) then the document should not be closed.
Initially I put my code on Document_Close, but soon realised that here it's too late to Cancel the close. This is no good to me.
Then, based on some online code, I put my code in a class module (EventClassModule). In the ThisDocument module, I initiate the class, and activate the event handler.
My code doesn't ever seem to run, or in anycase, breakpoints don't kick in.
I'm a little unsure of where Normal stops and my normal document begins...
In ThisDocument of Normal Template, I have this VBA
Dim X As New EventClassModule
Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Private Sub Document_Open()
Register_Event_Handler
End Sub
And I have a class module called EventClassModule with the following
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
Dim res As VbMsgBoxResult
If Me.AutoSaveOn Then
res = MsgBox("OK to Confirm", vbOKCancel, "Closing Document")
If res = vbCancel Then
Cancel = True
End If
End If
End Sub
I would like a prompt to confirm saving, any time a document is closed and the autosave option is turned off.
The Document_Open event handler will only fire if the Normal template is opened via File | Open. To get code to respond when starting Word you need to use an AutoExec routine. Simply rename your routine as below. You can find further info on Auto macros here
Public Sub AutoExec()
Register_Event_Handler
End Sub
Here is an easy way, include a method like this:
Public Sub DocClose()
' Disables Ctrl + W (but not other close methods, e.g. Alt + F4, menus, [X])
' Your code here:
ActiveDocument.Close
End Sub
Basically there are some method names that are used internally by MS-Word, and if you include them in your code they will prevent the default action. If you leave the sub empty it will effectively disable Ctrl + W.
See here for more information: Reserved method names in MS Word VBA
Advantage 1 of this technique
If you use Public WithEvents App As Word.Application and your project gets reset for any reason, and fails to restart... App will be Nothing and none of the events will fire.
Advantage 2 of this technique
On the occasions that you actually want to close a document... with this technique you wont get bugged by a (soon to become) annoying confirmation message - just sayin ;-)

Finding a syntax error in a large VBA/MS Access project

I have a MS Access database that displays an error message about a SQL syntax error on launch. "Syntax Error in query. Incomplete query clause." It also shows another error a few seconds after I hit "OK" on the first one.
Here's the two errors: https://imgur.com/a/PesjIFk
But it doesn't tell me where the syntax error is. There are SQL statements in a bunch of different places all over this project. This is a really large project and it wouldn't be practical to just look through all the code hoping that I notice an error someplace. How can I find out where this error is?
EDIT: Ok, so apparently you have to have a keyboard that has a "Break" key on it in order to even find where the error is. Wow. Fortunately I happen to have one. Here's the code that Access takes me to if I press break when I see the error message. This code is for a subform of another form. It highlights the first line (Private Sub Form_Current()).
Private Sub Form_Current()
If NumEnums > 0 Then
CurrentEnum = val(Nz(bit_value_edit.value)) 'Update CurrentEnum to the currently selected enum
Call UpdateEnumsLabel(Me.Parent![enums label]) 'Update label
End If
End Sub
...and here's UpdateEnumsLabel():
Public Sub UpdateEnumsLabel(ByRef label As Control)
If NumEnums > 0 Then
label.Caption = "Enums: " & CurrentEnum & "/" & NumEnums
Else
label.Caption = "Enums: 0"
End If
End Sub
The definition for CurrentEnum:
Public CurrentEnum, CurrentPort, CurrentFile, CurrentGroup As Long
I'm thinking that this error is unrelated to the code in Form_Current(), but Access is highlighting that line because the error happens when the form is opened. But the form doesn't contain anything that uses a query, so I'm confused as to what query Access has a problem with.
When the error Message pops up, Use Control+Break. It will take you to the line causes the issue.
You should also open a module and form the debug option in the VBA editor select "Compile All Modules"
And since it appears to happening on open/load, you can check both the macros and the main modules to find anything that triggers on AutoExec.
Often ctrl-break will hit the line where you errored out. However, in the case of multiple “events”, and code without error handling, then often the error occurs in the routine that called the code, not the actual code that caused the error.
What I would do launch the application (hold down the shift key to prevent any start up code, or forms running).
Now that you launched the application, but without forms or code running, then check for an autoexecc macro (if there is one, check what code it attempts to run).
If there not an autoexec macro in use, then check under file->options->current database. In this view, you can look/see/determine what the name of the start-up form is.
Once you determined the start-up form (or start up module/code) called from autoexec macro, then you can simply add a blank code module, and place in your code the SAME command that is used to start your application.
So let’s assume no autoexec macro, and you determine that the start-up form is frmMain.
So now, we can launch the application (hold down shift key to prevent any start up code from running). Now, type into a new “test” standard code module the following code:
Sub MyGo
Stop
Docmd.OpenForm "frmMain"
End sub
So you type in above code, hit ctrl-s to save the above code. Now with your cursor anyplace inside of the above code, you simply hit F5 to run.
At this point you hit/stop on the “stop” command in the debugger. At this point, you then can hit f8 to step to the next line of code. If the form in question calls other code etc., you still be able to single step, and “eventually” you hit the line which causes the error.
While the application may be large, a simple look at the start up forms (and huts then the start-up code) means that you ONLY really need to trace and look at the start up code – not the whole application.

Document_Open() event does not fire on every computer

I have a Word which is used for form filling. The users are filling what is asked and some macros runs depending on what they chose or where they click. This works fine.
I recently decided to make a newer version which countains some ComboBox that are filled when the document is opened. To do that, I used the Document_Open() event.
Now here is the part I don't get : On my side, every time I do open the document, the event is triggered and the ComboBox that should get filled are filled. The problem is, so far I asked some people to test it on their own computer with their Word. Both of them came back to me saying that the ComboBox weren't filled when the document was openned.
To be more specific let's go in the code itself :
In ThisDocument, I have multiple subs that works perfectly fine and this Document_Open() event which gives me trouble.
Private Sub Document_Open()
Application.Run ("Fill.ComboBox")
End Sub
Here, Fill is the name of the Module which contains :
Sub ComboBox()
'Calling another Sub in this Module which adds the Items in the ComboBoxes
'from what parameters it is given
End Sub
Now that this has been stated, why would it work on my side but not work when another user tries it from their computer ?
Miscorsoft Word Versions :
I myself use Microsoft Office Profesionnal 2013 and so are the two users that tested it. That being said, this is intended to be working on any Word liscense from 2007 up to the current versions.
You can try in a module in your document:
Public Sub AutoOpen()
ComboBox
End Sub
I just used this in a Word 2016 document to automagically run a macro.
For documentation: https://support.microsoft.com/en-us/help/286310/description-of-behaviors-of-autoexec-and-autoopen-macros-in-word

How do I effectively create controls dynamically in Excel's VBA or How do I use Application.OnTime()?

I am working on a very large VBA project in Excel at my job. We are about 1500 lines of code for just one feature and have about a dozen more features to add. Because of this, I've been trying to break everything down so that I can keep code for each feature in separate places. OOP sucks in VBA... The problem being that these controls MUST have events fired. Of course, some events (like the TextBox_AfterUpdate event) are not available when you dynamically create controls. It's a bit convoluted because of everything that is going on, so I'll break it down the best I can:
I have a class module that represents a tab for a multipage control. When a user clicks on a tab, the Userform calls this class module and THERE I have the controls created dynamically. This way I can keep the code in that class module. I have a sub that I deemed as the "AfterUpdate" sub and put code that I needed to run there. Now the problem is to get that sub to be called at the appropriate time.
So what I did is to set up a Timer of sorts to check and see if the "ActiveControl" is said textbox. If it is not, we can assume that focus has left and we can raise that event. Here's the code I'm using:
An abbreviated version of the tab creation...
Private WithEvents cmbMarketplace As MSForms.ComboBox
Public Sub LoadTab(ByVal oPageTab As Object)
If TabLoaded Then Exit Sub
Set PageTab = oPageTab
Dim tmp As Object
Set tmp = PageTab.Add("Forms.Label.1")
tmp.Top = 6: tmp.Left = 6: tmp.Width = 48
tmp.Caption = "Marketplace:"
Set cmbMarketplace = PageTab.Add("Forms.ComboBox.1", "cmbMarketplace")
' LOAD OTHER CONTROLS '
TabLoaded = True
Start_Timer
End Sub
Then Start_Timer:
Public Sub Start_Timer()
TimerActive = True
Application.OnTime Now() + TimeValue("00:00:01"), "Timer"
End Sub
And the sub that is to be fired:
Public Sub Timer()
If TimerActive Then
' DO SOME RANDOM THINGS '
Application.OnTime Now() + TimeValue("00:00:01"), "Timer"
End If
End Sub
Does this seem like a reasonable approach to solving the problem I'm facing? I'm open to suggestions...
That's the first problem. This seems like a lot of work to accomplish this. (I'm working on getting visual studio, but I don't know if that's going to happen)
The above code will work but the "Timer" sub will not get raised at all. I get no errors if I just run the code. Everything is created, everything works as I would hope. However, if I step through the code, I eventually will get the following error:
Cannot run the macro "...xlsm!Timer". The macro may not be available in this workbook or all macros may be disabled.
Obviously neither of those suggestions are valid. Macros ARE enabled and the sub is in the same darn class module. I tried making it public, same problem. Tried "ClassModule1!Timer" to no avail. I'm at my wits end trying to figure this out. Thinking of having people write ALL this in the Userform or just giving up.
Does anybody have any suggestions on how to effectively break up large chunks of code? And does anybody have a clue why this sub will not run and seemingly cannot be found?
I understand that this is a confusing situation, so if you need more info or code examples or want to know why I have something set up the way I do, let me know.
Thanks!
Obviously neither of those suggestions are valid. Macros ARE enabled and the sub is in the same darn class module.
There's the problem: a macro cannot be in a class module. The message is entirely correct: VBA cannot see the Timer procedure, because it's not accessible.
A class module is a blueprint for an object, VBA (or any OOP language for that matter) can't do anything with a class module, without an instance of that class - i.e. an object.
Your timer callback needs to be a Public Sub in a standard module, so that it can be called directly as a macro. Public procedures of a class modules are methods, not macros.
Depending on what ' DO SOME RANDOM THINGS ' actually stands for, this may or may not require some restructuring.
1500-liner spaghetti code can be written in any language BTW.

How to close an add-in when there are no more References to it?

I'm writing a simple set of modular add-ins in Excel VBA.
In Addin1.xlam, I have selected "Tools.. References" and added MyMenus.xlam as a reference.
Addin1 also has some code to close itself from a menu. But when Addin1 closes, MyMenus stays open, even though it is no longer needed or referenced by anything.
I may also have Addin2 or Addin3 with a reference to MyMenus.
How can I get MyMenus to automatically close when no other open Project has a Reference to it?
Alternatively, how can I tell Addin1 to "close, and also close anything I had a Reference to"?
I solved this in a different way because the users were constantly saving the sheet and destroying the reference. It's a bit of a hack but what in Excel VBA isn't?
Public Sub CloseYourself()
Application.OnTime Now + TimeValue("00:00:00"), "CloseYourselfNow"
End Sub
Private Sub CloseYourselfNow()
On Error Resume Next
ThisWorkbook.Close False
End Sub
Basically, you call CloseYourself in the Workbook_BeforeClose event, which schedules CloseYourselfNow for 0 seconds from now (this part of Excel is single threaded so it waits for the original workbook to close). By the time CloseYourselfNow runs, the reference will have been removed and the addin can close itself. You need the On Error Resume Next in case other still workbooks have a reference. Finally, you can change the False to some check that is only true on your development machine - like Environ("COMPUTERNAME") - so it saves the addin and you won't lose dev changes.
Is keeping the MyMenus add-in open causing any issue? References like this would normally be left open exactly for the reason you describe, i.e. in case it is referenced elsewhere. I would recommend leaving it all as is but if you do want to proceed then it needs to be done in two steps: First remove the reference from the first add-in (Addin1.xlam), and then close the second add-in (MyMenus.xlam). Something like this should work:
Dim objRef As Reference
Set objRef = ThisWorkbook.VBProject.References("MyMenus")
Call ThisWorkbook.VBProject.References.Remove(objRef)
'Do not raise an exception as may not be able to close if the add-in is referenced elsewhere
On Error Resume Next
Call Workbooks("MyMenus.xlam").Close(False)
On Error Goto 0
Note, I have the Microsoft Visual Basic for Applications Extensibility reference added to the project (alternatively just declare objRef as a Variant).
When you do finally then close AddIn1.xlam, make sure you do so without saving otherwise you'll permanently lose the reference.