Troubleshooting ThisAddIn.Startup() continued…
OK, once more and gently (as my dad always used to say): my best theory now is that my code needs to create an object that represents the W2MWPP toolbar, and create this object whether the toolbar exists or not. Once I have that object, then I can finally get the toolbar buttons instantiated and get on with the Wiki functionality [yeah, famous last words].
I figure that the code should test whether it can find an existing instance of the toolbar. If it can’t find it, then it should create it; if it can find it, then just assign it to a variable and we’re done.
BTW, I found a great idea in VSTO for Mere Mortals (McGrath, Stubbs): rather than continuously referring to “Word2Wiki Toolbar” as a string, I could define a CONST and then reference the CONST instead. [This has the added advantage that I could change the string value very easily if “Word2Wiki Toolbar” was no longer suitable.] Why didn’t I think of this myself?
Here’s the code I’ve finally come up with to instantiate the Word2Wiki toolbar:
' Create a new CommandBar instance, if it doesn't already exist
If commandBarsCollection.FindControl(Tag:=TOOLBAR_NAME) Is Nothing Then
W2MWPPBar = commandBarsCollection.Add(TOOLBAR_NAME, Microsoft.Office.Core.MsoBarPosition.msoBarTop, False, True)
W2MWPPBar = Application.CommandBars(TOOLBAR_NAME)
Catch ex As System.ArgumentException
MessageBox.Show(TOOLBAR_NAME + "add-in's toolbar wasn't found - you won't be able to upload to the Wiki until you restart Word and/or reinstall the Add-in." + _
vbCrLf + vbCrLf + "Error: " + ex.Message, "Add-in Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Note: I’m not sure, but I suspect it’ll still thrown an exception on the first try. However, it may just “take” on the second try, so that might be good enough for now.
Aside: CommandBars vs. Ribbon UI in Office 2007
While researching the CommandBar methods, I found this statement: “The use of CommandBars in some Microsoft Office applications has been superseded by the new Ribbon user interface.” Oops, that’s right – some of this functionality may have to be re-written for Office 2007. I’ll try to ensure the CommandBar-specific code can be decoupled from the application functionality, so we can get maximum reuse out of these efforts.
Next Issue: Word Template (changes to Normal.dot)
After instantiating the Toolbar successfully, Word 2003 at shutdown will ask me twice whether I want to save changes to Normal.dot. The first prompt only allows you to overwrite Normal.dot or cancel out of the shutdown of Word.
If I hit Cancel, then the second time I try to shut down Word, it prompts me with:
“Changes have been made that affect the global template, normal.dot. Do you want to save those changes?”
This time, I can say “No” to saving the changes, which leaves Normal.dot in its original form and finally lets me close Word. The help text for this second prompt says,
“This message can appear if you made changes to items, such as macros, toolbars, or AutoText, that are stored in a global template that is attached to your document. The most commonly used global template is Normal.dot, which comes with Word.”
Next Issue: CommandBarButton creation
In the code, I’m using a well-documented sample to add the CommandBarButton to the CommandBar, and yet I’m getting the error
************** Exception Text **************
System.ArgumentException: Value does not fall within the expected range.
at Microsoft.Office.Core.CommandBarsClass.get_Item(Object Index)
at Word2MediaWiki__.ThisAddIn.ThisAddIn_Startup(Object sender, EventArgs e) in \Word2MediaWiki++\ThisAddIn.vb:line 50
at Word2MediaWiki__.ThisAddIn.FinishInitialization() in \Word2MediaWiki++\ThisAddIn.Designer.vb:line 65
Now that I know how to read this exception (see Part 7 for that whole twisty maze), I’ll spend a whole lot less time deciphering it. This time it seems clear to me that it’s a problem with allocating a handle to the toolbar. However, because I know that the toolbar is being created properly, a second glance at the offending line of code gives me the answer:
ConvertControl = CType(Application.CommandBars("W2MWPPBar").Controls.Add(1), Office.CommandBarButton)
This one is easy: I’m mistakenly calling “W2MWPPBar” rather than “Word2Wiki Toolbar”. Let’s fix that: highlight the string, right-click, choose Refactor (or Refactor!), and find…nothing. D’oh — that’s right, the VB.NET refactoring tools (even the Refactor! add-on for Visual Studio) don’t have the Rename function that I’ve gotten used to in the C# world. Guess I’m just going to have to try Edit, Find and Replace, Replace in Files instead.
With the Const now properly in place, the CommandBar and its buttons fall neatly into place. Wasn’t that easy? 😉
Enhancement: Creating the CommandBarButton if it doesn’t exist
There’s two improvements I’ll make to the code that creates each CommandBarButton:
- I’ll mirror the way I construct the CommandBar – test if each CommandBar button exists, and if not, create it; if so, leave it alone. [McGrath’s code in VSTO for Mere Mortals will Delete the existing button and then create it — I don’t know why this should be necessary, so I’ll simplify this code for now.]
- Replace calls to Application.CommandBars(TOOLBAR_NAME) with an object for the toolbar itself (W2MWPPBar).
Here’s the current code:
For Each control As Microsoft.Office.Core.CommandBarControl In commandBarControlsCollection
If control.Tag = "W2MWPP Convert" Then
ConvertControl = control
buttonExists = True
If buttonExists = False Then
'Create a new ControlButton
ConvertControl = CType(Application.CommandBars(TOOLBAR_NAME).Controls.Add(1), Office.CommandBarButton)
And here’s my enhanced approach:
If W2MWPPBar.FindControl(Tag:="W2MWPP Convert") Is Nothing Then
ConvertControl = CType(W2MWPPBar.Controls.Add(1), Office.CommandBarButton)
ConvertControl = W2MWPPBar.FindControl(Tag:="W2MWPP Convert")
Enhancement: implement For Each loop for the CommandBarButtons
This code is creating three CommandBarButtons using the same Methods and Properties, but doing it three separate times. I know I’ve done this too, but I’d prefer to fix this. Unfortunately, there’s one slight challenge for me: there’s too many variables to pass into a Sub, and I’m not very good with multi-dimensional arrays, so I don’t know what to do with all the variable strings that need to be fed in.
However, I recall another kind of construct somewhat like a multi-dimensional array, and a little digging on the ‘net and in my books leads to Structures. Further, a nice little post to the MSDN Forums turns me on to another suitable idea: Arraylist. Combine these two, and I should be able to pass in an arraylist of structures to a InstantiateButtons() method, and I’ll be able to loop through them all in one go.
The only trick is, the articles I’m finding right now don’t seem to give me useable advice for creating a structure in VB — or perhaps it’s just that Visual Studio isn’t cooperating, because if I type “Private Structure CommandBarButtonSettings” or “Private Type CommandBarButtonSettings”, Visual Studio doesn’t seem to generate the automatic “End Structure” or “End Type” statements that appear to be necessary.
The book “The Visual Basic .NET Programming Language” (Vick) showed a very simple way to write the code for a Structure, and once I tried that VS started instructing me on what I needed to add/rearrange for this to work. One thing that hadn’t been clear is that the Structure has to appear outside of any Method, so I’ve moved it up to just after the Public Class statement.
BTW, I just stumbled across the concept of “composite formatting“, which I’m going to try to use in my MessageBox.Show() calls here. The code sample in the MSDN Forum post mentioned above, from which I borrowed, happened to use composite formatting in their Console.Writeline() call, which tipped me off to this elegant way of generating strings with dynamic content scattered throughout. I don’t know about you, but I’m a bit tired of all the ” + variable.ToString() + “ nonsense that I have to embed so often in my apps.
Enhancement: implement String.Empty
I’m not even sure where I read this, but it was related to some of my research into the rules that good code should follow: where an application needs to set a String variable with an empty value, we should use String.Empty instead of “”. Thus I’m making changes such as from this:
buttonSettings.DescriptionTextProperty = ""
buttonSettings.DescriptionTextProperty = String.Empty
Milestone: Toolbar works!
Well I suspect you’re all just *dying* to see this app working — “wow, a toolbar with buttons that do *nothing*? What a wonder!” I’m going to mark the occasion on this day by creating a CodePlex project for this Add-In and uploading the current Source Code so everyone can have a laugh. 😉
Please have a look here, and leave any Comments, Issues or Suggestions that come to mind. Any and all such assistance is appreciated. Browse to here: http://www.codeplex.com/word2mediawikipp