Creating Custom Word Menus with Word 2003

Saturday, 26 April 2008 15:00 by frimbob

As part of my office project I have found the need to create a custom menu, it sound easy but it really is not. There exist many cravats which need to be overcome before a menu can be created. In the following post I will attempt to explain how I have archived this.

Note about Application.CustomizationContext Property

MSDN Article Here

From the MSDN Article "Returns or sets a Template or Document object that represents the template or document in which changes to menu bars, toolbars, and key bindings are stored."

Before one can build custom Menus for Word an understanding of CustomizationContext is necessary. All controls for the commandbar have a context to either the current document or the a Template. That being said the default template is 'Normal.dot' . Once a menu is added it is saved into the current context, if you have opened a blank document Normal.dot will have these values saved into them, if you current context is another template that template will receive the new menus.

This applies for both Application and document level customizations.

A word about Persistence.

Word is unlike Excel it will not respect the 'Temporary:=True' parameter of the controls.add() method that is used to build the menus.  Therefore once a menu is created it is persistent for its current context.

For example if I create a menu programmatically and the context is normal.dot then that menu is persistent  in that template and every person using this template will have the same menu, so where is the problem? Our code will run every time the template is loaded a new menu will then be added to the current context, they will not overwrite... so after 10 executions of Word we will have 10 separate menus.

The solution is to find if the menu your about to add exists and if so delete/remove the menu before you add the new menu. I will demonstrate this solution later in this post.

I have asked myself should we delete the menu or just test for its existence, after some thought I arrived at the following conclusion, It is better to remove the menu, if items are added to the menu later a simple test against a menus tag (id name) will not be sufficient. By deleting the menu we ensure an up to date menu.

 

The Code

The is a MSDN article How to: Create Office Menus Programmatically, that covers creating a menu for Excel programmatically. It However does not cover the cravats when creating a menu for Word. You will notice that the code below is very similar to the MSDN Article. Please note , the following line.

 

  71:                 cmdBarControl.Tag = menuTag

 

This line is missing from the MSDN article, and is necessary for the Sub CheckIfMenuExists() to match the menu, this function uses the tag to identify the menu that is why we declare the menuTag at the class level.

   1: Public Class UIcontrols
   2:  
   3: #Region "Declarations"
   4:  
   5:     'Others Removed for Remove for brevity
   6:  
   7:     Private adoc As TheDocument = New TheDocument()
   8:     'menu bar declarations
   9:     Private WithEvents menuCommand As Office.CommandBarButton
  10:     Private menuTag As String = "Air Stories Helper Program"
  11:     Private menuID As Object = "AirStories"
  12:  
  13: #End Region
  14:     
  15: #Region "properties"
  16:     Private thisapp As Word.Application
  17:     Public Property ThisApplication() As Word.Application
  18:         Get
  19:             Return thisapp
  20:         End Get
  21:         Set(ByVal value As Word.Application)
  22:             thisapp = value
  23:         End Set
  24:     End Property
  25: #End Region
  26:     
  27: #Region "Public Methods"
  28:     'loads the controls on the toolbar
  29:     Public Sub LoadControls()
  30:     
  31:     'apply menu bars
  32:         Me.CheckIfMenuBarExists()
  33:         Me.AddMenuBar()
  34:         
  35:     End Sub
  36:     

