Further to yesterday's post, I've found that the issue with onLoad conflicts is much bigger than I thought. As Bob Phillips pointed out, this isn't actually a new issue, or one that is strictly an issue with RibbonX, but it is definitely worth mentioning.
To me, this is a pretty serious issue. In the RibbonX book, Robert & I picked a standard name for callbacks and used those throughout. For those of you who have created add-ins using our standard signatures, and distributed them to others, be aware that you may find that your ribbon hijacks someone else's, or vice versa. I discovered this after re-writing an old add-in and creating a new, unrelated one. My steps for both were to come back to my blog to pick up some framework code, and away I went. It started with the onLoad not working, but today I discovered that this is much bigger.
As it turns out, it appears that the FIRST add-in loaded gets the jump an all others with regards to callbacks. I've built and attached a simple demonstration of how easy this is to replicate, and that's what I'm going to discuss here.
Creating the test Add-ins:
- I created two add-ins and called them something really creative; ConflictAdd-in 1.xlam and ConflictAdd-in2.xlam
-
I created a the RibbonX structure for both Add-ins that looks like this:
ConflictAdd-in1.xlam
<customUI
xmlns="http://schemas.microsoft.com/office/2006/01/customui">
    <ribbon>
        <tabs>
            <tab id="rxtabCustom1"
                label="Add-in 1"
                insertAfterMso="TabHome">
                <group id="rxGrpConflict1"
                    label="Conflict Test - Add-in 1">
                    <button id="rxbtnConflict1"
                        label="I am in Add-in 1"
                        size="large"
                        onAction="rxbtnHandler_click"
                        imageMso="TentativeAcceptInvitation" />
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
ConflictAdd-in2.xlam
<customUI
xmlns="http://schemas.microsoft.com/office/2006/01/customui">
    <ribbon>
        <tabs>
            <tab id="rxtabCustom2"
                label="Add-in 2"
                insertAfterMso="TabHome">
                <group id="rxGrpConflict2"
                    label="Conflict Test - Add-in 2">
                    <button id="rxbtnConflict2"
                        label="I am in Add-in 2"
                        size="large"
                        onAction="rxbtnHandler_click"
                        imageMso="TentativeAcceptInvitation" />
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
Take a careful look through the XML and you'll notice that:
- The tab, group and button ID's are different, as are the button labels.
-
The onAction callback names are the same
Â
-
I created the VBA callback signatures for the add-ins, declaring them private, and also using the 'Option Private Module' keywords:
ConflictAdd-in1.xlam
Option Explicit
Option Private Module
Â
Private Sub rxbtnHandler_click(control As IRibbonControl)
'Callback for rxbtnConflict1 onAction
MsgBox "You triggered a the routine in Add-in 1!" & vbNewLine & vbNewLine & _
"And FYI, Add-in 1's code uses a Private Sub" & vbNewLine & _
"in a module marked 'Option Private Module'!"
End Sub
ConflictAdd-in2.xlam
Option Explicit
Option Private Module
Â
Private Sub rxbtnHandler_click(control As IRibbonControl)
'Callback for rxbtnConflict2 onAction
MsgBox "You triggered a the routine in Add-in 2!" & vbNewLine & vbNewLine & _
"And FYI, Add-in 2's code uses a Private Sub" & vbNewLine & _
"in a module marked 'Option Private Module'!"
End Sub
Again, a close examination of the code will show you:
- The module has been declared as private
- The callback has been declared as private
-
The only difference is the text about which module the routine was called from
Â
-
I then added a line to the Workbook_Open event in each Add-in to let me know when it had been fired.
ConflictAdd-in1.xlam
Private Sub Workbook_Open()
Debug.Print "Add-in 1 has been loaded..."
End Sub
ConflictAdd-in2.xlam
Private Sub Workbook_Open()
Debug.Print "Add-in 2 has been loaded..."
End Sub
Â
- Finally I installed both add-ins, closed Excel and re-opened it
The issue demonstrated:
So after opening Excel with the add-ins checked, I went into the VBE to check the load order of the add-ins:
We can also see that Add-in 1 loaded first in the image below, since it was loaded "AfterMSO" TabHome, then Add-in2 was as well.
At any rate, I then went to the Add-in 1 tab and clicked the button to see the results:
Okay, no problem. We are getting the callback from Add-in1 to fire without issue. Let's check Add-in2's button:
Wait a minute… this is Add-in2 calling Add-in1's procedure!
Just to prove out my theory, I unloaded Add-in1 at this point, and tried Add-in2's button again:
Okay, all good this time. So I re-loaded Add-in1 and tried both buttons again:
So here's my questions:
- Why does the RibbonX callback look outside its own workbook for the code?
- We have an IdQ attribute that is specifically designed for sharing RibbonX solutions across multiple workbooks. This really just reiterates my first question…
- I thought the entire point of marking your module as "Option Private Module" was to avoid hide your code from other workbooks?
- How can I prevent someone from accidentally (or intentionally) interfering with my add-ins callbacks?
- How are we supposed to put in a corporate standard for coding RibbonX solutions now?
- Do you feel that this is a bug, or a feature?
- Would it break any of your solutions if this cross workbook handling were removed?
Any way you slice this, it isn't good. As developers, we need to ability and assurance to make sure that other projects don't hijack, interfere or conflict with the code that we write.
Suggested solutions:
At this point the only solution that I can see working is to standardize on naming your add-ins with something that others won't use. I could preface all my callbacks with something like "XLGFT" for "ExcelGuru File Tools", for example:
onAction="XLGFT_btnHandler_click"
The chances are much lower that I'd run into a conflict, but it would certainly still be possible if I happened to come across the same nomenclature as someone else.
A possible use?
I can see one possible use that could be made from this. Theoretically, you could break your UI apart from the code that supports it. So you'd put all your callbacks in one master add-in, then load/unload the individual UI components through one means or another. The maintenance of it could be tough, but I guess it's not that far off what I do with controlling workbook templates from one central add-in. I just means you would be controlling sub add-ins from a central add-in…
One thought on “Demonstration of RibbonX add-in conflict dangers”
Can't onAction call "'ConflictAdd-in 1.xlam'!rxbtnHandler_click"?