Word Menus Update:

Friday, 20 June 2008 22:19 by frimbob

I have modified the code that I used to create a word menu to build a dynamic version of the previous code. The Application uses an xml file to store the names and location of a list of word-documents that need a menu button.

XML_File


   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <templates>
   4:     <name location="c:\template1.dot">Template1</name>
   5:     <name location="c:\template2.dot">Template2</name>
   6:     <name location="c:\template3.dot">Template3</name>
   7:     <name location="c:\template4.dot">Template4</name>
   8:   </templates>
   9: </configuration>

I then built a simple class to load and parse the xml file. The class uses and event handler to send the results to other class that are registered. (really an experiment for a delegate call back used in implementing patters like the observer, just for the practice).

ConfigData


   1: Imports System.Xml
   2: Imports System.Xml.Serialization
   3: Imports System.IO
   4:  
   5: '<summary> Reads a Config XmlFile and provides its data as Properties </summary>
   6: Public Class ConfigFile
   7:  
   8: #Region "Private Members"
   9:  
  10:     'Private Members
  11:     Dim TemplateNames_Collection As New System.Collections.Generic.Dictionary(Of String, String)
  12:     Dim objxmlDocument As New XmlDocument
  13:     Dim objRootNode As XmlNode
  14:  
  15:     Delegate Sub UpdateHandler(ByVal sender As Object)
  16:     Public Event UpdateEvent As UpdateHandler
  17:  
  18: #End Region
  19:  
  20: #Region "Properties"
  21:  
  22:     'Declare Properties
  23:     ReadOnly Property TemplateCollection() As System.Collections.Generic.Dictionary(Of String, String)
  24:         Get
  25:             Return TemplateNames_Collection
  26:         End Get
  27:     End Property
  28:  
  29: #End Region
  30:  
  31: #Region "Public Methods"
  32:  
  33:     Public Sub ReadXMLFile()
  34:         'declarations
  35:         Dim objRootChildNodes As XmlNodeList
  36:  
  37:         'get values out of the file and into the dataStructure
  38:         objRootChildNodes = objRootNode.ChildNodes
  39:  
  40:         For Each xmlnode As System.Xml.XmlNode In objRootChildNodes
  41:             For Each xmlNode2 As System.Xml.XmlNode In xmlnode.ChildNodes
  42:                 Dim strNodeValue As String = xmlNode2.InnerText
  43:                 Dim strNodeLocation As String = xmlNode2.Attributes.ItemOf(0).Value
  44:                 TemplateNames_Collection.Add(strNodeValue, strNodeLocation)
  45:             Next
  46:         Next
  47:  
  48:         RaiseEvent UpdateEvent(Me) ' Call the update Event
  49:  
  50:     End Sub
  51:  
  52: #End Region
  53:  
  54: #Region "Constructor"
  55:  
  56:     Public Sub New()
  57:         'loadtheXmlFile 
  58:         objxmlDocument.Load("C:\Users\Guest\Documents\Visual Studio 2005\Projects\WordAddIn1\WordAddIn1\Custom Classes\XMLFile1.xml")
  59:         objRootNode = objxmlDocument.DocumentElement
  60:     End Sub
  61:  
  62: #End Region
  63:  
  64:  
  65: End Class
  66:  

I used the collection in the above class in a For Each loop.  This loop creates a new commandbutton , which is then stored in an array declared at the class level. If the button object where not stored in the array they would go out of scope when the function ends and only the last instance of the button would still exist.