37: ' If the menu already exists, remove it.

  38:     Private Sub CheckIfMenuBarExists()
  39:         Try
  40:             Dim foundMenu As Office.CommandBarPopup = _
  41:                thisapp.Application.CommandBars.ActiveMenuBar.FindControl( _ 
  42:                Office.MsoControlType.msoControlPopup, Tag:=menuTag, Visible:=True, Recursive:=True)
  43:             If foundMenu IsNot Nothing Then
  44:                 foundMenu.Delete(True)
  45:             End If
  46:  
  47:         Catch ex As Exception
  48:             MessageBox.Show(ex.Message)
  49:         End Try
  50:     End Sub
  51:     
  52:     ' Create the menu,
  53:     Private Sub AddMenuBar()
  54:  
  55:         Try
  56:             Dim menuBar As Office.CommandBar = WordAddIn1.Globals.ThisAddIn.Application. _ 
  57:                                                 CommandBars.ActiveMenuBar
  58:             Dim menuCaption As String = "AIR Menu"
  59:  
  60:             If menuBar IsNot Nothing Then
  61:                 Dim cmdBarControl As Office.CommandBarPopup = Nothing
  62:                 Dim controlCount As Integer = menuBar.Controls.Count
  63:  
  64:                 ' Add the new menu.
  65:                 cmdBarControl = CType(menuBar.Controls.Add(_
  66:                                      Type:=Office.MsoControlType.msoControlPopup, _
  67:                                      Before:=controlCount, Temporary:=True), _
  68:                                      Office.CommandBaPopup)
  69:  
  70:                 cmdBarControl.Caption = menuCaption
  71:                 cmdBarControl.Tag = menuTag
  72:  
  73:  
  74:                 ' Add the menu command.
  75:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  76:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  77:                     Office.CommandBarButton)
  78:  
  79:                 With menuCommand
  80:                     .Caption = "Change Articles Order"
  81:                     .Tag = "Menu item"
  82:                     .FaceId = 300
  83:                 End With
  84:  
  85:                 ' Add the menu command.
  86:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  87:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  88:                     Office.CommandBarButton)
  89:  
  90:                 With menuCommand
  91:                     .Caption = "Process Articles"
  92:                     .Tag = "Menu item"
  93:                     .FaceId = 342
  94:                 End With
  95:  
  96:                 ' Add the menu command.
  97:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  98:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  99:                     Office.CommandBarButton)
 100:  
 101:                 With menuCommand
 102:                     .Caption = "New Document"
 103:                     .Tag = "Menu item"
 104:                     .FaceId = 278
 105:                 End With
 106:  
 107:             End If
 108:  
 109:         Catch ex As Exception
 110:             MessageBox.Show(ex.Message)
 111:         End Try
 112:     End Sub
 113:     
 114:         'Event Handlers Remove for brevity
 115:     
 116: #End Region
 117:  
 118: End Class

 

The Function CheckifMenuExists() is very simple at its heart is the FindControl() method

 

   1: ' If the menu already exists, remove it.
   2:     Private Sub CheckIfMenuBarExists()
   3:         Try
   4:             Dim foundMenu As Office.CommandBarPopup = _
   5:             thisapp.Application.CommandBars.ActiveMenuBar.FindControl(_ 
   6:             Office.MsoControlType.msoControlPopup, Tag:=menuTag, Visible:=True, Recursive:=True)
   7:             If foundMenu IsNot Nothing Then
   8:                 foundMenu.Delete(True)
   9:             End If
  10:  
  11:         Catch ex As Exception
  12:             MessageBox.Show(ex.Message)
  13:         End Try
  14:     End Sub

 

The FindControl() is from the current application namespace , its current commandBars and finally its activemenu. The 2 most important parameters are the Tag and the type. As explained earlier the FindControl() is matched on a type and a tag. After the Findcontrol method is executed it is tested for a null reference (Nothing in VB). The menu is deleted if found.

 

AddMenubar()

   1: Private Sub AddMenuBar()
   2:  
   3:         Try
   4:             Dim menuBar As Office.CommandBar = WordAddIn1.Globals.ThisAddIn.Application.CommandBars.ActiveMenuBar
   5:             Dim menuCaption As String = "AIR Menu"
   6:  
   7:             If menuBar IsNot Nothing Then
   8:                 Dim cmdBarControl As Office.CommandBarPopup = Nothing
   9:                 Dim controlCount As Integer = menuBar.Controls.Count
  10:  
  11:                 ' Add the new menu.
  12:                 cmdBarControl = CType(menuBar.Controls.Add( _
  13:                     Type:=Office.MsoControlType.msoControlPopup, Before:=controlCount, Temporary:=True), _
  14:                     Office.CommandBarPopup)
  15:  
  16:                 cmdBarControl.Caption = menuCaption
  17:                 cmdBarControl.Tag = menuTag
  18:  
  19:         'Code removed for Brevity
  20:  
  21:         Catch ex As Exception
  22:            MessageBox.Show(ex.Message)
  23:        End Try
  24: End Sub

 

The above code will Create and empty menu,  The menuBar declaration is using the application namespace.commandbars.ActiveMenuBar to retrieve the active menubar in the current context.

