Excel 2013 to 2010 backward compatibility issue with ActiveX (NOT Dec14th update issue) - vba

I am transitioning a set of interrelated Excel documents that use VBA code for lookups, data manipulation and calculations from Excel 2010 to Excel 365. I have both on my development machine, however these get sent (via email) to customers all over and then returned. I use .xlmb file formats for the file size savings however this doesn't seem to be affecting the outcome.
CURRENT ISSUE
When I save a workbook using 365 on my development machine, users get errors when performing an action that runs my VBA code when run from a machine that only has 2010 installed (if both versions are installed, the behavior does not seem to happen). I have focused the problem to when there is code in a module and there is an ActiveX control on a sheet. ONLY this combination seems to create the issue.
The test file/code I've created that consistently shows the issue is a workbook with the following code in a module:
Dim strBook As String
' Worksheet Names
Public Const wksTest = "Sheet1"
Public Function TestMe(PassedSheet As String)
strBook = ActiveWorkbook.Name
Workbooks(strBook).Worksheets(PassedSheet).Protect
Workbooks(strBook).Worksheets(PassedSheet).Unprotect
MsgBox "Worked from function", vbOKOnly, "Response"
End Function
And code in the sheet. It works with this code and selecting cell B2 protects and then unprotects the sheet while displaying message boxes:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Row = 2 And Target.Column = 2 Then
strBook = ActiveWorkbook.Name
Me.Protect
Me.Unprotect
MsgBox "Worked from sheet", vbOKOnly, "Response"
Call TestMe(wksTest)
End If
End Sub
However if an ActiveX Command Button named "TestButton" is added to Sheet1 and this code is added:
Private Sub TestButton_Click()
strBook = ActiveWorkbook.Name
Me.Protect
Me.Unprotect
MsgBox "Worked from Button", vbOKOnly, "Response"
Call TestMe(wksTest)
End Sub
The workbook will no longer function if saved from 365 and opened with 2010 on a machine with only 2010 installed.
This is unique enough that it has been difficult to test. Currently, the only option I seem to have is replace all my command buttons with Form Controls. This also is a separate issue from the security update (which is really muddying the waters).
I would love to get feedback on possible fixes for this issue or even just confirmation from others that this is an Excel issue and not somehow limited to our installation.
Thanks

I was looking for help on a similar question and stumbled on this info on the Microsoft help site. If this doesn't solve the problem you have, it might be a step in the right direction:
"If a workbook contains ActiveX controls that are considered to be Unsafe for Initialization (UFI), they are lost when you save the workbook to an earlier Excel file format. You may want to mark those controls as Safe for Initialization (SFI).
What to do: If you open a workbook that contains uninitialized ActiveX controls, and the workbook is set to high security, you must first use the Message Bar to enable them before they can be initialized."

Related

How to turn on 'Snap To Grid' on Excel start up with an add-in

I am looking for a way to automatically turn on Snap to Grid every time I start Excel.
I worked out this code and put it in an Add-In (.xlam) that I always load on Excel start-up.
Private Sub Workbook_Open()
Dim cbc As CommandBarControl
Set cbc = Application.CommandBars.FindControl(ID:=549)
If Not cbc.Enabled Then cbc.Execute
End Sub
But when I start Excel, it throws this error at cbc.Execute:
Can anyone tell me what is wrong?
I think you have two problems:
Problem 1
I believe that what you are trying to do with the Enabled property is to check the toggle state of the control. Instead, I think you want the State property instead (which doesn't show in intellisense). Your code should be something like this:
Public Sub ActivateSnapToGrid()
Dim cbc As CommandBarControl
Set cbc = Application.CommandBars.FindControl(ID:=549)
If Not cbc Is Nothing Then
If cbc.Enabled Then
'if snap to grid is off...
If cbc.State = 0 Then
cbc.Execute
'State should now = -1
End If
End If
End If
End Sub
Problem 2
The 'Snap to grid' control is not enabled if there is no workbook present (check this by closing all workbooks). In its current state, your code tries to execute in this case i.e. If Not cbc.Enabled Then cbc.Execute because I think you are trying to check if it is 'on' not if it is enabled.
Because an Excel add-in will load as a 'hidden' workbook, I don't believe it would enable the 'Snap to Grid' command bar control. Therefore, in the Workbook_Open event of the add-in then the control will be disabled and that's why you get the error.
You need an application-level event handler in your add-in. This is very common for Excel add-ins. See here on MSDN for some explanation. Plus also see this article by Chip Pearson which is very useful. It will allow you to write an event for any Workbook_Open event generated after your add-in loads.
So you will end up with this code (per Chip Pearson) in your add-in to call the sub I presented above (in Problem 1 section):
Private WithEvents App As Application
Private Sub Workbook_Open()
Set App = Application
End Sub
Private Sub App_NewWorkbook(ByVal Wb As Workbook)
Debug.Print "New Workbook: " & Wb.Name
Call ActivateSnapToGrid
End Sub
I haven't tested this thoroughly as it can't be known how your wrote your add-in. However, this is a very (if not the) standard pattern for doing this kind of stuff. See MSDN and the Chip Pearson article and you will figure out which code goes in a Class, in a Module etc.

Run Macro from Desktop Shortcut

I wrote a macro that does some calculations based on a particular type of excel sheet. Im trying to distribute this to my coworkers but the addition of a macro to a workbook and then running the macro is something foreign to them. I'd like to have a "shortcut" or some VBS program to open a specific workbook (specified by the user), run the macro, and display the results.
Your help is appreciated!
--Edit--
I wrote a macro in VBA. I exported the file to my desktop. Its simply called "Macro1". We have a standard form of excel sheet our company uses. Its literally the same sheet with different numbers. The macro I designed works on these kinds of sheets and does calculations. My coworkers aren't good with macros, so I want some sort of "code" that will prompt one of my coworkers for an excel file, then execute the macro on the file. Hopefully this clarifies any questions.
You need to make it a excel add-in.
Then in the add-in make it run on workbook open with Sub App_SheetActivate(ByVal Sh As Object) in thisworkbook.
In the macro you can then have it only activate on certain workbook name or workbook type by:
If range("A1").value = "something" ' something that makes the workbook type special.
' Maybe you need B1 value and so on too.
Do you need a way to self-install the add in just let me know and I have a code for that too.
Self install:
' if add-in is not installed and the workbook is a add-in (workbookcount =0)
' Also take note that this code will only run if the add-in is not installed
If Dir(Application.UserLibraryPath & "YourWorkbookName.xlam") = "" And Workbooks.Count = 0 Then
'optional ask user if he wants to install or not. Code not included.
' copy file from current position to add-ins folder.
Result = apiCopyFile(ThisWorkbook.FullName, Application.UserLibraryPath & "YourWorkbookName.xlam", False)
' activate the add-in
AddIns("YourAdd-inName").Installed = True
msgbox("add-in installed")
' Close Excel since add-ins does not work without restart of Excel
On Error Resume Next
Application.Interactive = False
AppActivate "Microsoft Excel"
Application.Quit
Exit Sub
End If
Note that the file must be saved as a add-in. (xlam) this means there is no sheets, the workbook is VBA code only.
Normally, that does not mean the code needs to be written in a special way.
Usually Range("XX").value works, but some commands may need to point towards the correct workbook. (you have two workbooks open with add-ins, the add-in with the code and the workbook with the sheets and numbers)
Hope this helps

Why doesn't VBE.ActiveCodePane.CodeModule work when the VBE (code window) isn't open?

Create a form that runs the following code.
MsgBox (VBE.ActiveCodePane.CodeModule)
And this message appears.
Now save, close, and reopen the database, and see this message:
Run-time error '91': Object variable or With block variable not set
If you open the Visual Basic Editor, it runs again. Even if you close the VBE, it still runs.
But when you close the whole application and reopen it, leaving the VBE closed, you get the error.
Why? What's going on here?
You reference the active pane object. The object isn't set until a pane gets activated. So before you open the VBE, the object is not set yet. Once you close the VBE, the object remains, so you can still reference it.
To get a handle to the ActiveCodepane object, without opening the VBE, is by activating a VBComponent, like this:
VBE.ActiveVBProject.VBComponents("Module1").Activate
You can activate any VBComponent like this.
Well when the VBE is closed upon the first opening of the application, there is no ActiveCodePane, you can check this in a conditional upon loading your form:
If (Application.VBE.ActiveCodePane Is Nothing) Then MsgBox "ActiveCodePane is Nothing"
The VBE exists and properties and methods can be used, but there is no ActiveCodePane which is why you're receiving the null reference exception. Just opening the VBE will still produce your error if you closed all CodePanes before saving and closing previously (unless a module exists for some reason). You must the explicitly open a CodePane, to set the 'ActiveCodePane' property.
This makes sense. What is it that you're trying to access via the ActiveCodePane property? Perhaps I can help find a way around?
Edit
Presumably, as you develop this Form and associated Modules, you'll know what they're called, and would be able to use a different method than the ActiveCodePane, such as that which #Bas Verlaat mentioned. Alternatively, you can loop through each code pane in the active VBProject and try and match on a name or something:
Option Compare Database
Option Explicit
Private vbProj As VBIDE.VBProject
Private vbComp As VBIDE.VBComponent
Private vbMod As VBIDE.CodeModule
Private Sub Command0_Click()
Set vbProj = Application.VBE.ActiveVBProject
For Each vbComp In vbProj.VBComponents
MsgBox vbComp.CodeModule
Next
End Sub
As Bas Verlaat said, before you open the VBE, the object is not set yet.
Obviously the VBE won't be open when a user is using the program. Referencing the Active Code Pane should only be done when developing and debugging, and never in production.
For example the following custom made error message is great for debugging, but will fail if deployed to production.
ErrorHandler:
MsgBox "Error " & Err.number & ": " & Err.Description & " in " & _
VBE.ActiveCodePane.CodeModule, vbOKOnly, "Error"
Approach
Since sometimes it can't be avoided to use VBE functionality, the general solution for me is this ...
make sure to initialize/activate the relevant workbooks VB project (components)
make sure to initialize/activate other workbooks VB projects (components) after they are opened (if their VBE properties/code may be relevant later in the process)
VbeInit usage
To do this one could call (see code below)
VbeInit in the beginning of some event (e.g. the Workbook_Open() event) and
VbeInit someOpenedWorkbook whenever a relevant workbook was just opened (e.g. using Workbooks.Open(...))
Example Case
It works for the simpler above case.
A more complex case, where this is very obvious and necessary, is when you rely on the CodeName of sheets in workbooks. E.g.
if the user is allowed to reName some sheets, but they should still be uniquely identifiable by the VB application, via their CodeName and
such sheets (e.g. serving as template sheets) are copied (sheet.Copy ...) to other workbooks.
Then the sheets CodeName will only be copied, if the sheets VBComponent has been activated (e.g. via myVBComponent.Activate or opening the source-sheet-containing workbook in the VBE).
The VbeInit procedure
Procedure VbeInit(Optional wb As Workbook)
With Application.VBE
Dim pj As VBProject: For Each pj In .VBProjects
'ignore unsaved (=> fully initialized) workbooks
If Not wb Is Nothing Then If wb.FullName <> VBProjFilename(pj) Then GoTo continue
Dim c As VBComponent: For Each c In pj.VBComponents
c.Activate
Next c
continue:
Next pj
End With
End Procedure
Function VBProjFilename(pj As VBProject) As String
On Error Resume Next 'leave result empty if workbook (code) not saved yet
VBProjFilename = pj.Filename
End Function

Catching FileFormat property of .XLAM without confusion with .XLSM

Context
I have developed a .xlam add-in that contains user-data inside. In other words, the user can decide to show the add-in file through a ThisWorkbook.IsAddIn = False to edit the content, which is functional to the add-in itself.
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Need to check for file extension
From here, it comes my need of checking for the file extension and validate it when some specific "forbidden" procedures might get called. I have made the following tests:
If ThisWorkbook.IsAddIn = True, then ThisWorkbook.FileFormat = 55;
If ThisWorkbook.IsAddIn = False, then ThisWorkbook.FileFormat = 52;
The source of confusion
This is not what I was expecting. By simply executing a FullName request when the Add-In is set visible:
ThisWorkbook.IsAddIn = False
MsgBox ThisWorkbook.FullName
I can read that my file is still named C:\myFile.xlam, even if in that moment is visible to the user. So, I would expect ThisWorkbook.FileFormat to raise a 55 even if visible at run-time. But it doesn't do that, apparently.
The question
I need to make sure to distinguish between modifications on the Add-In (.xlam) and modifications on a possible .xlsm file that the user created, from which is using my Add-In.
Why is the FileFormat of my add-in being equal to the one of an xlsm, if the file is clearly xlam to which is associated a 55 instead of a 52? Where am I being wrong?
EDIT - Example of the action to forbid
On the ribbon there's a button created and added from the add-in, which is connected to a macro that cannot be run into the Add-In. So the check I had in mind was something like this:
If ActiveWorkbook.FileFormat = 55 Then
Exit Sub
End If
However, as said above, this check will not be performed because the Add-In has FileFormat = 52 in the moment in which is set to .IsAddIn = False; hence, even if the ActiveWorkbook is the add-in where I do not want to run the macro, the check will fail and the macro will run anyway.
The .IsAddIn workbook property simply indicates whether the file is being run as an Add-in. It does not change the file format. From the documentation:
When you set this property to True, the workbook has the following characteristics:
You won’t be prompted to save the workbook if changes are made while the workbook is open.
The workbook window won’t be visible.
Any macros in the workbook won’t be visible in the Macro dialog box (displayed by pointing to Macro on the Tools menu and clicking Macros).
Macros in the workbook can still be run from the Macro dialog box even though they’re not visible. In addition, macro names don’t need to be qualified with the workbook name.
Holding down the SHIFT key when you open the workbook has no effect.
I sense that this is the real problem you're trying to tackle:
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Perhaps it will be best if you can specify what actions you're trying to restrict? There may be a better way to solve this.
For the moment I have found four possible solutions, that I'm going to post here just in case someone would have my same issue:
Comparing the full names - credit to Tim Williams
The "special code" cannot run if the full names are different:
If ActiveWorkbook.FullName = ThisWorkbook.FullName Then
Exit Sub
End If
'"special code"
Comparing the isAddIn property - credit to David Zemens
The "special code" cannot run if this workbook is not currently an add-in:
If ThisWorkbook.IsAddIn = False Then
Exit Sub
End If
'"special code"
Comparing the two objects
The "special code" cannot run if the active workbook is the add-in workbook:
If ActiveWorkbook Is ThisWorkbook Then
Exit Sub
End If
'"special code"
Checking for "xlam" extension
The "special" code will not be run if the extension of the file is xlam:
If Right(ActiveWorkbook.FullName,4) = "xlam" Then
Exit Sub
End If
The four solutions above work fine for the purpose, but the question is still opened : why the FileFormat property changes over the same file depending on ThisWorkbook.IsAddIn being False rather than True?

excel vba projects not closing

I'm going through 100s of excel files in VBA, extracting certain data and copying it to a main spreadsheet in a main workbook. I have a VBA script that resides in this main spreadsheet.
I'm trying to get each source workbook to close after I open it and get what I need. It looks something like this:
dim main_wb
dim source_wb
set main_wb = activeworkbook
Loop thru workbook names
set source_wb = workbooks.open(a_workbook_name)
do some stuff
eventually copy a few rows from various sheets into the main wb
source_wb.close()
set source_wb = Nothing
End Loop
The problem is that it SEEMS like the system is continuing to keep the file open in the project explorer ... and eventually it runs out of memory or something. All files work fine individually. It's only when I attempt to process them all at once that I have a problem. The workbook "closes()" but the project still exists in the project explorer in the developer window.
How do I tell it to close out a project. I need to be able to, no BS, close the project and go on to the next one for hundreds and potentially thousands of files - automatically, in code, no intervention from user.
try... It works for me in a similar type of program.
'closes data workbook
source_wb.Close False
I recently had this problem: I have a workbook that grabs data from other workbooks that I use as databases. On one of these, I inadvertently placed some code. This caused the workbook to remain visible in VBE even after it had been closed. My solution was to keep my database workbooks free of code, and that solved the problem.
It seems that the VBE editor is not always visible to the workbook that is being closed.
I included the following code in my ThisWorkbook module which comes from a comment in another thread and this resolved matters.
http://dailydoseofexcel.com/archives/2004/12/11/google-desktop/
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next
' -------------------------------------------------------------
' this code ensures that the VBA project is completely removed
' when the workbook is closed
' http://dailydoseofexcel.com/archives/2004/12/11/google-desktop/
' -------------------------------------------------------------
If Not (Application.VBE.MainWindow.Visible) Then
Application.VBE.MainWindow.Visible = True
Application.VBE.MainWindow.Visible = False
End If
End Sub
Solution :
Manage your Save (Yes, No, Cancel)
Destroy links to Addins in your Application
Close these Addins
Close your Application