Troubleshooting ThisAddIn.Startup() continued…
Still struggling with getting the CommandBar and CommandBarButton instantiated in the ThisAddIn.Startup() Sub. I’m finding that the initial exploration of the CommandBar to see if there is a pre-existing instance of the “W2MWPP Convert” button is not working. The code starts off like this:
Dim MyControl As Microsoft.Office.Core.CommandBarButton MyControl = Application.CommandBars("W2MWPPBar").FindControl(Tag:="W2MWPP Convert")
Then when I debug (F5) this addin, Word reports an unhandled exception with the error “Value does not fall within the expected range”. I seem to recall having this same problem with my previous VSTO Word AddIn until I first had the button created — then, the next time I ran the addin, it had something to FindControl(). At present, since the button doesn’t exist, it appears that FindControl() is getting “jammed” and I’m never going to get anywhere (kind of a chicken-and-egg problem).
It will be easy to get around this problem on my computer, but I’m afraid that when I build and release this add-in for others to install, if I start the code with a FindControl() call when there’s no button to find, no one else will be able to use this addin either.
Alternative approach to creating the CommandBarButton?
I have to imagine that there’s another way to skin the cat: if we need to determine if the button exists before attempting to create it, but trying to find it by name isn’t working, then perhaps there’s some CommandBar control collection that we could iterate through, and compare the Tag value for each (if any) to find the one we want. That should go something like this:
Dim commandBarControlsCollection As Office.CommandBarControls = W2MWPPBar.Controls Dim buttonExists As BooleanFor Each control As Microsoft.Office.Core.CommandBarControl In commandBarControlsCollection If control.Tag = "W2MWPP Convert" Then MyControl = control buttonExists = True End If Next If buttonExists = False Then 'Create a new ControlButton MyControl = Application.CommandBars("W2MWPPBar").Controls.Add(Type:=Microsoft.Office.Core.MsoControlType.msoControlButton) End If
Is it a Variable Scope issue?
This still doesn’t resolve the error, so I’m continuing to search for good example code from folks who should know how to construct VSTO code. This blog entry from the VSTO team has an interesting thing to say:
You should declare your variables for the command bar and buttons at the class level so that your buttons don’t suddenly stop working.
The referenced article (which I’ve linked from Archive.org — the Internet Wayback Machine”) says:
The solution is to always declare your toolbar/menu/form variables at the class level instead of inside the method where they’re called. This ensures that they will remain in scope as long as the application is running.
I wonder whether this advice was more relevant to document-based VSTO projects rather than the application-level add-ins that are possible today — but something tells me it can’t hurt either way, and it’s worth trying to see if it changes anything about the errors above.
Result: unfortunately, this isn’t a problem of variable scope. In taking a closer look at the exception, here’s the first-level exception:
System.ArgumentException: Value does not fall within the expected range.
at Microsoft.Office.Core.CommandBarsClass.get_Item(Object Index)
Am I looking at the wrong object?
What exactly is the problem? Is this saying that get_Item() is failing to get the CommandBar, or the CommandBarButton? I’ve assumed up to now that it’s a problem referencing the CommandBarButton, since the CommandBar is getting created in Word each time I Debug this add-in. However, now that I’m looking at it, CommandBarsClass.get_Item() seems more likely to be acting on the CommandBar than the button (or else it’d refer to something like CommandBarButtonsClass.get_Item(), no?).
What’s odd, however, is that the VS Object Browser doesn’t even have an entry for CommandBarsClass — when I search for that term, no results come up, and when I search on “CommandBars”, the closest thing I can find is the “Class CommandBars” entry, which doesn’t have a get_Item() method.
Searching in MSDN, I found the entry for CommandBarsClass Members, which doesn’t reference the get_Item() method but does mention an Item Property. That page says a very curious thing:
This property supports the .NET Framework infrastructure and is not intended to be used directly from your code.
I wonder what that’s all about then? In fact, the documentation for the CommandBarsClass Class also says the same thing. I can understand that there are some “internal functions” generated by the compiler that aren’t really meant for use in my code, but it’s really tough to debug a problem when these constructs barely get a stub page and there’s no information to explain what I should think when one of these things pops up in my day-to-day work.
I feel like I’m chasing my tail here — now I’m back on the Members page, hoping that one of the Properties or Methods that are documented will help me deduce whether this class references the CommandBar or the CommandBarButton when it calls get_Item() [and maybe even help me figure out why a just-created CommandBar object can’t be referenced in code].
The best clue I’m coming up with so far is that the page documenting the Parent Property shows that under J#, there’s what appears to be a get_Parent() method, not existing in the other languages mentioned, which leads me to believe that the get_Item() method is something generated by the compiler when it needs to get the value of the Item Property. [At least I’m learning something for all my trouble…]
The only other tantalizing tidbit so far is that the CommandBarsClass page indicates that this class implements interfaces that all refer to CommandBar, not to any controls associated with the CommandBar: _CommandBars, CommandBars, _CommandBarsEvents_Event. I can’t tell the difference between the first two (at least from the docs), but obviously the Event interface is its own beast.
Success: it’s the CommandBar!
I think I have confirmation, finally: the docs for _CommandBars.Item state that the Item Property “Returns a CommandBar object from the CommandBars collection.” OK, so now I finally know: my code is barfing on trying to access the just-created CommandBar, not the CommandBarButton as I thought all along. Whew!
Aside: Re-Using Variables
I’m not much of a code snob yet — there’s very few things I know how to do “right”. However, I’ve found something that just doesn’t seem right in the original VBA code that I’m going to change.
The original code sets up three separate buttons in the CommandBar, and each time the code looks for the button, then creates it, then configures it, it’s using the exact same variable each time: MyControl. I know this obviously worked (at least in the VBA code), so it’s hardly illegal, but it seems much safer to create three variables and instantiate them separately. Maybe it’s just so that I can follow the code easier, I don’t know. In any case, I’m having a hard time with it so I’m going to call them each something else.
However, I’m not so much of a snob that I’ll create three boolean variables to track whether I’ve found an existing instance of the button, so I’m going to re-use the buttonExists variable.
Keep tuning in… someday I’ll actually start getting into Wiki-related code (I swear!)