none
Method added to click event is not called when another document has been closed (Application Add-In) RRS feed

  • Question

  • Hi!

    I have written an Application Add-In (in C#) for Word 2010. Everytime a new document is created, the Add-In adds temporary CommandBarButtons with click events to all context menus of the new document. This works just fine, and all desired methods are executed.

    I can create (or open) several documents, and in each document the desired methods are executed on click.

    As soon as I close one of these documents, the methods in the context menus of the other still open documents are not executed anymore.

    When I then create a new document again, the methods are executed again, in all documents.

    This is how I add a CommandBarButton:

        private static CommandBarButton createCommandBarButton(CommandBar contextMenu, int position, int numOfContextMenu, 
          _CommandBarButtonEvents_ClickEventHandler cmdCommand_Click, string tag, 
          string caption, bool beginGroup, int faceId)
        {
          CommandBarButton commandBarButton;
          commandBarButton = (CommandBarButton)contextMenu.Controls.Add(MsoControlType.msoControlButton, Type.Missing, Type.Missing, position, true);
          commandBarButton.Caption = caption;
          commandBarButton.BeginGroup = beginGroup;
          // The Tag property MUST be set, otherwise the Click event is not registered.
          commandBarButton.Tag = tag;
          commandBarButton.FaceId = faceId;
          // The event must only be registered once; this is valid for all context menus:
          if (numOfContextMenu == 1)
          {
            commandBarButton.Click += cmdCommand_Click;
            // If Garbage Collection is not forced, the event only fires once per Word session (see
            // http://bytes.com/topic/c-sharp/answers/493442-vsto-outlook-addin-toolbar-button-event-handler-fires-one-time-only):
            System.GC.Collect();
          }
          return commandBarButton;
        }

    And this is how I call the method (example with just one CommandBarButton:

          int numOfContextMenus = 1;
          foreach (CommandBar contextMenu in document.CommandBars)
          {
            if (contextMenu.Type == MsoBarType.msoBarTypePopup && contextMenu.Name != "System")
            {
              createCommandBarButton(contextMenu, 1, numOfContextMenus, CleanDocument_Click, Resources.IdCleanDocument,
                Configuration.GetNodeValueByParentId(Resources.ConfigElementMethod, Resources.IdCleanDocument, Resources.ConfigElementCaption), true, faceIdUnselected);
              numOfContextMenus = numOfContextMenus + 1;
            }
          }

    Does anybody know what I am doing wrong? Any help is much appreciated.

    Thanks in advance

    Nora


    • Edited by nora.sailer Wednesday, May 23, 2012 9:35 AM
    Tuesday, May 22, 2012 2:14 PM

Answers

  • Hi Nora

    Here's the basic information:
    http://msdn.microsoft.com/en-us/library/ee691832.aspx

    But this does require Ribbon XML. However, it's possible to load the Ribbon XML as the add-in loads, rather than having it static. See for example:

    http://social.msdn.microsoft.com/forums/en-us/vsto/thread/86C1FC3C-2707-47B8-AB3C-C20028EACF4C


    Cindy Meister, VSTO/Word MVP

    Thursday, May 24, 2012 7:45 AM
    Moderator
  • Hi Nora

    I'm not sure I understand question (1). I don't know what "My Toggle Button" is supposed to be, but the Paragraph command in the context menu opens a dialog box (Form). IOW there's no additional context menu extension to the right - it's a simple <button> element.

    (2) Ribbon XML has the InsertBeforeMso attribute that allows you to position your custom control relative to a built-in control. So in this example you'd find out the idMso of the cut command (probably "Cut") and assign that value to the attribute.

    (3) You really have to define each and every individual context menu.

    (4) You can write the callback in your C# application. See the first part of the three-part article on customizing the ribbon for developers. You want section "Usng COM add-ins to Modify the RIbbon UI". The example, Point (8) under subsection "To access the host application and work with the Ribbon".


    Cindy Meister, VSTO/Word MVP

    • Marked as answer by nora.sailer Wednesday, July 11, 2012 2:25 PM
    Tuesday, July 10, 2012 4:16 PM
    Moderator
  • Hi Nora

    (2) You need to know the idMSO string of each top menu entry, I'm afraid. It would be nice if there was an "onTop" or "first" kind of setting but I'm afraid there's not. (And if I were Microsoft I'd probably not want it because then I'd have to answer a thousand times the question as to why it's not working - because there's another add-in using it too, and who "wins"?)

    FWIW it's simpler to just have them all at the bottom (default). Depending on where the selection is on the screen, the menu could open upwards, so then it would be closer. (Just trying to give you an additional headache <g>)


    Cindy Meister, VSTO/Word MVP

    • Marked as answer by nora.sailer Thursday, July 12, 2012 1:13 PM
    Wednesday, July 11, 2012 3:34 PM
    Moderator

All replies

  • Hi Nora,

    Thanks for posting in the MSDN Forum.

    I suppose that your issue doesn't about another document's closing. Your commandButton will work fine only once in your application due to all of the logic is in ThisAddIn_Starup method and your commandButton isn't a Class-level variable in your application.

    If I have misunderstood anything, please feel free to let me know.

    Have a good day,

    Tom


    Tom Xu [MSFT]
    MSDN Community Support | Feedback to us

    Wednesday, May 23, 2012 1:50 AM
    Moderator
  • Hello Tom

    Thanks for your reply.

    I have created a new Add-In project and stripped down the code for easier understanding and testing. And I have declared the CommandBarButton at class level. But the problem remains the same.

    And yes, there might be a misunderstanding: The command works and works and works... Not only once in the application, but every time it is called, and in every document when there are several documents open. The method only stops to be executed when I have created or opened several documents and then close one. And it is re-called as soon as I create or open enother document. I am sure that I'm doing something wrong. But what??

    This is the test project, all code in ThisAddIn.cs, in a project called CommandBarTest (Word 2010 Add-In for .NET Framework 4):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;
    using Word = Microsoft.Office.Interop.Word;
    using Office = Microsoft.Office.Core;
    using Microsoft.Office.Tools.Word;
    using Microsoft.Office.Interop.Word;
    using Microsoft.Office.Core;
    using System.Windows.Forms;
    
    namespace CommandBarTest
    {
        public partial class ThisAddIn
        {
    
          private static CommandBarButton commandBarButton;
    
          private void ThisAddIn_Startup(object sender, System.EventArgs e)
          {
            Application.DocumentOpen += new ApplicationEvents4_DocumentOpenEventHandler(Application_DocumentOpen);
            ((Word.ApplicationEvents4_Event)this.Application).NewDocument += new Word.ApplicationEvents4_NewDocumentEventHandler(ThisAddIn_NewDocument);
            Application.DocumentChange += new ApplicationEvents4_DocumentChangeEventHandler(Application_DocumentChange);
            if (Application.Documents.Count > 0)
            {
              ThisAddIn_NewDocument(Application.ActiveDocument);
            }
          }
    
          private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
          {
          }
    
          private void ThisAddIn_NewDocument(Word.Document document)
          {
            InitializeDocument();
          }
    
          private void Application_DocumentOpen(Word.Document document)
          {
            InitializeDocument();
          }
    
          void Application_DocumentChange()
          {
            // Explicitly do nothing
          }
          
          private void InitializeDocument()
          {
            if (Application.ActiveDocument.Type == WdDocumentType.wdTypeDocument)
            {
              Application.CustomizationContext = Application.ActiveDocument;
              ResetCommands();
              Application.ActiveDocument.Saved = true;
            }
          }
    
          private void ResetCommands()
          {
            foreach (CommandBar contextMenu in Application.ActiveDocument.CommandBars)
            {
              foreach (CommandBarControl control in contextMenu.Controls)
              {
                if (control.BuiltIn == false)
                {
                  try
                  {
                    control.Delete(false);
                  }
                  catch (System.Runtime.InteropServices.COMException)
                  {
                    // Explicitly do nothing
                  }
                }
              }
            }
            AddContextMenuCommands();
          }
    
          private void AddContextMenuCommands()
          {
            int numOfContextMenus = 1;
            foreach (CommandBar contextMenu in Application.ActiveDocument.CommandBars)
            {
              if (contextMenu.Type == MsoBarType.msoBarTypePopup && contextMenu.Name != "System")
              {
                CreateCommandBarButton(contextMenu, 1, numOfContextMenus, HelloWorld_Click, "OctHelloWorld", "Hello World", true, 0);
                numOfContextMenus = numOfContextMenus + 1;
              }
            }
          }
    
          private static CommandBarButton CreateCommandBarButton(CommandBar contextMenu, int position, int numOfContextMenu,
                _CommandBarButtonEvents_ClickEventHandler cmdCommand_Click, string tag,
                string caption, bool beginGroup, int faceId)
          {
            commandBarButton = (CommandBarButton)contextMenu.Controls.Add(MsoControlType.msoControlButton, Type.Missing, Type.Missing, position, true);
            commandBarButton.Caption = caption;
            commandBarButton.BeginGroup = beginGroup;
            // The Tag property MUST be set, otherwise the Click event is not registered.
            commandBarButton.Tag = tag;
            commandBarButton.FaceId = faceId;
            // The event must only be registered once; this is valid for all context menus:
            if (numOfContextMenu == 1)
            {
              commandBarButton.Click += cmdCommand_Click;
              // If Garbage Collection is not forced, the event only fires once per Word session (see
              // http://bytes.com/topic/c-sharp/answers/493442-vsto-outlook-addin-toolbar-button-event-handler-fires-one-time-only):
              System.GC.Collect();
            }
            return commandBarButton;
          }
    
          private static void HelloWorld_Click(CommandBarButton commandBarButton, ref bool cancelDefault)
          {
            MessageBox.Show("Hello World");
          }
    
        #region VSTO generated code
    
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InternalStartup()
            {
                this.Startup += new System.EventHandler(ThisAddIn_Startup);
                this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
            }
            
            #endregion
        }
    }
    

    What do you think? Can you (or anybody else) reproduce the phenomenon? And does someone know what's wrong?

    Kind regards

    Nora

    Wednesday, May 23, 2012 9:34 AM
  • Hi Nora

    For version 2010, I'd seriously consider customizing the context menu using the Ribbon. This is where things are headed in the future and, eventually, CommandBar objects aren't going to work in context menus any more, just as they no longer really work well in the Ribbon.

    As to the problem you're having, my guess is that it's because you're using only the one commandBarButton object (declared at class level) rather than an object for each button.

    If you really want to use CommandBar objects, try creating a class to manage them (assign properties, etc.), instantiating a new member of the class for each button. Declare an array or collection at the class level (where you now have commandBarButton) and add each object to that.

    It would probably also make sense to associate them with the specific document and destroy those when the document is closed.

    Another factor could be that you declare CustomizationContext only the one time. As best I can recall, if you're not passing the document object along the line of methods, you need to do that.

    Even better would be to declare a Word.Document object in InitializeDocument, assign ActiveDocument to that, then pass that through the chain. Word will constantly re-evaluate Application.ActiveDocument every time you use it, so this may be messing up CustomizationContext.

    Those are at least the first two things I'd try if I couldn't use Ribbon XML...


    Cindy Meister, VSTO/Word MVP

    Wednesday, May 23, 2012 2:11 PM
    Moderator
  • Hi Cindy Meister

    Thanks for you much appreciated reply!

    I do not insist on using CommandBar objects... So, before I check out all your suggestions about the CommandBar objects and the CustomizationContext: Could you point me to documentation/books/any information on how to customize the context menu using the ribbon? Important: It will have to be customizable by code, not by an XML file, as the Add-In gathers information at startup not only from one XML file, but also, for instance, from Excel tables (as they are easier for the users to manipulate; and the context menu shall be highly configurable).

    Thanks again, and kind regards

    Nora

    Thursday, May 24, 2012 6:59 AM
  • Hi Nora

    Here's the basic information:
    http://msdn.microsoft.com/en-us/library/ee691832.aspx

    But this does require Ribbon XML. However, it's possible to load the Ribbon XML as the add-in loads, rather than having it static. See for example:

    http://social.msdn.microsoft.com/forums/en-us/vsto/thread/86C1FC3C-2707-47B8-AB3C-C20028EACF4C


    Cindy Meister, VSTO/Word MVP

    Thursday, May 24, 2012 7:45 AM
    Moderator
  • Hi Cindy

    This looks interesting and might just be what I need. It means a lot of alteration to existing code and configurations though, and I will need some more time to have a closer look at this possiblity (after a week off -- today was kind of busy).

    Have a nice weekend

    Nora

    Friday, May 25, 2012 1:35 PM
  • Hi Nora,

    Any update?

    Have a good day,

    Tom


    Tom Xu [MSFT]
    MSDN Community Support | Feedback to us

    Monday, May 28, 2012 9:06 AM
    Moderator
  • Hi Cindy

    (I've been kept away from this project longer than planned, sorry for not reacting earlier.)

    I think the dynamicMenu might do the trick. But I still have some questions:

    1. How can I add dynamic commands directly to a context menu, without housing it in a context menu? (Looking like "Paragraph..." or "My Toggle Button" in the  attached screenshot.)
    2. How can I position such elements at the top of the context menus, not at the bottom?
    3. Is there I way to add such elements to ALL context menus at once? Or does one really have to know the designation of each of them?

    Kind regards

    Nora

    Friday, July 6, 2012 3:21 PM
  • Hi Cindy

    And still another question: Would I need to code in VBA instead of C# if I am using "onAction"? Or is there a way to add the click event handler?

    Kind regards

    Nora

    Tuesday, July 10, 2012 3:40 PM
  • Hi Nora

    I'm not sure I understand question (1). I don't know what "My Toggle Button" is supposed to be, but the Paragraph command in the context menu opens a dialog box (Form). IOW there's no additional context menu extension to the right - it's a simple <button> element.

    (2) Ribbon XML has the InsertBeforeMso attribute that allows you to position your custom control relative to a built-in control. So in this example you'd find out the idMso of the cut command (probably "Cut") and assign that value to the attribute.

    (3) You really have to define each and every individual context menu.

    (4) You can write the callback in your C# application. See the first part of the three-part article on customizing the ribbon for developers. You want section "Usng COM add-ins to Modify the RIbbon UI". The example, Point (8) under subsection "To access the host application and work with the Ribbon".


    Cindy Meister, VSTO/Word MVP

    • Marked as answer by nora.sailer Wednesday, July 11, 2012 2:25 PM
    Tuesday, July 10, 2012 4:16 PM
    Moderator
  • Hi Cindy

    To 1: I thought there might exist a DYNAMIC button, like there exists a dynamic menu.

    To 2: Do I need to know the insertBeforeMso-String of each top menu entry of each context menu? Or is there a way to position my customized commands by means of a number?

    To 3: Oh... Then I will first have to code some sort of Ribbon XML generator that creates the same buttons for all context menus.

    To 4: Of course -- I must have been blind!

    Thank you so much for your clear and quick answers and your admirable patience

    Nora

    Wednesday, July 11, 2012 2:24 PM
  • Hi Nora

    (2) You need to know the idMSO string of each top menu entry, I'm afraid. It would be nice if there was an "onTop" or "first" kind of setting but I'm afraid there's not. (And if I were Microsoft I'd probably not want it because then I'd have to answer a thousand times the question as to why it's not working - because there's another add-in using it too, and who "wins"?)

    FWIW it's simpler to just have them all at the bottom (default). Depending on where the selection is on the screen, the menu could open upwards, so then it would be closer. (Just trying to give you an additional headache <g>)


    Cindy Meister, VSTO/Word MVP

    • Marked as answer by nora.sailer Thursday, July 12, 2012 1:13 PM
    Wednesday, July 11, 2012 3:34 PM
    Moderator
  • Hi Cindy

    Ok, I'll just add the commands at the bottom then, that's just as fine.

    With your help, I'm getting along well now.

    Thanks again, have a nice day.

    Nora

    Thursday, July 12, 2012 1:12 PM