Developing .NET Smart Clients for Microsoft Office XP

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Simon Guest and Siew-Moi Khor
Microsoft Corporation

December 2002

Applies to:
    Microsoft® Office XP

Summary: Use this step-by-step guide to learn about creating and extending smart client solutions for Microsoft Office XP using Microsoft .NET. We start by looking at today's development opportunities with Office XP, the process of creating an Office add-in using Microsoft Visual Studio .NET, and then follow some design guidelines to help build a stable and reusable application. (40 printed pages)

Contents

Introduction
What is a Smart Client?
What Makes Developing with Microsoft Office So Attractive?
How Do Office and .NET Interoperate?
How to Build an Add-in
Debugging the Add-in
Calling an XML Web Service Through the Add-in
Thinking About the Design of the Add-in
Adding Outlook References
The Application Object
Developing the User Experience
Building a Menu Structure in Office
Code Patterns
Creating and Working with Office Objects
Performance Considerations When Working with Office Objects
Displaying and Using the Office Assistant
Displaying Windows Forms in Office
Handling Exceptions
Conclusion

Introduction

Through a layer called COM interop, you can access the Microsoft® Office XP programming model from the managed Microsoft .NET world. This article outlines some of the building blocks to developing .NET smart client applications for Office XP—from creating your first add-in to developing a fully fledged enterprise application.

The examples used throughout this article are based on Microsoft Outlook® 2002, but these ideas and techniques can be applied to other applications of the Office XP suite. The best way to use this paper is as a tutorial from start to finish. We start by building a simple Component Object Model (COM) add-in, and then expand upon the functionality in each section.

It should be noted that the installation and deployment of managed COM add-ins need special security considerations and handling. To ensure a successful deployment of digitally signed managed COM add-ins you will need to incorporate into your managed COM add-in project a small unmanaged proxy called a shim. There are already articles extensively written on this matter and it is strongly recommended that you thoroughly acquaint yourself with the issues:

This article is written with the assumption that the reader is comfortable using Office XP and developing client applications using Microsoft Visual Studio® .NET, preferably in Microsoft Visual C#™ so that the samples can be followed. Knowledge of programming Office using Microsoft Visual Basic® Scripting Edition (VBScript) or Microsoft Office XP Visual Basic for Applications (VBA) is useful, but not required for this paper.

What is a Smart Client?

This is a common question for developers of smart clients in .NET. To summarize, a smart client should:

  • Leverage local processing power
  • Consume XML Web services
  • Support online and offline scenarios

Microsoft .NET promotes the development of smart client applications by offering a compelling solution to develop clients that offer the services described above.

What Makes Developing with Microsoft Office So Attractive?

One of the most compelling reasons for developing using Office is that the learning curve for the end user is greatly reduced. Given its popularity, it is fair to say that Office is a great starting point for developing very popular and widely distributable smart client applications. Here are a couple of examples:

  • Imagine a stock trading application—would it be quicker for the user to learn a new interface to buy and sell stocks, or simply to enter values into a Microsoft Excel sheet and hit a "commit" button to make the trade?
  • Customer relationship management (CRM) application—does an experience where the user is required to press ALT+TAB between their existing version of Outlook and another contact management system lend itself to the best user experience, as opposed to one that allows you to add contacts into Outlook (and the addition into the CRM system is taken care of in the background)?

How Do Office and .NET Interoperate?

Today, there is no "magic" interface to expose and call the Office application programming interfaces (APIs) from .NET. It would be nice if we could connect the two using this:

Aa163619.odc_dnscof01(en-us,office.10).gif

Figure 1. The ideal connectivity for .NET and Office

To achieve connectivity as shown above, we need to look at existing technology that is available today. Office exposes a number of interfaces and objects through COM. To access this through .NET, we have to create a runtime callable wrapper (RCW), which allows these COM objects to be called from a .NET assembly.

The end result looks something like the following:

Aa163619.odc_dnscof02(en-us,office.10).gif

Figure 2. Today's connectivity for .NET and Office

For the developer, there is very little overhead in creating the RCW as the process is automatic if using Visual Studio .NET, and fortunately for the end user, a .NET assembly that is integrated with Office using this method can be made to look and feel as part of the Office application.

The purpose of this document is not to dig into how the interop layer works, but more the practicalities of creating a smart client application. For further details on the RCW technology, the Microsoft Office and .NET Interoperability article is a good starting point.

How to Build an Add-in

Prerequisites

To follow the examples and code in this article, you will need to install the Microsoft Office XP Primary Interop Assemblies (PIAs). The Readme file included in the download has the instructions on how to install the PIAs.

Creating a New Project

We will start with a walkthrough on how to create a Visual Studio .NET Shared Add-In project:

  1. Click Start, point to Programs, point to Microsoft Visual Studio .NET, and click Microsoft Visual Studio .NET.

  2. On the File menu, point to New, and click Project. The New Project dialog box appears.

  3. In the Project Types pane, double-click the Other Projects folder, and click Extensibility Projects.

  4. In the Templates pane, click Shared Add-In.

  5. In the Name box, type a name for the Shared Add-In project. For this sample, name it MyAddIn as shown in Figure 3.

    **Note   **If you plan to follow the demo code throughout this article, it is recommended that you keep the project name as MyAddIn (note the case). Otherwise, you will have to change the namespace declaration if you copy and paste the code from this article.

    Click here for larger image.

    Figure 3. New Project selection dialog box (click picture for larger image)

  6. In the Location box, save the project at "c:\temp" as shown in Figure 3. Click OK. The Extensibility Wizard appears as shown in Figure 4.

    Click here for larger image.

    Figure 4. The Extensibility Wizard (click picture for larger image)

  7. Click Next. The Select a Programming Language page appears. Select the language that you wish to use to create the add-in. Currently supported languages for creating shared add-ins are Visual C#, Visual Basic .NET and C++/ATL. The samples in this paper use Visual C# throughout.

  8. Select Create an Add-in using Visual C# as shown in Figure 5, then click Next. The Select An Application Host page appears.

    Click here for larger image.

    Figure 5. Selecting the programming language for the add-in (click picture for larger image)

  9. Once the language has been selected, a list of supported applications is displayed as shown in Figure 6. This list shows the host applications for which an add-in can be created. For this sample, select Microsoft Outlook.

    Click here for larger image.

    Figure 6. Selecting the host application for the add-in (click picture for larger image)

  10. Click Next. The Enter a Name and Description page appears.

  11. In the What is the name of your Add-In box, type the name of the add-in as ExampleAddIn as shown in Figure 7.

  12. In the What is the description of your Add-In box, type the description for the add-in.

    Click here for larger image.

    Figure 7. Naming the add-in (click picture for larger image)

  13. Click Next. The Choose Add-In Options page appears. In the next step, you need to specify whether the add-in gets loaded when the application is loaded, and whether the application is available for all users. Most of the time, loading the application on startup is always a good idea. Even if the user does not immediately activate it, you have everything loaded in memory for when they are ready.

  14. Check the I would like my Add-in to load when the host application loads and/or My Add-in should be available to all users of the computer it was installed on, not just the person who installs it boxes as appropriate as shown in Figure 8.

    Click here for larger image.

    Figure 8. Choosing add-in options (click picture for larger image)

  15. Click Next. The Summary page appears, as shown in Figure 9.

  16. Click Finish, and wait while the Shared Add-In project is built.

    Next, we will investigate what has been created.

    Click here for larger image.

    Figure 9. Completing the add-in (click picture for larger image)

  17. Closing the wizard will generate two projects under one solution file, and if everything went well, it should look as shown in Figure 10.

    Aa163619.odc_dnscof10(en-us,office.10).gif

    Figure 10. The add-in as displayed in the Solution Explorer

The MyAddIn project contains one created class (Connect.cs) which we will look at shortly.

The second project, MyAddInSetup, is also automatically generated for you. This is a Microsoft Installer (MSI) project file used to create a setup application for the add-in. Unless explicitly included, this project does not get built when you hit F5 to compile.

Some scenarios where an MSI is useful:

  • Something gets corrupted and you need to reinstall the add-in.
  • You wish to deploy the add-in to another user or machine.

Examining the Generated Code

We will now examine the Connect.cs class file.

The class itself implements the Extensibility.IDTExtensibility2 interface, which is core to making the add-in work with Office. By default, you will get a number of methods created:

  • OnConnection. This method is called whenever the add-in is loaded by an Office application.
  • OnDisconnection. This method is called whenever the add-in is unloaded by an Office application.
  • OnAddInsUpdate. This method is called whenever the add-in changes (for example, if we were to download or load a new version of an assembly while the add-in was still running).
  • OnStartupComplete. This method is called whenever the add-in is completed.
  • OnBeginShutdown. This method is called whenever the application is shutting down. The difference between OnBeginShutdown and OnDisconnection is that the OnBeginShutdown method still has access to some of the Microsoft Windows® Explorer controls. For example, you may want to remove a window or change a control just as the user shuts down the application.

There are also two generic objects defined in the class, applicationObject and addInInstance. For the purpose of the add-in, applicationObject is the most valuable as it gives us access to the programming model and environment that we need.

Try compiling the solution, but don't run the solution yet. To compile, right-click on Solution MyAddIn' (2 projects) and click Build Solution. You should see a successful compile, indicated by:

Build: 1 succeeded, 0 failed, 1 skipped

The one file that is skipped is the Setup Project that we described earlier.

Creating the Hello World Application

An example wouldn't be an example if we did not have the mandatory Hello World test, and this walkthrough is certainly no exception to the rule.

To prove that the add-in is being loaded successfully by Outlook, we are going to display a "Hello World" message on startup. To do this, follow these steps.

  1. In the Solution Explorer, first click the My AddIn project.

  2. In the Project menu, click Add Reference. This displays the Add Reference dialog box.

  3. Select the System.Windows.Forms.dll reference as shown in Figure 11.

  4. Click Select, then OK.

    This is required to display the message dialogs and Windows Forms within the application.

    Click here for larger image.

    Figure 11. Adding a reference (click picture for larger image)

  5. Next, insert the following using statement at the top of the Connect.cs class:

    using System.Windows.Forms;
    
  6. Locate the OnStartupComplete method within the code that has been generated. Within this method, insert the following line:

    MessageBox.Show("Hello World - Your Add In is working successfully.");
    
  7. All the code is now complete. Compile the project, but don't run it yet. To compile, right-click on Solution MyAddIn' (2 projects) and click Rebuild Solution.

Running the Hello World Sample

Before you can run the add-in, you need to set the project properties to launch and debug Outlook from within Visual Studio .NET:

  1. In Solution Explorer, right-click on the MyAddIn project and click Properties.

    Click here for larger image.

    Figure 12. Setting the project properties (click picture for larger image)

  2. In the properties window, select Configuration Properties and click Debugging as shown in Figure 12.

  3. Set the Start Application property to the location of the Outlook executable on your machine. On a default installation of Office XP, this should be:

    C:\Program Files\Microsoft Office\Office10\OUTLOOK.EXE
    
  4. In the same window, set the Working Directory to be the same directory as the OUTLOOK.EXE application resides within. For example:

    C:\Program Files\Microsoft Office\Office10
    
  5. Click OK, and you should be in a good position to test the sample add-in.

  6. Press F5 to compile and run the project. If successful, Outlook 2002 should launch, and the test message displayed when the add-in starts up as shown in Figure 13.

    **Note   **If you currently have Outlook 2002 running, close it before hitting F5.

    Aa163619.odc_dnscof13(en-us,office.10).gif

    Figure 13. Running the add-in

Debugging the Add-in

Before we move forward with developing this example (into something a little more useful!), we will first take a look at the environment in which we are working.

You may have noticed that Visual Studio .NET was responsible for launching the Outlook 2002 application as a process. Because of this we are in a good position if we want to pause, terminate or debug the application when required. To test, close Outlook (wait until Outlook terminates) and go back to the code and insert a breakpoint at the MessageBox line as shown in Figure 14. (To insert a breakpoint, right-click on that line and click Insert Breakpoint.)

Click here for larger image.

Figure 14. Setting a breakpoint in the add-in

Press F5 to restart the application. What you should observe is Outlook 2002 starting up as usual, but when the add-in is loaded and the breakpoint is reached, control is returned back to Visual Studio .NET.

Click here for larger image.

Figure 15.    Trapping a breakpoint in the add-in (click picture for larger image)

This is very powerful because it gives us a very granular mechanism through which to debug. At any point during the lifetime of the add-in, you can effectively "pause" Outlook and investigate what is going on underneath the covers — investigating both .NET and Office objects.

**Caution   **Although pausing and restarting Outlook can help in the debugging process, try to avoid terminating the Outlook process, that is, stopping the project from running halfway through. When you quit Outlook, wait for the Outlook process to finish cleanly and for control to be returned to Visual Studio .NET before continuing.

This may take a few seconds depending on the complexity of the add-in that you are writing, but terminating the process halfway through sometimes causes more problems than it is worth. Outlook has a feature where it can disable add-ins if it thinks that they are corrupted or damaged, which can in some cases indicate that you need to reinstall the add-in using the setup program generated in the first step.

Another option for debugging is to attach to the Outlook process when it is already running. This is especially useful if you have deployed an add-in, and don't have the source code from which to launch the project and debugger. To test this, do the following:

  1. Close both Outlook 2002 and Visual Studio .NET. Remember to save your project as we will be using it for later examples.

  2. Reopen Outlook 2002 by selecting it from the menu or icon from which you normally launch it. If the add-in is still working (and it should be!), you should see the Hello World message reappear in the Outlook window on startup. Don't click on the OK button for now.

  3. Launch Visual Studio .NET, but do not open a project at this time. Instead, in the Tools menu, click Debug Processes.

  4. Scroll down to select the Outlook process (Outlook.exe) and click Attach as shown in Figure 16.

    Click here for larger image.

    Figure 16. Debugging processes (click picture for larger image).

  5. In the Attach to Process dialog box, select the Common Language Runtime and Native options to debug. Click OK.

  6. In the Processes dialog box, click Break to intercept the process — you should be positioned at the message box point where we were in the previous example.

    **Note   **Watch the instances. In the previous example, we launched the Outlook process separately and then ran the debugger to attach to it. While this works, the Outlook process places a lock on the add-in DLL that we initially created—which means that we cannot recompile the project without first closing the Outlook application. When we attempt to do this, we get an error saying Cannot copy temporary files to the debug directory.

Removing the Add-in

You may have been developing the add-in on the main machine from which you read your e-mail messages. If so, you may want to move to your development machine. However, first you need to remove the add-in from Outlook (as it keeps loading even when you are not launching the process from Visual Studio .NET).

To remove an add-in, return to Visual Studio .NET, compile and run the add-in Setup project that was generated as part of the initial solution. Do this by right-clicking on MyAddInSetup, and click Rebuild. Then, run the installer application that is generated by the project by right-clicking on MyAddInSetup, and click Install. This will reinstall the add-in, but more importantly, place an icon in your Add/Remove Programs window. From there, you can remove the add-in as you would with any other program and it will cleanly remove the files and registry settings from your machine.

If you want to reinstall the add-in to continue with the rest of this walkthrough, simply rerun the add-in Setup installer.

Calling an XML Web Service Through the Add-in

At this point, this Hello World message is getting a little repetitive now. For a change, it would be fun to consume an XML Web service, for example, a "Quote of the Day" XML Web service and display that message instead.

This example shows how this can be done—not so much demonstrating the actual quote XML Web service itself, but more to show once the add-in is loaded, how easy it is for developers to create applications that consume XML Web services and present them through Outlook. This also makes for an alternative to using the Office XP Web Services Toolkit and calling the XML Web service from VBScript.

To add the XML Web service in our example, make sure that Outlook is closed and reopen Visual Studio .NET and the add-in project that you saved earlier by clicking on MyAddIn.sln located in C:\temp\MyAddIn.

  1. In the Solution Explorer, right-click on the MyAddIn project and click Add Web Reference. This displays the Add Web Reference UDDI window as shown in Figure 17.

    Click here for larger image.

    Figure 17. Adding a Web Reference (click picture for larger image)

  2. If you don't have access to the Web directly from your machine, you may want to create a test XML Web service that generates quotes. For the purpose of this example, it is assumed that you have access to the Web, and we will pick a predefined one from the UDDI registry. Click on the UDDI Directory Hyperlink in the window as shown above. A search window will be displayed as shown in Figure 18.

    Click here for larger image.

    Figure 18. Searching UDDI (click picture for larger image)

  3. In the Service name field box, type QUOTE and click Search. A list of matching services will be displayed:

    Click here for larger image.

    Figure 19. Search results from UDDI (click picture for larger image)

  4. Expand the Quote of the Day service under the GOTDOTNET header by clicking on the + symbol.

    Click here for larger image.

    Figure 20. Selecting the Quote of the Day Service from UDDI (click picture for larger image)

  5. Scroll down—here we are shown a short description of the Quote XML Web service itself as shown in Figure 20. Click on the hyperlink underneath the Interface Definitions: header.

  6. The contract for this XML Web service should then be loaded, and the Add Reference button should become enabled as shown in Figure 21.

    Click here for larger image.

    Figure 21. The WSDL contract for the Quote of the Day XML Web service (click picture for larger image)

  7. Click Add Reference to add the reference to this XML Web service and return to the Visual Studio .NET integrated development environment (IDE).

  8. Under the project Web References, you should now see the following XML Web service:

    Aa163619.odc_dnscof22(en-us,office.10).gif

    Figure 22. The Quote of the Day XML Web service reference

    We can now start calling this XML Web service from our add-in. To do this, locate the OnStartupComplete method in the Connect.cs class.

  9. Replace the code we wrote earlier:

    MessageBox.Show("Hello World - Your Add In is working successfully.");
    

    With the following code:

    com.gotdotnet.www.Quote q = new com.gotdotnet.www.Quote();
    MessageBox.Show(q.GetQuote().Trim());
    

    This code makes a new instance of the quote XML Web service (q) and calls the GetQuote method from the service to retrieve a quote. This quote is then displayed in a message box within the add-in itself.

  10. Once this is written, recompile and run the add-in. If everything has worked successfully, you should see a quote that is obtained by the XML Web service appears as Outlook starts as shown in Figure 23.

    Click here for larger image.

    Figure 23. Running the new version of the add-in (click picture for larger image)

We're not sure how applicable that quote is, but hopefully this has demonstrated how easy it is to consume an XML Web service within .NET and display output from them out using Outlook as a client.

Preparing to Further Develop the Add-in

We are going to pick up the pace somewhat, and start exploring why we are really using Outlook as the client to host these smart client add-ins—as mentioned in the introduction, to take advantage of the user interface that already exists for us upon which to develop.

In this section, we will be showing how to create a menu, how to work with objects specific to Office, and how to integrate custom Windows Forms that we create into the Office application.

Thinking About the Design of the Add-in

Before we dive into the code to build up the user interface, we will spend some time to think about how the add-in is going to run, and what processes we need to build in order to make the solution manageable and robust.

We are going to want the add-in to initialize when it is first started. Initialization may require a number of actions to be taken by the add-in. These could be to create menus, create other objects within the Office application, register events, and so on.

Once these events are complete, we can expect control to be handed back to the user. The add-in will only "come back to life" if a user triggers an event that is being listened for (for example, clicking on a menu item, and creating a new Office object against which we have registered events).

The core classes required for an add-in to develop into a fully fledged application are broken down into the Connect, Init, OfficeUI, Events, and Actions classes.

Click here for larger image.

Figure 24. Recommended core classes (click picture for larger image)

The Connect class gets called when Outlook is loaded. It is passed a context (the Application object that was previously mentioned will be covered in the next section) and will be responsible for maintaining this context for the life of the add-in.

Imagine a process where we are creating a menu structure within Outlook and want to display our Quote message when that menu option is clicked.

Click here for larger image.

Figure 25. Calling the Init class (click picture for larger image)

Using the above sequence diagram, we can see that the Connect object should call an Init object and instruct it to start the initialization routines. Part of this initialization routine will be to create the relevant Office user interface experience, that is, the menu.

The menu is now created by the OfficeUI class—but that does not automatically mean that the menu is usable. We have to register some events with the menu. To do this, we call an Events class. After the events are registered, initialization is complete.

Click here for larger image.

Figure 26. Complete sequence diagram (click picture for larger image)

At this point, the only thing that is missing from our process diagram is the action that is taken when the event is triggered.

For the menu, we are going to call an action in the Actions class named MenuItemClicked.

Click here for larger image.

Figure 27. Complete sequence diagram with Action (click picture for larger image)

Turning the Diagram into Code

We now have a good concept of our objects and how they should be called. To make this real in our example application, we are going to create four blank classes.

  • Init.cs
  • OfficeUI.cs
  • Events.cs
  • Actions.cs

Now, create these classes by right-clicking the MyAddIn project within Solution Explorer in Visual Studio .NET. (Make sure you stop debugging first before proceeding. To do this, on the Debug menu, click Stop Debugging.) Point to Add and click Add New Item. This displays the Add New Item dialog box. In the Template list, select Class. Enter the appropriate class name, for example Init.cs. When you have done that, click Open. Repeat the steps above for:

  • OfficeUI.cs
  • Events.cs
  • Actions.cs

After these are created, your solution should now look as follows:

Aa163619.odc_dnscof28(en-us,office.10).gif

Figure 28. View of the MyAddIn project from Solution Explorer

To continue the example, copy and paste the following two code snippets for the Action and Init classes. The Action class displays our quote, while the Init class is (at the moment) just a placeholder for creating the menu structure.

Action Class

using System;
using System.Windows.Forms;
using System.Diagnostics;

namespace MyAddIn
{
    public class Actions
    {
        public static void MenuItemClicked()
        {
            try
            {
                com.gotdotnet.www.Quote q = 
                    new com.gotdotnet.www.Quote();
                // Replace the " with " marks
                String qotd =
                    q.GetQuote().Trim().Replace(""","\"");

                MessageBox.Show(qotd);
            }
            catch (Exception e)
            {
                Debug.Write(e.ToString());
            }
        }
    }
}

Init Class

This skeleton Init class will allow us to start programming menus and other .NET controls.

using System;
namespace MyAddIn
{
    public class Init
    {
        public static void Start()
        {
            // Create the menu structure
            // Register the menu events

            // Complete
        }
    }
}

Modify the Connect Class

Also, in the StartupComplete method of the Connect class, remove the two lines of code to display the quote and instead insert a line to call the new Init class.

public void OnStartupComplete(ref System.Array custom)
{
    Init.Start();
}

Try to compile, but do not run the add-in at this time. To compile, right-click on Solution 'MyAddIn' (2 projects) and click Rebuild.

**Note   **Make sure the namespace in the code that you are copying or pasting matches the namespace of the project that you created. Otherwise, you may get errors when calling between classes in the project.

Adding Outlook References

Importing the Primary Interop Assemblies

Up until now, we have not really used any functions offered by Outlook through the add-in. This is going to change over the next few sections and to facilitate this we need to add a reference to the required libraries.

When we create the add-in through the wizard, we have the Office assembly loaded (which provides us with access to Office features that are common across all the applications), but nothing specific to the Outlook application.

To do this, right-click on the MyAddIn project and click Add Reference. Then, click on the COM tab and scroll down to locate the Microsoft Outlook 10.0 Object Library as shown in Figure 29. Click Select, then click OK to return to the project.

Click here for larger image.

Figure 29. Adding a reference to the interop library (click picture for larger image)

In the Solution Explorer, you should see a couple of new references added as shown in Figure 30.

Aa163619.odc_dnscof30(en-us,office.10).gif

Figure 30. All of the references in the add-in

**Note   **To check that you are correctly referencing the Microsoft.Office.Interop.Outlook primary interop assembly (PIA) installed earlier (see the "Prerequisites" portion of the How to Build an Add-in section) in the global assembly cache (GAC), do the following. In the Solution Explorer, select the Outlook reference to display its properties. The Copy Local property should be False and the Path property should point to the Microsoft.Office.Interop.Outlook PIA located in the GAC. On a Microsoft Windows NT machine, the path should look something like C:\WINNT\assembly\GAC\Microsoft.Office.Interop.Outlook\10.0.4504.0__31bf3856ad364e35\Microsoft.Office.Interop.Outlook.dll.

Outlook is the reference to the Outlook primary interop library that we just selected.

Microsoft.Office.Core is a reference to a dependent library that is required by the Outlook library.

Once the Microsoft.Office.Core reference is added, remove the Office reference as this is an older library and will only cause duplicate class errors if left in there. To do this, right-click on Office and click Remove.

When these references are linked as part of the project, you will see that the file name for the assemblies generated are prefixed with "Interop" (for example, Interop.Outlook.dll). This indicates that they are an interop layer to the COM world.

**Note   **After adding and removing these references, it is a good idea to restart Visual Studio .NET to make sure all of the assemblies are being loaded correctly. Remember to save your project first.

The Application Object

As previously shown, when we first create an add-in, we get an empty project and a Connect class. This class contains the methods required for intercepting the startup and shutdown of the Office application.

This class also contains an application object:

private object applicationObject;

In its raw form, this isn't that useful to us (as it is not typed). To change this we are going to change the type to an Outlook specific Application object. To do this, add the following using line at the top of the Connect.cs class:

using Outlook = Microsoft.Office.Interop.Outlook;

This using statement allows us to reference the required library to the Outlook interop assembly that we imported in the previous section. In our example code, we alias the library with the Outlook namespace to avoid ambiguity between methods and properties.

Towards the bottom of the Connect.cs class, change the application object declaration from

private object applicationObject;

to the following:

public static Outlook.Application applicationObject;

You will note that we change this object from private to public. This is so that it can be used by other classes within the add-in. We have also made the object static so that it resides as a single reference for the duration of the add-in.

In addition, as part of the code that gets automatically created by the Add-in wizard, the OnConnection method containshat sets the global application object to the one that is passed through to the add-in

applicationObject = application;

As we have changed the type of the application object, we now need to cast this to be the correct type. To do this, replace this line with the following:

applicationObject = (Outlook.Application)application;

The add-in should now compile and run successfully (although very little will happen as we are not making any calls!). You are now ready to start expanding the add-in and developing a full rich client.

Developing the User Experience

About Explorers

Before we look at the code for building a menu structure, it is a good idea to understand how a concept called explorers work within Office.

We will take Outlook 2002 as an example. When you click the Outlook icon, what typically happens is that the Outlook.exe process gets started on your machine. During this startup, the windows and view that you are used to seeing gets rendered and displayed. We call this view an explorer.

If we were to look at this pictorially, we could imagine something like this:

Aa163619.odc_dnscof31(en-us,office.10).gif

Figure 31. A single process with a single explorer

As expected, we have the single Outlook.exe process and then an explorer which handles my folder list, views and so forth.

But what happens if we click on the Outlook icon again to launch a second copy of Outlook? You may think that we would see this:

Click here for larger image.

Figure 32. Do we get two processes per Outlook application? No (click picture for larger image)

But actually, we don't. Instead, a new explorer is opened within the same Outlook.exe process.

Click here for larger image.

Figure 33. Single process, multiple explorers (click picture for larger image)

This has some interesting consequences for creating and maintaining objects within the explorer. Menus, for instance, are objects that get created at the explorer level.

Click here for larger image.

Figure 34. Menu and explorer relationships (click picture for larger image)

This has three impacts for us:

  1. When we create new menus (and other objects within the explorer), we have to be aware of the current explorer and the context in which we are running.

    **Note   **The current explorer object can be found by calling the ActiveExplorer method from the Application object. This will return a type of Outlook.Explorer which can be used through the creation of objects.

  2. More importantly, we have to think very carefully about the user experience if the user opens a new explorer (that is, they double-click on the Outlook icon again when an instance of Outlook is still running).

  3. We only get one instance of the add-in per Outlook.exe process, so when the user opens a new explorer, a new version of the add-in does not get launched (as it is still running for us).

Fortunately we can trap for the creation of a new explorer by the user—this is something that we will design into the code that you see, and something that we will cover in a later section.

Building a Menu Structure in Office

At this point, having a pop-up message once Outlook starts is great for testing, but it does not really lend itself to an intuitive user experience. Ideally, when the user wants a quote for the day, they will want to select the options themselves from a menu option.

We will look into the steps required for building such a menu and then calling our Quote service. Here is the code for the OfficeUI class to create and delete a test menu. Copy and paste this code over the OfficeUI.cs class (you can copy and paste over any code that was automatically generated by creating the class—but again, remember to change the namespace if you did not use MyAddIn).

using System;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace MyAddIn
{
    public class OfficeUI
    {
        public static CommandBarPopup Menu1;
        public static CommandBarButton MenuItem1;

        public static String _menu1Text = "Test Menu";
        public static String _menuItem1Text = "Get Quote of the Day";

        public static void CreateMenu(Outlook.Explorer currentExplorer)
        {
            try
            {
                // First try and delete the menu
                DeleteMenu(currentExplorer);

                // Create the menu root
                Menu1 =
                    (CommandBarPopup)currentExplorer.CommandBars
                    ["Menu Bar"].Controls.Add
                    (MsoControlType.msoControlPopup,Missing.Value,
                    Missing.Value,Missing.Value,Missing.Value);
                    Menu1.Caption = _menu1Text;

                // Create the first menu item
                MenuItem1 = (CommandBarButton)Menu1.Controls.Add(
                    MsoControlType.msoControlButton,
                    Missing.Value,Missing.Value,Missing.Value,
                    Missing.Value);
                MenuItem1.Caption = _menuItem1Text;
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
        }

        public static void DeleteMenu(Outlook.Explorer currentExplorer)
        {
            try
            {
                currentExplorer.CommandBars["Menu Bar"].
                    Controls[_menu1Text].Delete(Missing.Value);
            }
            catch (Exception)
            {
                // Menu did not exist
                Debug.WriteLine("Could not delete. " + 
                    "Assuming Menu does not exist");
            }
        }
    }
}

We will go through the code step-by-step to see what is happening. The required references for this piece of code are:

using System;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;

In terms of namespaces, System is inserted by default. System.Diagnostics is used to write to the debug window in Visual Studio .NET if an exception occurs. System.Reflection is used for values called across the COM interop layer. Microsoft.Office.Core is the main Office object model.

We also have a number of global variables defined that are worth taking a look at.

Two items, Menu1 and MenuItem1 are both of types derived from the Office library (CommandBarPopup and CommandBarButton).

The other two global declarations are for the menu text that will be used (making them global makes it easy to change the menu items at a later date without altering the structure).

public static CommandBarPopup Menu1;
public static CommandBarButton MenuItem1;
public static String _menu1Text = "Test Menu";
public static String _menuItem1Text = "Get Quote of the Day";

We then have two methods in this class, CreateMenu and DeleteMenu. CreateMenu actually calls DeleteMenu before it runs any code. This is to avoid multiple menus being displayed if the menu does not get deleted automatically on shutdown.

To create the root of the menu we make a call to the CommandBars["Menu Bar"].Controls.Add(...) method. Menu Bar is a fixed menu in Office and can be verified in any office application by customizing the toolbars.

public static void CreateMenu(Outlook.Explorer currentExplorer)
{
    try
    {
        // First try and delete the menu
        DeleteMenu(currentExplorer);

        // Create the menu root
        Menu1 = 
            (CommandBarPopup)currentExplorer.CommandBars
            ["Menu Bar"].Controls.Add
            (MsoControlType.msoControlPopup,Missing.Value,
            Missing.Value,Missing.Value,Missing.Value);
            Menu1.Caption = _menu1Text;

        // Create the first menu item
        MenuItem1 = (CommandBarButton)Menu1.Controls.Add(
            MsoControlType.msoControlButton,
            Missing.Value,Missing.Value,Missing.Value,
            Missing.Value);
        MenuItem1.Caption = _menuItem1Text;
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.ToString());
    }
}

You may have noticed that we are using the Missing.Value parameter on a number of calls to the Office object model. This is a very useful parameter from System.Reflection namespace and allows us to call methods which have optional parameters in the COM world. (COM does not support overloading through the interop layer, so we have to supply a Missing.Value parameter to specify that we are not going to be sending anything across.)

The DeleteMenu method simply tries to delete the menu—if it cannot (that is, the menu does not exist—this could be the first time that the add-in has been run) then a message is written to debug, but otherwise, we ignore the error.

public static void DeleteMenu(Outlook.Explorer currentExplorer)
{
    try
    {
       currentExplorer.CommandBars["Menu Bar"].
           Controls[_menu1Text].Delete(Missing.Value);
    }
    catch (Exception)
    {
        // Menu did not exist
        Debug.WriteLine("Could not delete. " + 
            "Assuming Menu does not exist");
    }
}

Now that we have that code written all we have to do is to change the Init class to call our new Menu method.

using System;
namespace MyAddIn
{
    public class Init
    {
        public static void Start()
        {
            // Create the menu structure
            OfficeUI.CreateMenu
                (Connect.applicationObject.ActiveExplorer());

            // Register the menu events

            // Complete
        }
    }
}

Make this change, compile and run the add-in to see whether our menu is being created. You can do this by pressing F5 in Visual Studio .NET. If everything is working, this should compile correctly and the following should be displayed:

Click here for larger image.

Figure 35. The new Test Menu in Outlook (click picture for larger image)

Everything looks good, but of course you will notice that if you click on the menu nothing will happen. This is because we have no events registered with the men yet.

Registering Menu Click Events

This is a small piece of code to register the events for the menu (that is, what happens when a user clicks on the menu item). Copy and paste this code into the Events.cs class file.

using System;
using System.Diagnostics;
using Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace MyAddIn
{
    public class Events
    {
        public static void 
                RegisterMenuEvents(Outlook.Explorer currentExplorer)
        {
            try
            {
                // Find the current menu
                OfficeUI.Menu1 = (CommandBarPopup)currentExplorer.
                    CommandBars["Menu Bar"].
                    Controls[OfficeUI._menu1Text];

                // Find the first menu item
                OfficeUI.MenuItem1 =
                    (CommandBarButton)OfficeUI.Menu1.Controls
                    [OfficeUI._menuItem1Text];
                OfficeUI.MenuItem1.Click += 
                    new _CommandBarButtonEvents_ClickEventHandler
                    (Actions.MenuItemClicked);
            }
            catch (Exception e)
            {
                Debug.Write(e.ToString());
            }
        }
    }
}

Again, we are using System.Diagnostics for debugging and the Microsoft.Office.Core library to access the office menu controls in code.

The RegisterMenuEvents method that we have does three tasks. It firsts locates the Test Menu by indexing the CommandBars["Menu Bar"] object. Once we have a reference to the Test Menu, we find the control that matches the text of the menu item we are trying to locate. This gives us the exact CommandBarButton reference for the menu item that we need to work with.

Once we have this, we register the ClickEventHandler, and set the target to the MenuItemClicked method of the Actions class, which we created earlier.

We now need to add the call in the Init class to register the events. Add the Events.RegisterMenuEvents line as shown in the following code:

using System;
namespace MyAddIn
{
    public class Init
    {
        public static void Start()
        {
            // Create the menu structure
            OfficeUI.CreateMenu
                (Connect.applicationObject.ActiveExplorer());

            // Register the menu events
            Events.RegisterMenuEvents                (Connect.applicationObject.ActiveExplorer());

            // Complete
        }
    }
}

And finally, make a single change to the Actions class by specifying the right parameters. The target method for the ClickEventHandler requires that we have a method that accepts a reference for the button that was clicked and a bool value.

The reference to the object is useful as it allows us to make changes (for example, grey out the button if this is a one-time operation). The bool value allows us to send a continue or don't continue message back to Outlook. For a custom menu operation this has no impact, but for other Outlook objects where Outlook may make its own changes after our method, this is a good way to cancel the operation on behalf of the user.

using System;
using System.Windows.Forms;
using System.Diagnostics;
using Microsoft.Office.Core;
namespace MyAddIn
{
    public class Actions
    {
        public static void MenuItemClicked
                (CommandBarButton cmb, ref bool action)
        {
            try
            {
                com.gotdotnet.www.Quote q = 
                    new com.gotdotnet.www.Quote();

                // Replace the " with " marks
                String qotd =
                    q.GetQuote().Trim().Replace(""","\"");

                MessageBox.Show(qotd);

            }
            catch (Exception e)
            {
                Debug.Write(e.ToString());
            }
        }
    }
}

Copy and paste the above code and then compile. If everything works correctly, the call to the XML Web service should be made when the get quote item from the menu is selected from the Test Menu.

**Note   **There may be a slight delay between clicking on the menu item and retrieving the value from the XML Web service. This is normal as obtaining the value from the external XML Web service usually will take some time.

Code Patterns

At this point you may be looking at the code and thinking a couple of things:

  1. Why have we split the creation of the menu with the registration of the menu events? Surely we could register the menu events at the same time we created the menu?
  2. Why are we using static declarations everywhere?

These are both great points, and ones that we will address now:

Why have we split the creation of the menu with the registration of the menu events?

This really comes down to what was described in a previous section about the new explorers being launched. Do you remember this diagram?

Click here for larger image.

Figure 36. Multiple menu to explorer mapping (click picture for larger image)

At this point, imagine we have only the one explorer open. Our add-in has run successfully and we have created a menu, and we have registered the events. We can show it pictorially like this:

Aa163619.odc_dnscof37(en-us,office.10).gif

Figure 37. Single menu registered

This works, and everyone is happy. The user then double-clicks on the Outlook icon again. This launches a new explorer in the same Outlook.exe process. Take a look at what happens to our menu.

Click here for larger image.

Figure 38. Menu registrations with second open explorer (click picture for larger image)

The menu already exists (as it was created by the add-in) and it will appear in the explorer (the menu only gets deleted when we delete it in code, or when the user resets the menu bar in Office).

The problem is that although the menu is there, there are no events registered against it. The end result for the users is that they see a menu, but when they click on any of the options, nothing happens.

Give it a go with the example we have built earlier. Open up a new instance of Outlook when the add-in is running (you can do this by clicking Start, then Run and type Outlook.exe). You should see that the Test Menu appears, but nothing happens when you click on the button.

As mentioned earlier, it is possible to trap the creation of a new explorer. This is done with the following event handler:

Outlook.ExplorersEvents_NewExplorerEventHandler(...)

In our Connect.cs class we can create an event handler that traps a new instance of the explorer being created and calls a method with the following code:

First, create a new declaration (after the application object declaration public static Outlook.Application applicationObject;):

public static Outlook.Explorers Exps;

Then, in the OnConnection method, add the following two lines of code at the end of the method:

Exps = applicationObject.Explorers;
Exps.NewExplorer += 
    new Outlook.ExplorersEvents_NewExplorerEventHandler
    (NewExplorerMethod);

At this point, given the current state when the new explorer is launched, what do we need to do in the NewExplorer method?

Click here for larger image.

Figure 39. Deciding what to do with the second explorer (click picture for larger image)

We don't need to create a new menu as one already exists, but we do need to reregister the events for the new explorer.

Because we have split out the creation of the menu with the registration of the events, this becomes very easy for us. We can simply call the method to register the events from the method that gets called when the new explorer is launched. Add the following new method in the Connect.cs class:

private void NewExplorerMethod(Outlook.Explorer newExplorer)
{
    newExplorer.Activate();
    Events.RegisterMenuEvents(newExplorer);
}

(The Activate method ensures that the new window is fully loaded and visible before we start adding menu events.)

You will also notice that we need to pass the instance of the new explorer to the RegisterMenuEvents method. This is the primary reason that I chose to have an Outlook.Explorer parameter on the RegisterMenuEvents method (and other methods in the example).

After this code is run, the new instance of the explorer should look like the previous.

Click here for larger image.

Figure 40. Correctly registered menu in second explorer (click picture for larger image)

Compile and run this code, try opening a new instance of the explorer (and more if you wish). You should now see that when the new explorer opens you are still able to launch the GetQuote function.

Why are we using static declarations everywhere?

This really is a two-part answer.

The first reason is a personal preference. The process and execution of the add-in and the interaction with the user is typically a single task operation. For example, the user clicks on a menu item—we then follow a course of action at the end of which the user is returned to Outlook. There are very few cases where you can create a multitasked operation in Outlook.

Secondly, when building the add-in, it is always a very good idea to declare any object that is going to be around for the lifetime of the add-in as static.

A great example (and one that stumped me for some time) of this is with menu items. If menu items and events are declared non-static (that is, local to the methods in which they were created) they can still be created to still work. However, when garbage collection (the freeing up of objects that are no longer in use, but have not been correctly disposed) runs, it has a tendency to collect these objects—some of which we probably did not really wanted collected.

Confused? We will now take a look at what the user experiences.

The add-in creates a menu and registers an event. The menu object is a non-static object and local to the method in which it was created.

The user clicks on the menu item and a function runs. The main job of this function is to call an XML Web service, do some calculations, and return to Outlook. To do this, the add-in is going to require some memory. At this point, the garbage collector collects our menu and menu event (it does not have the ability to physically remove the menu from the explorer, but it does have the ability to unregister the event). The function executes and the user is returned to Outlook.

The user tries to click on the menu item again to rerun the function. Nothing happens. There is no event registered to this menu any more, and there will not be one until the add-in restarts.

Creating and Working with Office Objects

Put the Quote of the Day in Today's Calendar

One of the benefits of working with the Office and (in this case) Outlook object model is that we have very fine access to the objects presented within Office.

As a developer of this example add-in, we will say you decided that once you get a quote back from the XML Web service, you would like to store it in the Outlook Calendar.

To do this, you need to write a new class called CalendarItems.cs. Create this new class in your example project by right-clicking the MyAddIn project within Solution Explorer in Visual Studio .NET. (You will have to exit debugging first before proceeding. In the Debug menu, click Stop Debugging.) Point to Add and click Add New Item. This displays the Add New Item dialog box. In the Template list, select Class. Enter the class name as CalendarItems.cs. When you have done that, click Open.

Then copy and paste the following code:

using System;
using System.Reflection;
using System.Diagnostics;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace MyAddIn
{
    public class CalendarItems
    {
        public static void CreateAllDayEvent
            (String Subject, String Body)
        {
            try
            {
                // Set the namespace from 
                // the existing Application Object
                Outlook.NameSpace ns =
                    Connect.applicationObject.GetNamespace("MAPI");

                // Get the default calendar folder
                Outlook.MAPIFolder calendarFolder =
                    ns.GetDefaultFolder
                    (Outlook.OlDefaultFolders.olFolderCalendar);

                // Create a new calendar item
                Outlook.AppointmentItem newItem =
                    (Outlook.AppointmentItem)calendarFolder.Items.Add
                    (Outlook.OlItemType.olAppointmentItem);

                // Set the properties of the item
                newItem.Start = System.DateTime.Now;
                newItem.AllDayEvent = true;
                newItem.Subject = Subject;
                newItem.Body = Body;

                // Save the item
                newItem.Save();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
        }
    }
}

As can be deduced, this code first locates the user's default Calendar Folder. Within this folder, we create a new appointment item, set all of the properties of the item (including the subject and body fields which will be passed to us as parameters) and saves the item.

To be able to call this, we need to modify the Actions.cs class to call the calendar method when the quote is returned.

Insert the following line just after the point where the message box is being displayed:

CalendarItems.CreateAllDayEvent(qotd,"");

When the add-in is run, and a quote is obtained, the following all day calendar event is created in the default calendar folder of the user:

Click here for larger image.

Figure 41. Automatically generated calendar event (click picture for larger image)

This small piece of code shows a relatively simple example of creating a new Outlook item and setting some simple properties. The complete Office object model offers very granular access to many object types and items. It was never the intention of this paper to cover the entire object model. There are good programming books on Outlook available that you can go to for more information. As with most of the current Outlook programming books available today, many of the examples are in VBA or VBScript—these will require converting to the appropriate calls in C#.

Performance Considerations When Working with Office Objects

Although programming against the Outlook and Office model is fairly transparent to the developer, there is still a COM interop layer that all calls need to go through from .NET to Office. The overhead that these calls add is minimal if only one or two calls are made, but can add up to some significant performance overhead if used incorrectly.

The recommendation behind this is to minimize the number of calls across the COM interop layer where possible. If some serious processing on an object is required, convert the Office objects into .NET data types, do the processing, and write any changes back. Dealing with objects of .NET data types within the add-in will be much quicker than stepping across the COM layer to read and write individual properties for each object.

Displaying and Using the Office Assistant

The following example shows how to make calls to the Office assistant:

Add a new class called Assistant.cs. Create this new class in your example project by right-clicking the MyAddIn project within Solution Explorer in Visual Studio .NET. (You will have to exit debugging first before proceeding. In the Debug menu, click Stop Debugging.) Point to Add and click Add New Item. This displays the Add New Item dialog box. In the Template list, select Class. Enter the class name as Assistant.cs. When you have done that, click Open.

Then copy and paste the following code:

using System;
using System.Reflection;
using Microsoft.Office.Core;
namespace MyAddIn
{
    public class Assistant
    {
        public static void DisplayMessage
            (String caption, String message)
        {
            // Turns on the assistant
            Connect.applicationObject.Assistant.Visible = true;
            Connect.applicationObject.Assistant.On = true;
            Connect.applicationObject.Assistant.AssistWithAlerts =
                true;
            Connect.applicationObject.Assistant.AssistWithHelp = true;

            // Displays the message
            Connect.applicationObject.Assistant.DoAlert
                (caption,message,MsoAlertButtonType.msoAlertButtonOK,
                MsoAlertIconType.msoAlertIconInfo,
                MsoAlertDefaultType.msoAlertDefaultFirst,
                MsoAlertCancelType.msoAlertCancelFirst,false);
        }
    }
}

We want to call the Office Assistant when we choose the GetQuote option from the Test Menu. To do this, in Actions.cs, change the following line:

MessageBox.Show(qotd);

to:

Assistant.DisplayMessage("Quote of the Day",qotd);

Compile and run the add-in.

Aa163619.odc_dnscof42(en-us,office.10).gif

Figure 42. Our old friend, Clippy

The Assistant also allows prompting for questions (yes or no responses). It is possible to make the calls in order to detect whether the Assistant is activated or not. This could be more appropriate than just turning on the Assistant as we have in our code.

Supporting Online and Offline Modes (for Office Applications that Support Online and Offline Functionality)

One of the valuable features of the add-in (and a prerequisite for building a smart client) is the ability to detect whether Outlook is running in online or offline mode. (Online means that Outlook is currently connected to the Microsoft Exchange Server; offline means that we are viewing mail from an .OST folder).

A property of the main Application object is a Session Object. This particular object holds information about the current session—one of the items being whether Outlook is running in online or offline mode.

To call this from code in our example, we can make a call to:

Connect.applicationObject.Session.Offline

This property returns a Boolean value (True if we are offline, False otherwise).

To integrate this functionality into our Quote of the Day example, simply copy and paste the following code over the existing Actions.cs class:

using System;
using System.Windows.Forms;
using System.Diagnostics;
using Microsoft.Office.Core;
namespace MyAddIn
{
    public class Actions
    {
        public static void MenuItemClicked
                (CommandBarButton cmb, ref bool action)
        {
            try
            {
                // First detect whether Outlook is running 
                // Online or Offline
                if (Connect.applicationObject.Session.Offline)
                {
                    Assistant.DisplayMessage
                        ("Quote of the Day",
                        "I cannot retrieve the quote as " + 
                        "you are currently running in Offline mode.");
                }
                else
                {
                    com.gotdotnet.www.Quote q = 
                        new com.gotdotnet.www.Quote();

                    // Replace the " with " marks
                    String qotd = 
                        q.GetQuote().Trim().Replace(""","\"");

                    Assistant.DisplayMessage
                        ("Quote of the Day",qotd);

                    CalendarItems.CreateAllDayEvent(qotd,"");
                }
            }
            catch (Exception e)
            {
                Debug.Write(e.ToString());
            }
        }
    }
}

This will change the message to a "You are Offline" message if it detects that Outlook is running in offline mode. A further extension of the above code could also be to put a message in the catch block to display a default message saying that the XML Web service could not be reached.

Test this functionality by launching the add-in as normal after rebuilding it, selecting the Work Offline option from the File menu, restarting Outlook, selecting offline mode, and retrieving a quote.

Aa163619.odc_dnscof43(en-us,office.10).gif

Figure 43. Displaying offline status in Outlook

On a serious note, this can be very useful for developing smart client applications. This functionality immediately gives the developer a way of finding out whether Outlook is connected to the Exchange Server (and presumably the rest of the world). From there, the add-in can decide which functionality to support or not support based on this mode.

Displaying Windows Forms in Office

Displaying Windows Forms in Office is probably one of the most powerful features of writing an add-in as it allows you to create fully functional applications that could potentially be developed stand-alone from Office and then allows you to import them and use them as Office add-ins.

Here is an example with a few recommendations. First, create a Dialog folder in the project structure as shown in Figure 44. This is purely a personal preference, but I find that it works well to keep the dialogs separate from the main code of the add-in.

Do this by right-clicking the MyAddIn project within Solution Explorer in Visual Studio .NET. (You will have to exit debugging first before proceeding. In the Debug menu, click Stop Debugging.) Point to Add and click New Folder.

Aa163619.odc_dnscof44(en-us,office.10).gif

Figure 44. Recommended Dialog folder for Windows Forms

In the Dialog folder, create a new Windows Form. This Windows Form is going to ask the user whether they want to save this quote in today's calendar, and will also let them modify certain elements of the calendar control as well.

The Windows Form shown in Figure 45 is created using Visual Studio .NET. The code for this Windows Form will not be shown in this article as this would greatly increase the length of the article. Instead, we will leave it up to you as a developer to create a similar simple form.

Aa163619.odc_dnscof45(en-us,office.10).gif

Figure 45. Sample Windows Form

The form works by displaying the quote when it loads (the quote label component is made public, and the value set just before the form is displayed).

The user has the option of appending some additional text, and then either e-mailing it to another user, saving the quote as today's all day event in their calendar, or just ignoring the whole thing and closing the form.

It is recommended when dealing with forms in the add-in is to create a thin wrapper between all of your forms and the add-in code itself. This wrapper is called the DialogManager in this example. Here is the code for the sample DialogManager, using the above form.

using System;
namespace MyAddIn.Dialogs
{
    public class DialogManager
    {
        public DialogManager()
        {
        }

        public void QotdSaveDialog(String passedQuote)
        {
             QotdSaveDialog qsd = new QotdSaveDialog();
             qsd.label2.Text = passedQuote;
             qsd.Show();
        }
    }
}

As you can see, all the DialogManager does is to abstract the creation and display of each form. The main advantage about this is that this class can be used as an interface for calls outside of .NET.

For example, you could have an add-in for Outlook that searches a series of contacts. This add-in may have its own set of Windows Forms to do this. It is possible that the add-in may not be launched from a menu, but instead, from a button on a regular Outlook form (for example from VBScript on a "New Contact" form). If this is the case, you are going to have to build a native to managed interface (to go from VBScript to .NET through COM interop). If all of your dialogs are abstracted through one class, this is going to be an easy, one time process instead of thinking about interface considerations each time you create a new form.

To get this form displayed, in the Actions.cs class file, simply comment out the reference to the Office Assistant and instead call the form through the dialog manager:

//Assistant.DisplayMessage("Quote of the Day",qotd);
Dialogs.DialogManager dm = new Dialogs.DialogManager();
dm.QotdSaveDialog(qotd);

When the add-in is compiled and run, the form is displayed (instead of the Office Assistant).

Handling Exceptions

When coding in .NET, getting unhandled exceptions is generally undesirable; getting unhandled exceptions in add-ins is even more undesirable.

There is one major problem that we want to draw your attention to when developing add-ins using the concepts described in this paper. Any unhandled exceptions that occur in your add-in at runtime will likely a) not get reported in Outlook and b) generally cause the add-in to be unloaded, rendering it useless.

This is undeirable, as it means that even the most innocent NullReferenceException that does not get trapped by your code could result in your user being stranded with an add-in that is no longer working (until they next restart Outlook). For the user, this will make menu items generally unresponsive, and any functionality or interfaces that the add-in provides will not work.

One way to overcome this (and something we tried to demonstrate in all of the code samples that have written for this document) is to use the trycatch blocks for every piece of code. On some one line methods, this may sound like overkill, but it is a good habit to start from the beginning to avoid these problems.

Conclusion

Going back to our initial three points that described a smart client—can these tasks now be accomplished in Office?

  • Leverage local processing power:
    We are certainly doing that. By running all of the code on the client (within Office), we have the ability to leverage the power of the machine.
  • Consume XML Web services:
    Our first example showed how easy it was to consume an XML Web service and display the results in a format within Office.
  • Support online and offline scenarios:
    Outlook has great support for running and supporting both online and offline operations. This includes the ability to detect the state, and of course, store objects (cache data) locally to the application if running offline.

As pointed out in the introduction section, to securely install and deploy managed COM add-ins, you will need to incorporate into your managed COM add-in project a small unmanaged proxy called a shim. For details, see the following articles:

To conclude, I hope that this article has introduced you to some of the options for creating .NET smart client applications in Office XP. With both the flexibility of calling any .NET class, project, or assembly, and access to an extensive Office object model, this can be a very powerful development environment to create compelling applications using .NET.