The cmdBarControl is a CommandBarPopup, which make sense as each menu popsup when click on in the menu bar. It is initially set to null as we will use the 'menuBar Controls.Add()' method to add this control to the menu bar hierarchy.

 

   1: ' Add the new menu.
   2:    cmdBarControl = CType(menuBar.Controls.Add( _
   3:     Type:=Office.MsoControlType.msoControlPopup, Before:=controlCount, Temporary:=True), _
   4:        Office.CommandBarPopup)

 

Since the Controls.Add() method returns a reference to the new control we are doing 2 things in the above code, where adding a CommandBarPopup to the menuBar hierarchy through the Add method and assigning its reference to the cmdBarControl so we can later set its properties.

 

   1: cmdBarControl.Caption = menuCaption
   2: cmdBarControl.Tag = menuTag

 

As mentioned earlier both the above properties must be unique string values.

 

Adding Items to the new menu

Part 2 off the AddMenuBar()

   1: Public Class UIcontrols
   2:  
   3:     'Code Removed for Remove for brevity
   4:  
   5:     ' Create the menu,
   6:     Private Sub AddMenuBar()
   7:     
   8:     'Code Removed for Remove for brevity
   9:  
  10:     ' Add the menu command.
  11:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  12:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  13:                     Office.CommandBarButton)
  14:  
  15:                 With menuCommand
  16:                     .Caption = "Change Articles Order"
  17:                     .Tag = "Menu item"
  18:                     .FaceId = 300
  19:                 End With
  20:  
  21:                 ' Add the menu command.
  22:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  23:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  24:                     Office.CommandBarButton)
  25:  
  26:                 With menuCommand
  27:                     .Caption = "Process Articles"
  28:                     .Tag = "Menu item"
  29:                     .FaceId = 342
  30:                 End With
  31:  
  32:                 ' Add the menu command.
  33:                 menuCommand = CType(cmdBarControl.Controls.Add( _
  34:                     Type:=Office.MsoControlType.msoControlButton, Temporary:=True), _
  35:                     Office.CommandBarButton)
  36:  
  37:                 With menuCommand
  38:                     .Caption = "New Document"
  39:                     .Tag = "Menu item"
  40:                     .FaceId = 278
  41:                 End With
  42:  
  43:             End If
  44:  
  45:         Catch ex As Exception
  46:             MessageBox.Show(ex.Message)
  47:         End Try
  48:     End Sub

 

The following code creates new items on the menu. Areas to not are the FaceId of the menuCommand. The Proeprty sets the icon to use on the menu, A good source of information on that can be found at MSDN FaceID article and a list of numbes to icons can be found here. The Caption is the text that the control displays in the menu, while the tag is the ID of the control. 

It is very important that each Tag be unique for the menu container, Why might you ask? Unfortunately there is only one event for the menu that is the click event on the menuCommand which you would expect, but what you wouldn't expect is the event bubbles. This mentions briefly in the MSDN article where the is based from.  Quote from the Article:-

You must set the Tag property of your controls when you add event handlers. Office uses the Tag property to keep track of event handlers for a specific CommandBarControl. If the Tag property is blank, the events are not handled properly.

An Event Handler like the one below is needed to differentiate between the menu items.

   1: Private Sub menuCommand_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _
   2:     ByRef CancelDefault As Boolean) Handles menuCommand.Click
   3:  
   4:     'select a action to completet
   5:     Select Case Ctrl.Caption
   6:         Case "New Document"
   7:             'MsgBox("New Document")
   8:             Call Newdocument()
   9:  
  10:         Case "Process Articles"
  11:             'MsgBox("Process Articles")
  12:  
  13:         Case "Change Articles Order"
  14:             'MsgBox("Change Articles Order")
  15:  
  16:         Case Else
  17:             Throw New Exception("Menu Error")
  18:  
  19:     End Select
  20:  
  21: End Sub

 

This causes problems when you wish to use the same event handler for the toolbar buttons and the menu buttons. I found that I had to move the event code into functions and subs that could be called from both event-handlers, There may be another solution that presents itself with more time and research invested but not at the current time.

 

The Result

blog_260408_01

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:   , , ,
Categories:   Projects
Actions:   E-mail | Permalink | Comments (469) | Comment RSSRSS comment feed

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading