Office Development with Visual Studio

Develop Office Business Applications using Visual Studio

March, 2009

Posts
  • Office Development with Visual Studio

    Deploying Office Solutions with Visual Studio 2008 SP1 and Windows Installer (Mary Lee)

    • 1 Comments

    In Deploying Office Solutions with a Setup Project (Mary Lee), you read about how to deploy Visual Studio Tools for Office solutions with Windows Installer (.msi) in Visual Studio 2008.  With Visual Studio 2008 Service Pack 1, the steps are the same: add the project output to the Setup project, add registry keys, sign the application and deployment manifests, and so on.

    However if you are using launch conditions to verify that the prerequisites are installed, you must update the launch condition to look for the VSTO 3.0 SP1 runtime.

    In the Deploying a Visual Studio Tools for the Office System 3.0 Solution for the 2007 Microsoft Office System Using Windows Installer (Part 1 of 2) whitepaper, look at the To configure launch conditions procedure.  Change step 6c from this:

    Set the value of RegKey to Software\Microsoft\vsto runtime Setup\v9.0.21022

    to the following:

    Set the value of RegKey to Software\Microsoft\vsto runtime Setup\v9.0.30729

    We are working to update the whitepaper to reflect both of these registry keys. If you have questions about the whitepapers, please visit the Visual Studio Tools for Office MSDN Forum to search for answers and ask your questions.

     

    Happy deployment!

    Mary Lee, Programming Writer.

  • Office Development with Visual Studio

    Deploying Documents to a SharePoint Document Library (Mary Lee)

    • 3 Comments

    When you are done developing your customized Microsoft Excel 2007 or Microsoft Word 2007 document-level project in Visual Studio 2008, you may want to publish it to a SharePoint document library for others to use from the SharePoint site, or they can copy the file from the site to their desktop to use locally. In either case, the customization assembly cannot be stored in the SharePoint document library, so you have to configure the workbook to store the assembly on a file share.

    In Solution Explorer, right-click the Excel document-level project, and then click Publish. You'll see the Publish Wizard appear.

     

    In the first page of the Publish Wizard, type the folder name where you want Visual Studio to copy the files, known as the publish location. In this case, it's a subfolder of the project folder.

    image

     

    In the second page of the Publish Wizard, type the folder name where the installation files will be located. In this example, the installation files are located on a configure the workbook to look for the assembly on a file share, known as the install location.

    image

    Note: You could have set the publish location to this file share in step 1 if you have access rights to the file share. Setting the install location separate from the publish location allows you to publish the files locally and then hand the files over to an IT administrator to upload the files to a file share.

     

    The third and last page of the Publish Wizard is a summary of the publish options.

    image

     

    Copy the publish folder from step 1 to the file share, and upload the Excel workbook file to a SharePoint document library.

    image

    The final step is to trust the location of the SharePoint document library to the Trusted Location in Excel.  Copy the URL of the SharePoint document library and remove the last Forms/Allitems.aspx text from the URL.  For example, if your SharePoint document library is http://sharepoint/sites/writers/SampleDocLibrary/Forms/AllItems.aspx, the location to add to Excel is http://sharepoint/sites/writers/SampleDocLibrary.

    1. Open Excel.

    2. Click the Microsoft Office button. 

    3. Click Excel Options.

    4. Click Trust Center.

    5. Click Trust Center Settings.

    6. Click Trusted Locations.

    7. Check Allow Trusted Locations on my network.

    8. Click Add new location.

    9. In the Microsoft Office Trusted Location dialog box, in the Path textbox, type or paste the URL to the SharePoint document library. In this example, it's http://sharepoint/sites/writers/SampleDocLibrary.

    10. Click OK to close the Microsoft Office Trusted Location dialog box.

    11. Click OK to close the Trust Center dialog box.

    12. Click OK to close the Excel Options dialog box.

    Finally, open the Excel workbook file from the SharePoint document library. This starts the installation process for the Excel solution. Depending on the security settings, you may see the Microsoft Office Customization Installer dialog box prompting you to install the Excel solution.

     

    This is the blog version of the following topic in the MSDN Library: How to: Deploy a Document-Level Office Solution to a SharePoint Server (2007 System). You can also see the video demonstration at Video How to: Deploy a Document-Level Office Solution to a SharePoint Server (2007 System).

     

    Happy deployment!

    Mary Lee, Programming Writer.

  • Office Development with Visual Studio

    Video How Do I: Add Controls at Run Time in an Application-Level Project (Mary Lee)

    • 1 Comments

    In Walkthrough: Adding Controls to a Worksheet at Run Time in an Application-Level Project, you can see  to add host controls at run time in an application-level add-in by using Visual Studio 2008 SP1.  This video is posted to the How Do I Videos - Visual Basic page, part of the Visual Basic Developer Center.

    image

    How Do I: Add Controls to a Worksheet at Run Time in an Application-Level Project?

    Mary Lee, Programming Writer.

  • Office Development with Visual Studio

    Most Valuable Professionals and a Rocket Car

    • 1 Comments

    Microsoft Most Valuable Professionals (MVPs) are amazing!  They work really hard at their “day jobs” as developers and then they volunteer to help their local communities.  MVPs write blogs in many languages, deliver presentations at local User Groups, and answer questions on the Forums.  Once a year they fly to Redmond for a special conference to meet with the product teams.  This year 6 of the 15 VSTO MVPs came to Redmond, in the cold and rainy winter and a crappy economy, to learn what’s up in Microsoft and give us a ton of really detailed technical feedback. 

    This year’s MVPs from the VSTO specialty included:

    Robert Green, Helmut Obertanner, Ryosuke Uemoto, Maarten van Stam, and Mike Walker. 

    The feedback flowed freely after a few Washington microbrews and greasy appetizers in one of the Microsoft cafeterias.  Here’s a picture of me, Maarten, Steve and Mike in front of my rocket car, which is called the Cappisen 38.

    Capissen38_MVPsummit

    What did we talk about?  Everything!  We previewed some future stuff, and we really dove into the deep technical architecture of the VSTO Runtime, the Primary Interop Assemblies, and how the Office security model effects deployment.  Richard Cook and Misha Shneerson ran the deep dive discussion.  Rachel Schaw and Lily Ma presented an end-to-end OBA demonstration and Saurabh Bhatia presented ClickOnce deployment in great detail. 

  • Office Development with Visual Studio

    My Word Add-In Creates Duplicate Menu Items. Make it Stop! (Norm Estabrook)

    • 14 Comments

    So I want my add-in to place a custom command into the shortcut menu. The shortcut menu is that cool menu that appears when you right-click a document. Great, so I read some articles in MSDN, write some code, run the add-in and voila there it is! I give it to my buddy, he is proud of my accomplishment and and installs my add-in.  Now he hates me because every time he opens up Word, a duplicate menu appears.  Where did I go wrong?

    Well actually, I didn’t do anything wrong.  It’s just that Word requires a little more attention when it comes to handling menus. I guess you can say that Word is a bit more “needy” than other Office applications. But being “higher maintenance” does not have to mean “higher maintenance costs”.  Hopefully this post will get your friend talking to you again.

    My code

    Here is the code that did not work for me.  BTW – I will paste in both Visual Basic and C# examples for this post.

    [Visual Basic]

    Private MyApplication As Word.Application
    Private WithEvents myControl As Office.CommandBarButton
    
    Private Sub ThisAddIn_Startup _
    (ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
    
        MyApplication = Me.Application
        AddMenuItem()
    
    End Sub
    
    Private Sub AddMenuItem()
    
        Dim menuItem As Office.MsoControlType = _
            Office.MsoControlType.msoControlButton
    
        myControl = CType(MyApplication.CommandBars("Text").Controls.Add _
           (menuItem, 1, True), Office.CommandBarButton)
    
        myControl.Style = Office.MsoButtonStyle.msoButtonCaption
        myControl.Caption = "My Menu Item"
        myControl.Tag = "MyMenuItem"
    
    End Sub
    
    
    Sub myControl_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _
                        ByRef CancelDefault As Boolean) Handles myControl.Click
    
        System.Windows.Forms.MessageBox.Show("My Menu Item clicked")
    
    End Sub

    [C#]

    private Word.Application myApplication;
    private Office.CommandBarButton myControl;
    
    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        myApplication = this.Application;
        AddMenuItem(); 
    }
     
    private void AddMenuItem()
    {
       Office.MsoControlType menuItem = 
            Office.MsoControlType.msoControlButton;
    
        myControl = 
            (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add
            (menuItem,missing, missing, 1, true);
    
        myControl.Style = Office.MsoButtonStyle.msoButtonCaption;
        myControl.Caption = "My Menu Item";
        myControl.Tag = "MyMenuItem";
    
        myControl.Click += 
            new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
                (myControl_Click);
    
    }
    
    void myControl_Click(Microsoft.Office.Core.CommandBarButton Ctrl, 
        ref bool CancelDefault)
    {
        System.Windows.Forms.MessageBox.Show("My Menu Item clicked");
    }

    Why my code does not work as expected

    Here is one issue I can see right off the bat. Note this line of code for adding a control:

    myControl = CType(MyApplication.CommandBars("Text").Controls.Add _ (menuItem, 1, True), Office.CommandBarButton)

    I set the last parameter of the Add method to True.  This value specifies that I want my control to be temporary. I am trying to tell Word not to save the control so that duplicate menu items won’t be added every time Word opens.  Only there is a problem here.  Word ignores this parameter (at least for controls in a CommandBar collection anyway). So I can keep it set to true, but it really means false. Lovely.

    So what is happening? Well, Word is actually saving your new menu command to the Normal.dot template every time a new instance of Word opens – hence the duplicates.

    What can I do about this?

    There are probably a billion creative ways to stop the duplicate menus from appearing, but here are three tips that work really well. Here they are:

    • Check for duplicates before adding an item (control) to a menu.
    • Always set the customization context of the application to the same document or template before adding or deleting a control.
    • Because there are no temporary commands in Word, use a custom template to save the commands.

    Tip # 1: Check for Duplicates:

    This one is pretty easy.  Just add code to your add-in that looks for a control that has the same tag as the control you are about to add.  If one exists, perform one of the following actions:

    • Don’t add the control (It is already there).
    • Delete the control. Then you can add it.

    I modified my code to delete the control. Here is my code.

    [Visual Basic]

    Private Sub RemoveExistingMenuItem()
    
        Dim contextMenu As Office.CommandBar = _
        MyApplication.CommandBars("Text")
    
        MyApplication.CustomizationContext = customTemplate
    
        Dim control As Office.CommandBarButton = contextMenu.FindControl _
            (Office.MsoControlType.msoControlButton, System.Type.Missing, _
             "MyMenuItem", True, True)
    
        If Not (control Is Nothing) Then
            control.Delete(True)
        End If
    
    End Sub

    [C#]

    private void RemoveExistingMenuItem()
    {
        Office.CommandBar contextMenu = myApplication.CommandBars["Text"];
        myApplication.CustomizationContext = customTemplate;
    
        Office.CommandBarButton control = 
            (Office.CommandBarButton)contextMenu.FindControl
            (Office.MsoControlType.msoControlButton, missing,
            "MyMenuItem", true, true);
    
        if ((control != null))
        {
            control.Delete(true);
        }
    
    }

    Tip #2: Set the Customization Context

    The customization context of the application tells Word where to save your customizations. To specify the customization context, set the CustomizationContext property of the Application object.

    By default, Word uses the Normal.dot template as it’s customization context.  This is not reliable and can change. If you do not explicitly set the context, you might search for controls saved to one context such as a document, delete controls from another context such as a custom template and then add the control to another context such as Normal.dot.

    To avoid these issues, always set the customization context of the application to the same document or template every time you search for, delete, or add controls to a menu.

    Note that in a Word document-level customization, it is probably best to set the customization context to the active document.  That way when the user uninstalls the customization, the document and the menu commands that pertain to that document disappear as expected.

    In Word application-level add-in, the best practice is to use a custom template for reasons mentioned later on in this post.

    In the following example, I highlighted in bold the line that sets the customization context.  Further along in this post, I will show you where I got customTemplate.

    [Visual Basic]

    Private Sub AddMenuItem()
    
        MyApplication.CustomizationContext = customTemplate
    
        Dim menuItem As Office.MsoControlType = _
            Office.MsoControlType.msoControlButton
    
        myControl = CType(MyApplication.CommandBars("Text").Controls.Add _
           (menuItem, 1, True), Office.CommandBarButton)
    
        myControl.Style = Office.MsoButtonStyle.msoButtonCaption
        myControl.Caption = "My Menu Item"
        myControl.Tag = "MyMenuItem"
        customTemplate.Saved = True
    
        GC.Collect()
    
    End Sub

    [C#]

    private void AddMenuItem()
    {
        myApplication.CustomizationContext = customTemplate;
        Office.MsoControlType menuItem = 
            Office.MsoControlType.msoControlButton;
    
        myControl = 
            (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add
            (menuItem,missing, missing, 1, true);
    
        myControl.Style = Office.MsoButtonStyle.msoButtonCaption;
        myControl.Caption = "My Menu Item";
        myControl.Tag = "MyMenuItem";
    
        myControl.Click += 
            new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
                (myControl_Click);
    
        customTemplate.Saved = true;
        GC.Collect();
    }

    Also – a quick tip – set the Saved property of the template to true after you add the control.  This stops that annoying prompt from appearing that asks if you would like to save your customizations to the template.

    Tip #3: Use a Custom Template

    It is very difficult to delete a control before the add-in shuts down.  For example, if you attempt to delete a control in the ThisAddIn_Shutdown event handler, you will receive a not so helpful COM exception.  You will get similar results in the Quit event of Word. 

    That is because the template that you are using as your application’s customization context is not writable in either of those event handlers.  So if you cannot easily delete the control when Word closes, that means that the control will always live inside of the template.

    This is a problem if you are using Normal.dot to persist the controls.  Here is why.  Let’s say the user decides that he does not want to see your command in a menu anymore.  With a caption such as “My Menu Item”, can you really blame him? So the user uninstalls your add-in. However, the menu command still lives in Normal.dot! When that user opens his document in Word, "My Menu Item” still appears. Doooh! Here comes the support calls!

    The way around this is to provide your own custom template to store customizations such as custom menus and menu items.  Your setup application can remove the template along with add-in. That way when the user uninstalls the add-in, they also remove the template that contains the menu items.

    In the following example, I retrieve a custom template from the users documents folder. Yes, your setup application will probably use a different location to place the custom template, but this is just for an example.

    [Visual Basic]

    Private Sub GetCustomTemplate()
        Dim TemplatePath As String = Environment.GetFolderPath _
            (Environment.SpecialFolder.MyDocuments) + "\MyCustomTemplate.dotx"
        Dim install As Boolean = True
    
        For Each installedTemplate As Word.Template In MyApplication.Templates
            If installedTemplate.FullName = DirectCast(TemplatePath, String) Then
                install = False
            End If
        Next
    
        If install = True Then
            MyApplication.AddIns.Add(TemplatePath.ToString(), True)
        End If
    
        customTemplate = MyApplication.Templates(TemplatePath)
    End Sub

    [C#]

    private void GetCustomTemplate()
    {
        object TemplatePath = Environment.GetFolderPath
            (Environment.SpecialFolder.MyDocuments) +
            "\\MyCustomTemplate.dotx";
        object install = true;
        
        foreach (Word.Template installedTemplate in myApplication.Templates)
        {
            if (installedTemplate.FullName == (string)TemplatePath)
            {
                install = false;
            }
        }
        if ((bool)install)
        {
            myApplication.AddIns.Add(TemplatePath.ToString(), ref install);
        }
        customTemplate = myApplication.Templates.get_Item(ref TemplatePath);
    
    }

    Drum roll please .. I present the complete example

    To provide context, here is the complete sample:

    [Visual Basic]

    Public Class ThisAddIn
    
        Private MyApplication As Word.Application
        Private WithEvents myControl As Office.CommandBarButton
        Private customTemplate As Word.Template
    
        Private Sub ThisAddIn_Startup _
        (ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
    
            MyApplication = Me.Application
    
            GetCustomTemplate()
            RemoveExistingMenuItem()
            AddMenuItem()
    
        End Sub
    
        Private Sub GetCustomTemplate()
            Dim TemplatePath As String = Environment.GetFolderPath _
                (Environment.SpecialFolder.MyDocuments) + "\MyCustomTemplate.dotx"
            Dim install As Boolean = True
    
            For Each installedTemplate As Word.Template In MyApplication.Templates
                If installedTemplate.FullName = DirectCast(TemplatePath, String) Then
                    install = False
                End If
            Next
    
            If install = True Then
                MyApplication.AddIns.Add(TemplatePath.ToString(), True)
            End If
    
            customTemplate = MyApplication.Templates(TemplatePath)
    
        End Sub
    
        Private Sub RemoveExistingMenuItem()
    
            Dim contextMenu As Office.CommandBar = _
            MyApplication.CommandBars("Text")
    
            MyApplication.CustomizationContext = customTemplate
    
            Dim control As Office.CommandBarButton = contextMenu.FindControl _
                (Office.MsoControlType.msoControlButton, System.Type.Missing, _
                 "MyMenuItem", True, True)
    
            If Not (control Is Nothing) Then
                control.Delete(True)
            End If
    
        End Sub
    
    
        Private Sub AddMenuItem()
    
            MyApplication.CustomizationContext = customTemplate
    
            Dim menuItem As Office.MsoControlType = _
                Office.MsoControlType.msoControlButton
    
            myControl = CType(MyApplication.CommandBars("Text").Controls.Add _
               (menuItem, 1, True), Office.CommandBarButton)
    
            myControl.Style = Office.MsoButtonStyle.msoButtonCaption
            myControl.Caption = "My Menu Item"
            myControl.Tag = "MyMenuItem"
            customTemplate.Saved = True
    
            GC.Collect()
    
        End Sub
    
        Sub myControl_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _
                            ByRef CancelDefault As Boolean) Handles myControl.Click
    
            System.Windows.Forms.MessageBox.Show("My Menu Item clicked")
    
        End Sub
    
        Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
    
        End Sub
    
    End Class
    

    [C#]

    public partial class ThisAddIn
    {
        private Word.Application myApplication;
        private Office.CommandBarButton myControl;
        private Word.Template customTemplate;
    
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            myApplication = this.Application;
    
            GetCustomTemplate();
            RemoveExistingMenuItem();
            AddMenuItem();
        }
    
        private void GetCustomTemplate()
        {
            object TemplatePath = Environment.GetFolderPath
                (Environment.SpecialFolder.MyDocuments) +
                "\\MyCustomTemplate.dotx";
            object install = true;
            foreach (Word.Template installedTemplate in myApplication.Templates)
            {
                if (installedTemplate.FullName == (string)TemplatePath)
                {
                    install = false;
                }
            }
            if ((bool)install)
            {
                myApplication.AddIns.Add(TemplatePath.ToString(), ref install);
            }
            customTemplate = myApplication.Templates.get_Item(ref TemplatePath);
    
        }
    
        private void RemoveExistingMenuItem()
        {
            Office.CommandBar contextMenu = myApplication.CommandBars["Text"];
            myApplication.CustomizationContext = customTemplate;
    
            Office.CommandBarButton control =
                (Office.CommandBarButton)contextMenu.FindControl
                (Office.MsoControlType.msoControlButton, missing,
                "MyMenuItem", true, true);
    
            if ((control != null))
            {
                control.Delete(true);
            }
    
        }
    
        private void AddMenuItem()
        {
            myApplication.CustomizationContext = customTemplate;
            Office.MsoControlType menuItem =
                Office.MsoControlType.msoControlButton;
    
            myControl =
                (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add
                (menuItem, missing, missing, 1, true);
    
            myControl.Style = Office.MsoButtonStyle.msoButtonCaption;
            myControl.Caption = "My Menu Item";
            myControl.Tag = "MyMenuItem";
    
            myControl.Click +=
                new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler
                    (myControl_Click);
    
            customTemplate.Saved = true;
    
            GC.Collect();
    
        }
    
        void myControl_Click(Microsoft.Office.Core.CommandBarButton Ctrl,
            ref bool CancelDefault)
        {
            System.Windows.Forms.MessageBox.Show("My Menu Item clicked");
        }
        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }
    }
    Now you can issue a patch to your buddy so that he has only one “My Menu Item” appearing in his shortcut menu.  Although .. I am not sure what “My Menu Item” is really suppose to do …
Page 1 of 1 (5 items)