MenuBuilder


   1: Public Class MenuBuilder
   2:  
   3: #Region "Declarations"
   4:  
   5:     Private TemplateCollection As System.Collections.Generic.Dictionary(Of String, String)
   6:     Private MenuTag As String = "Air Stories Helper Program"
   7:     Dim menuCommand() As Office.CommandBarButton
   8: #End Region
   9:  
  10: #Region "properties"
  11:     Private thisapp As Word.Application
  12:     Public Property ThisApplication() As Word.Application
  13:         Get
  14:             Return thisapp
  15:         End Get
  16:         Set(ByVal value As Word.Application)
  17:             thisapp = value
  18:         End Set
  19:     End Property
  20:  
  21: #End Region
  22:  
  23: #Region "Public Methods"
  24:     'loads the controls on the toolbar
  25:     Public Sub LoadData(ByVal subject As Object)
  26:         Dim configData As ConfigFile = TryCast(subject, ConfigFile)
  27:         If Not IsNothing(configData) Or Not IsDBNull(configData) Then
  28:             TemplateCollection = configData.TemplateCollection
  29:         End If
  30:     End Sub
  31:  
  32:  
  33:     Public Sub LoadControls()
  34:  
  35:         'Test for data
  36:         If Not IsNothing(TemplateCollection) Then
  37:             'check if menu bar exists function
  38:             Call CheckIfMenuBarExists()
  39:  
  40:             'load function
  41:             Call AddMenuBar()
  42:         Else
  43:             MessageBox.Show("No menu data present plese contant Administrator")
  44:  
  45:         End If
  46:     End Sub
  47:  
  48:     ' Create the menu, if it does not exist.
  49:     Private Sub AddMenuBar()
  50:         Try
  51:             Dim menuBar As Office.CommandBar = WordAddIn1.Globals.ThisAddIn.Application.CommandBars.ActiveMenuBar
  52:             Dim menuCaption As String = "AIR Template Menu"
  53:             Dim intcounter As Integer = 0
  54:  
  55:             If menuBar IsNot Nothing Then
  56:                 Dim cmdBarControl As Office.CommandBarPopup = Nothing
  57:                 Dim controlCount As Integer = menuBar.Controls.Count
  58:  
  59:                 ' Add the new menu.
  60:                 cmdBarControl = CType(menuBar.Controls.Add(Type:=Office.MsoControlType.msoControlPopup, Before:=controlCount, Temporary:=True),  _
  61:                     Office.CommandBarPopup)
  62:                 cmdBarControl.Caption = "AirStories"
  63:                 cmdBarControl.Tag = MenuTag
  64:  
  65:                 For Each item As String In TemplateCollection.Keys
  66:                     Dim Button As Office.CommandBarButton
  67:                     'Add the menu command.
  68:                     Button = CType(cmdBarControl.Controls.Add(Type:=Office.MsoControlType.msoControlButton, Temporary:=True), Office.CommandBarButton)
  69:                     With Button
  70:                         .Caption = item.ToString
  71:                         .Tag = item.ToString
  72:                         .FaceId = 300
  73:                     End With
  74:                     AddHandler Button.Click, AddressOf menuCommand_Click
  75:                     ReDim Preserve menuCommand(intcounter) ' re-size the array
  76:                     menuCommand(intcounter) = Button
  77:                     intcounter = intcounter + 1 ' increment the counter       
  78:                 Next
  79:  
  80:             End If
  81:         Catch ex As Exception
  82:             MessageBox.Show(ex.Message)
  83:         End Try
  84:     End Sub
  85:  
  86:  
  87: #Region "Constructor"
  88:     Public Sub New()
  89:  
  90:  
  91:     End Sub
  92: #End Region
  93:  
  94: #End Region
  95:  
  96: #Region "Private Methods"
  97:  
  98:     ' If the menu already exists, remove it.
  99:     Private Sub CheckIfMenuBarExists()
 100:         Try
 101:             Dim foundMenu As Office.CommandBarPopup = thisapp.Application.CommandBars.ActiveMenuBar.FindControl(Office.MsoControlType.msoControlPopup, Tag:=MenuTag, Visible:=True, Recursive:=True)
 102:             If foundMenu IsNot Nothing Then
 103:                 foundMenu.Delete(True)
 104:             End If
 105:  
 106:         Catch ex As Exception
 107:             MessageBox.Show(ex.Message)
 108:         End Try
 109:     End Sub
 110:  
 111:  
 112:     'Method to verify the template location
 113:     Private Function VerifyTemplateLoc(ByVal Template As String) As Boolean
 114:         Dim Applic As Word.Application = Globals.ThisAddIn.Application
 115:  
 116:         'split the file name and the location
 117:         Dim TemplateSplit() As String = Template.Split("\")
 118:         Dim fname As String = TemplateSplit(TemplateSplit.Length - 1)
 119:         Dim flocation As String = Template.Replace(fname, "")
 120:         flocation = flocation.Trim()
 121:  
 122:         'found bool
 123:         Dim found As Boolean = False 'default is false
 124:         Try
 125:             Applic.System.Cursor = Word.WdCursorType.wdCursorWait
 126:             With Applic.FileSearch
 127:                 .FileName = fname
 128:                 .LookIn = flocation
 129:                 .SearchSubFolders = True
 130:                 If .Execute() = 1 Then
 131:                     found = True
 132:                 Else
 133:                     found = False
 134:                 End If
 135:             End With
 136:         Finally
 137:             Applic.System.Cursor = Word.WdCursorType.wdCursorNormal
 138:         End Try
 139:         Return found 'return bool
 140:     End Function
 141:  
 142:     Private Sub Newdocument(ByVal location As String)
 143:         
 144:         MessageBox.Show(location.ToString)
 145:  
 146:     End Sub
 147:  
 148: #End Region
 149:  
 150: #Region "Event Handlers"
 151:  
 152:     'click event for our custom menu
 153:     Private Sub menuCommand_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _
 154:         ByRef CancelDefault As Boolean)
 155:  
 156:         MessageBox.Show("woank")
 157:      End Sub
 158:  
 159:  
 160: #End Region
 161:  
 162:  
 163: End Class

Be the first to rate this post

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

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 (516) | Comment RSSRSS comment feed