text:
Developing .NET Smart Clients for Microsoft Office XPThis 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)
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
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:
- Secure Deployment of Managed COM Add-Ins in Office XP
- Deployment of Managed COM Add-Ins in Office XP
- Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP
- Using the Smart Tag Shim Solution to Deploy Managed COM Add-ins in Office XP
- Randy Byrne's Using the COM Add-in Shim to Trust Outlook Add-ins Built with Visual Studio .NET article is also a must read on how to use a shim in Outlook 2002.
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:
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:
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
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:
- Click Start, point to Programs, point to Microsoft Visual Studio .NET, and click Microsoft Visual Studio .NET.
- On the File menu, point to New, and click Project. The New Project dialog box appears.
- In the Project Types pane, double-click the Other Projects folder, and click Extensibility Projects.
- In the Templates pane, click Shared Add-In.
- 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.
Figure 3. New Project selection dialog box (click picture for larger image)
- 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.
Figure 4. The Extensibility Wizard (click picture for larger image)
- 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.
- Select Create an Add-in using Visual C# as shown in Figure 5, then click Next. The Select An Application Host page appears.
Figure 5. Selecting the programming language for the add-in (click picture for larger image)
- 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.
Figure 6. Selecting the host application for the add-in (click picture for larger image)
- Click Next. The Enter a Name and Description page appears.
- 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.
- In the What is the description of your Add-In box, type the description for the add-in.
Figure 7. Naming the add-in (click picture for larger image)
- 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.
- 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.
Figure 8. Choosing add-in options (click picture for larger image)
- Click Next. The Summary page appears, as shown in Figure 9.
- Click Finish, and wait while the Shared Add-In project is built.
Next, we will investigate what has been created.
Figure 9. Completing the add-in (click picture for larger image)
- Closing the wizard will generate two projects under one solution file, and if everything went well, it should look as shown in Figure 10.
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.
- In the Solution Explorer, first click the My AddIn project.
- In the Project menu, click Add Reference. This displays the Add Reference dialog box.
- Select the System.Windows.Forms.dll reference as shown in Figure 11.
- Click Select, then OK.
This is required to display the message dialogs and Windows Forms within the application.
Figure 11. Adding a reference (click picture for larger image)
- Next, insert the following using statement at the top of the Connect.cs class:
using System.Windows.Forms;
- Locate the OnStartupComplete method within the code that has been generated. Within this method, insert the following line:
- 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:
- In Solution Explorer, right-click on the MyAddIn project and click Properties.
Figure 12. Setting the project properties (click picture for larger image)
- In the properties window, select Configuration Properties and click Debugging as shown in Figure 12.
- 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
- 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
- Click OK, and you should be in a good position to test the sample add-in.
- 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.
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.)
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.
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:
- Close both Outlook 2002 and Visual Studio .NET. Remember to save your project as we will be using it for later examples.
- 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.
- Launch Visual Studio .NET, but do not open a project at this time. Instead, in the Tools menu, click Debug Processes.
- Scroll down to select the Outlook process (Outlook.exe) and click Attach as shown in Figure 16.
Figure 16. Debugging processes (click picture for larger image).
- In the Attach to Process dialog box, select the Common Language Runtime and Native options to debug. Click OK.
- 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.
- 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.
Figure 17. Adding a Web Reference (click picture for larger image)
- 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.
Figure 18. Searching UDDI (click picture for larger image)
- In the Service name field box, type QUOTE and click Search. A list of matching services will be displayed:
Figure 19. Search results from UDDI (click picture for larger image)
- Expand the Quote of the Day service under the GOTDOTNET header by clicking on the + symbol.
Figure 20. Selecting the Quote of the Day Service from UDDI (click picture for larger image)
- 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.
- The contract for this XML Web service should then be loaded, and the Add Reference button should become enabled as shown in Figure 21.
Figure 21. The WSDL contract for the Quote of the Day XML Web service (click picture for larger image)
- Click Add Reference to add the reference to this XML Web service and return to the Visual Studio .NET integrated development environment (IDE).
- Under the project Web References, you should now see the following XML Web service:
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.
- Replace the code we wrote earlier:
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.
- 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.
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.
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.
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
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.
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:
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.
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.
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:
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
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:
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:
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.
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.
Figure 34. Menu and explorer relationships (click picture for larger image)
This has three impacts for us:
- 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.
- 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).
- 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:
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:
- 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?
- 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?
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:
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.
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:
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?
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.
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:
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:
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:
Assistant.DisplayMessage("Quote of the Day",qotd);
Compile and run the add-in.
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:
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.
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.
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.
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 try…catch 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.
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:
- Secure Deployment of Managed COM Add-Ins in Office XP
- Deployment of Managed COM Add-Ins in Office XP
- Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP
- Using the Smart Tag Shim Solution to Deploy Managed COM Add-ins in Office XP
- Using the COM Add-in Shim to Trust Outlook Add-ins Built with Visual Studio .NET is a must read on how to use a shim in Outlook 2002.
- Building Outlook 2002 Add-ins with Visual Basic .NET
- The Knowledge Base article PRB: Visual Studio .NET Shared Add-in Is Not Displayed in Office COM Add-ins Dialog Box.
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.


text:
President Obama may finally be getting the message: Don't treat terrorists like common criminals, to be tried in a civilian court — particularly if that court is in New York.
First, Obama's staff began suggesting that 9/11 mastermind Khalid Sheik Mohammed and his fellow butchers wouldn't be tried in New York City, as had been planned.
It's terrific news.
Then there came a hint that they may consider trying the thugs in a military court at Guantanamo Bay.
Even more terrific.
Gitmo, after all, is surely the most logical and appropriate place for terrorists.
In any event, though — as we've been saying — under no condition should the trials be in New York City, which already contributed its share, and then some, to the War on Terror . . . on 9/11.
No doubt, the president is beginning to see just how difficult it is to find a suitable venue for a civilian trial — anywhere. What city, after all, would want to take center ring as KSM & Co. turn the trials into a circus — and a juicy jihadi target?
(As The Post's Carl Campanile reports today, an offer by Mayor Nicholas Valentine of upstate Newburgh to host the trials and "promote tourism" drew a, uh, hostile response from local residents.)
The White House may also be starting to glean more of the voters' message from the recent Massachusetts election: They're not only worried about Obama's approach to health care, but also to terror.
Let's face it: Thugs like KSM are at war with America. They need to be held as enemy combatants, not read their Miranda rights.
Meantime, opposition to civilian trials is also swelling in Congress — with some members looking to strip funding for them.
Whatever's motivating White House officials, though, it's certainly good to see them moving in the right direction.
The sooner they take that final step — ending civilian trials for terrorists for good, or at least keeping them out of New York — the better.

subset: commoncrawl


text:
Jinan Aonuo CNC Equipment Co.LTD
wood cnc router, woodworking machine, woodworking cnc router, woodworking engraver, wood engraver machine, Manufacturer and Supplier - China
SG-1215s Stone Engraver Machine (SG-1215s)
Standard features:
1. It adopts Taiwan linear guide rail. The bearing of the linear guide rail is face to face, which can guarantee high precision for long time use.
2. It is equipped with high-precision ball screws, which can produce the movement to minimize the gap automatically when it is working.
3. High speed driver with high speed and high power.
4. Dustproof and oiling structure can effectively guarantee the long-tem running of the mechanical aspect.
5. Continuous engraving function for broken point and broken knife. You can engrave anywhere you like.
6. High compatibility: all kinds of CAD/ CAM software such as Type3/ Artcarm/ CASTmate/ Proe/ Coreldraw/ Artcut.
7. High power frequency changing water-cooling spindle, it can process hard materials efficiently
X Y working area: 1200*1500mm
Z working area: 200mm
Table size: 1540*2070mm
Max. Speed: 30m/min
Max. Working speed: 12m/min
Working delicacy: 0.005mm
Repeat Repositioning Accuracy: 0.05mm
Command: G code*, u00*, mmg*, plt
Surroundings for software: Windows98/2000/xp
Voltage: AC 380V/50HZ
Spindle power: 2.2KW
Spindle speed: 6000-24000rpm/min
Weight: 420KG


text:
Hip deep in controversy, Mayor Bob Filner has tapped one of the region's most respected bureaucrats to run San Diego's day-to-day operations.
Walt Ekard, who resigned as San Diego County's chief administrative officer after 13 years on Dec. 1, has been appointed the city's interim chief operating officer. Filner had previously hired Ekard on a three-month consulting contract to guide him on good-government practices.
Ekard's appointment was one of several staffing changes made by the embattled Filner since he's been accused of sexual harassment by numerous unidentified women.
The turmoil has led many to question whether Filner can properly run the city.
"Some who have called for my resignation have justified it with the claim that the operations of the city will somehow be compromised by the current controversy," Filner said in a statement. "… Today's announcements make clear the operations of city government will not be negatively impacted."
Ekard has been widely praised for his fiscal discipline while leading the county, using tightfisted budgeting and multiyear spending plans to largely avoid fiscal turmoil gripping governments across the country. Ekard will be making $275,000 annually in his new position with the city.
In an interview, Ekard said he took the job on the condition that city employees would be respected and valued and that he would have the ability to make decisions regarding city operations. Filner is widely described as a micromanager.
"There is a lot of turmoil going on and I think I can help," he said. "… I needed to know that I have the appropriate level of authority to manage day-to-day operations in the city. (Filner) has agreed to that."
Ekard, 59, was appointed the county's chief administrator on May 4, 1999, after rising through the ranks of county government. He began as a policy aide to former Supervisor Paul Fordem in 1981 and four years later signed on as chief of staff to Supervisor Brian Bilbray.
One of Ekard's first moves was to appoint Scott Chadwick as assistant chief operating officer and Judy Von Kalinowski as human resources director. Chadwick had been serving as interim COO while Von Kalinowski temporarily replaced him as human resources director.
Filner also made several other staffing changes, including Tony Buckles as his new chief of staff. Buckles had previously served in that same position in Washington, D.C., for Filner while he was a congressman.


text:
Product Brochures
Textbook Management Solution
TIPWeb-IM is the only software solution for Instructional Material Management to satisfy the procedural needs of both the district and campus levels for complete inventory control. TIPWeb-IM helps ensure communication, accountability, and availability of textbooks, resource kits, and other instructional materials.
Download the TIPWeb-IM District Centralized Product Brochure
Download the TIPWeb-IM Single-Site, Hosted Product Brochure
Asset Management Solution
TIPWeb-IT is an easy to use inventory system designed to track K-12 instructional technology resources. Identify the items at your schools and buildings and which items are distributed to individual rooms. TIPWeb-IT is for curriculum and technology staff who need to track, manage and report items they purchase and supervise.
Download the TIPWeb-IT Product Brochure
Watch the TIPWeb-IT product overview video
RFID Auditing Solution
TIPWeb-RFID is the first RFID (radio-frequency identification) inventory solution specifically designed for K-12 education. Now school districts can take advantage of the streamlined audit process benefits of RFID while also performing the day-to-day tracking of inventory to buildings, departments, rooms, staff and students in a single solution, TIPWeb-IT
Download the TIPWeb-RFID Product Brochure
Watch the TIPWeb-RFID product overview video
Help Desk Solution
GetHelp, our integrated help desk and asset management software, ensures your staff remains as efficient as possible by quickly viewing details about assets, all while resolving ticket requests.


text:
Tom Franklin, author of Sun-Up to Sun-Down: The Lives of Golf Course Owners (Blue River Press, 9781935628262) will appear on That's Golf! on SportsTalk AM 1300 The Zone in Austin, Tex. on Saturday, August 3.
Thomas Lippman, author of Saudi Arabia on the Edge: The Uncertain Future of an American Ally (Potomac Books, 9781597976886) will appear on Fareed Zakaria's world affairs show on CNN Sunday, August 4.
Booksellers can order these titles through Ingram at ipage.
To be included in the Authors on the Air compilation, email information--at least TWO days in advance, please--to email@example.com.


text:
DENVER, Jan. 25 (UPI) -- Denver Nuggets forward Danilo Gallinari has agreed to a four-year contract extension, the team said Wednesday.
Terms of the deal weren't disclosed but The Denver Post reported the 23-year-old Italian will earn close to $42 million over the duration of the deal.
Gallinari inked the deal as Wednesday's NBA deadline approached for negotiating extensions with members of the NBA Draft Class of 2008. Players who do not reach agreement by the deadline can become restricted free agents beginning July 1.
"Gallo has been excellent and done everything we have asked since we made the trade last February," Nuggets Executive Vice President of Basketball Operations Masai Ujiri said. "Everybody is seeing how talented he is, and he's only 23 years old. He is only going to get better."
The signing signals the team is ready to move ahead after last season's trades of forward Carmelo Anthony and guard Chauncey Billups, the Post said.
Gallinari is the Nuggets' leading scorer at 17.4 points per game, shooting a career-best 46.2 percent from the floor this season.


text:
The case of the missing iPhone is a story that can't decide whether it wants to be serious or ridiculous.
In the circle of Apple bloggers, the story about how an Apple engineer lost a valuable prototype of an iPhone that may reveal secrets about the company's future models is serious business. And a police search of a Gizmodo editor's home shows that local authorities in Silicon Valley are not treating it as a laughing matter.
But the comics among us want to turn the affair into a circus. First, Apple cofounder Steve Wozniak created a stir with a gag T-shirt that said "I went drinking with Gray Powell and all I got was a lousy iPhone prototype." (Powell is the Apple engineer who lost the phone.) Then today, Scott Adams lampooned the matter in a cartoon released on his website.
"I found this story too delicious to resist, but I worried that the story would become stale before my comics would work through the pipeline. I think the soonest I can get something published is in about a month, perhaps a bit sooner, but I've never tested it," Adams wrote.
He wrote the comics exclusively for his blog readers, and he noted he didn't need to invent any part of it.
It's a story you just can't make up.
[image credit: Scott Adams]
Don't miss MobileBeat 2010, VentureBeat's conference on the future of mobile. The theme: "The year of the superphone and who will profit." Now expanded to two days, MobileBeat 2010 will take place on July 12-13 at The Palace Hotel in San Francisco. Early-bird pricing is available until May 15. For complete conference details, or to apply for the MobileBeat Startup Competition, click here.


text:
Some relics of the past are best left moldering in the attic. Such a dusty curio is "Loud Speaker," John Howard Lawson's frantic 1927 burlesque of American politics, presented in repertory with "Success Story," another Lawson work, by ReGroup Theatre. Lawson's humor may have been fresh 86 years ago, but the wisecracks about candidates pandering to various ethnic groups and the eagerness of the press for scandal are tired and obvious. Director Nic Davies hits the audience over the head with every line and too many of the scenes don't even make sense.
The trouble starts at the top of the mercifully brief show when Matt Giroveanu, as New York gubernatorial candidate Harry U. Collins, enters wearing a Hasidic fur cap and side curls to address a Jewish group. After some unfunny remarks about his audience of prospective voters, he dons one of those cheap green party hats for St. Patrick's Day and turns to an Irish crowd. Things go downhill from there as we enter the Collins home and encounter blithering wife Emma, nasty alcoholic daughter Clare, tap-dancing butler Josephus, and Emma's nutty spiritual adviser, who doesn't speak English or have a name. On the eve of the election this madhouse is invaded by overeager rookie reporter Johnnie Dunne, whose job depends on him digging up dirt on Collins, and Johnnie's mother, Dorothy, a gossip columnist out to pry secrets from Emma. There's also Floradora, a former fling of Harry's who may or may not be a ghost. This young lady's corporeal substance is just one of the many mysteries that are never cleared up. Even in a zany comedy sketch logical consistency is required, and Lawson's dated script lacks it.
Davies' hyped-up, sloppy staging just adds to the confusion, as do the majority of the performances. Most of the cast mugs shamelessly, trying to be "funny" rather than pursuing their characters' objectives. Giroveanu at least has a foundation of credibility upon which to build a few comic flourishes. Daniel Hainsworth provides snappy commentary and musical accompaniment as a backroom flunkey, but his role is too small for any major impact; ditto for Ave M. Lindon's sharp sob sister Dorothy.
Pairing "Loud Speaker" with "Success Story" makes a certain kind of sense. They offer contrasting interpretations of the corrupt pursuit of the American dream, but both are so antiquated that they fail to rise above the status of historical oddity. For still-effective political satire of this era try listening to a CD of the Pulitzer Prize–winning musical "Of Thee I Sing" or even its obscure sequel, "Let 'Em Eat Cake."
Presented by ReGroup Theatre at the Robert Moss Theater at 440 Studios, 440 Lafayette St., NYC. March 9–23. (212) 868-4444 or www.smarttix.com.
Critic's Score: D


text:
While his little sister Willow was busy working the red carpet at this week's Twilight: Breaking Dawn premiere in LA, 13-year-old Jaden Smith was enjoying some guys' time with his dad Will in Miami, Florida (November 15). The father-son pair were spotted posing for pics with fans as they headed out to grab a bite to eat.
The very talented family all have projects on the go at the moment: Willow and Jaden will be teaming up in the comic-inspired Amulet while their movie star dad is scheduled to soon begin shooting his new drama The Last Pharaoh. Mom Jada Pinkett-Smith, meanwhile, will lend her voice to the animated flick Madagascar 3.
Earlier this year Jada talked about her kids' growing success, saying, "I think the most fulfilling thing is allowing them to fulfill their dreams and seeing them fly. This is probably the most gratifying experience that I could have. Giving them the opportunities that they've had and being by their sides will be the most gratifying thing that I will probably ever do in my life."


text:
New 'Jetsons' Animated Movie In the Works
Posted on Saturday, January 24th, 2015 by Germain Lussier
Everyone's favorite futuristic family, The Jetsons, have been around for a while. They started in the Sixties, had a movie made back in 1990 and, over the past few years, several people – including Kanye West – have been thinking about making another. Well, the needle is moving once again on a new Jetsons movie and, this time, it's a lot more believable. Warner Bros. has hired Matt Lieberman to penning a screenplay to a new animated feature. Read More »


text:
Created for: The Sims 3
This is my Liverpool Home Shirt for the 2012-2013 season.
Sorry it's been a long time since my last creation but I took a much needed holiday and so now I need to catch up on requests.
Here is Liverpool, a team which has had a great history but has been struggling in recent times, finishing 8th in the league last year. However, they won the League Cup which secured them a place in the Europa League Qualifying and with a new manager, could they find the success that they once had?
Included is: Liverpool Home Shirt
Liverpool Shirt with 8 Gerrard printing
Liverpool Shirt with 7 Suarez printing
Liverpool Shirt with 5 Agger printing
*Note: Shirt names and numbers are correct as of 23rd July 2012.
Short URL: http://www.thesimsresource.com/downloads/1159997
ItemID: 1159997
This Creation requires what's listed below in order to work properly.
Note that those downloads are not required for the Creation to work!
The TSR Creations below were used in this Creation.
The EA Creations below were used in this Creation.
Please report any offensive comments so we can remove them. Use of bad language, unsuitable links or flaming may result in deletion of your account.
We have detected that you are using an Ad-blocker plugin. This means our main source of income to cover bandwidth costs is blocked when you are using our free service. We would ask that you whitelist us and allow ads to show.
Anyone using an Ad-blocker plugin will be forced to wait 30 seconds instead of 10 on the "please wait" page.
VIP Membership
For as little as $3.50 per month you can become a VIP member. This lets you use our service free of all ads and unlocks access to our popular Download Basket and Quick Download features.
Not a Member yet?
Click here to Register now


text:
ESSEX-UK-L ArchivesArchiver > ESSEX-UK > 2011-03 > 1300240786
Subject: Re: [Ess] Legal marriage name.
Date: Wed, 16 Mar 2011 01:59:46 +0000 (UTC)
In-Reply-To: <email@example.com>
No Angelika they have not !
----- Original Message -----
From: "Angelika" <>
Sent: Tuesday, March 15, 2011 6:46:37 PM
Subject: Re: [Ess] Legal marriage name.
Just to give another example of how lax things used to be, even as
recently as the late 20th century:
We got married in 1988, and as the wedding was to be in a non-conformist
church we had to do the necessary paperwork at Colchester Register
Office beforehand.
The formalities went something like this:
"Are you both over 18?" - "Yes"
"Are you both single?" - "Yes"
"Are you both British?" - "I am, she isn't"
"Do you have your passport on you, Madam?"
I handed over my passport, he wrote down my name, he gave it back to me.
My husband-to-be did not have to produce ANY kind of ID or documents,
and if I had lied and said I was British they would probably not have
asked me for any documents either.
I'm sure things have changed since then!
Any problems, please contact the List Admin:
To unsubscribe from the list, please send an email to with the word 'unsubscribe' without the quotes in the subject and the body of the message
This thread:


text:
Significant changes are pending in the top division of Portuguese soccer. Its professional league (LPFP) has approved the entry of two additional teams, and its president said it would challenge
the current TV rights bargaining model in European courts.
"The league's council of (club) presidents approved an expansion of the competition to 18 clubs in the 2012-2013 season," the LPFP said in a statement Monday. To do this the two teams that finish at the bottom this season will not be relegated. The decision, which provoked a heated exchange between club delegates, still needs to be ratified by the Portuguese soccer federation and Portugal's National Sports Council.
The LPFP also assigned president Mario Figueiredo the task of preparing a complaint to the European Union about the current TV rights bargaining model that privileges bigger clubs.
"This was an historic day, we managed to gather support for a structural question and an eventual complaint to end the monopoly of TV rights," Figueiredo said He wants rights to league games bargained collectively rather than on a club-by-club basis. Half of the revenues for Portuguese league games currently go to the so-called "big three"; Benfica, Sporting and Porto. Benfica and Porto did not back the proposal for league-wide selling of rights.


text:
This product is no longer available.
You can try these similar gifts instead.
Expressions Cube Picture Frame
The Cube Picture Frame is perfect for displaying pictures of the new baby and will go with any decor. Personalize with a special sentiment with names and dates for a special new baby or baby shower gift.


text:
Where the Puck is Going…
A conversation with Gautam Ramdurai, Insights Lead at Google on randomness, rabbit holes and Justin Bieber
BEING ABLE TO PREDICT WHAT PEOPLE WANT is what helps the Mountain View Monolith be one of the biggest and most innovative companies today.
Helping to make sense of today and tomorrow is Insights Lead, Gautam Ramdurai. Gautam was gracious enough to take a few minutes to talk to GapJumpers about where the comms industry is heading… kinda.. (who the fux, knows.. really)
"Bet on hiring someone who is obviously competent but probably doesn't fit into your ideal candidature"
Q: Gautam, in "hug the random" you mention inherited probability biases. How can the communications industry be more 'random' as to how it looks for talent, instead of preferring (inherited) biases such as the school where you studied (Oxbridge, MICA, VCU), level of education (MBA, PhD) or grade point average.
Gautam Ramdurai: I think institutions in general, not just the communications industry, could use some injection of randomness — especially in hiring. We spend way too much time defining the principles of our companies and the "criteria" for our new recruits.
This drives us deeper into a self-constraining pigeon hole that stops us from seeing and identifying new skills that we didn't even know existed.
It happened when big marketing shops never thought of hiring good programmers in 2004 and is happening again now when the comms industry has a dearth of good data crunchers.
One way to get around this is by willing to accept "wildcard" entries. Bet on hiring someone who is obviously competent but probably doesn't fit into your ideal candidature.
A lot of my friends who loosely fall under the label of "creative technologists" — UX designers, prototypers, HTML5 devs, visualization experts — were hired this way and have lent an entirely new skillset to their respective employers.
Q: Somewhat related to your idea of being prepared to meet randomness and hedging against volatility, Mike Arauz of Undercurrent wrote that, "The typical 'T-shaped' team member is no longer adaptable enough to keep and maintain value in a market that evolves as quickly as today's market does. The ideal evolving skill set for today's digital strategy world is shaped more like an expanding square than a 'T'." Would you agree?
Gautam Ramdurai: 100% agree with Mike. There is a definite place in the world for laser-focused expertise — strategy is not one of them. Richness in thinking comes from diversity of exposure. You don't come up with interesting new connections if you only read about one thing over and over. That was the fundamental flaw of all industries that got disrupted by some new app.
I have a long list of skills I want to pick up, topics I want to learn about, places I want to visit and movies I want to watch. And it's not casually perusing these new topics and skills — but going deep. I love learning languages for the same reason — not to just learn how to say "where is the bathroom" nor to attain native fluency, but enough to have a coherent conversation at a party.
I absolutely love this statement in Satya Nadella's first letter to Microsoft employees:
"Many who know me say I am also defined by my curiosity and thirst for learning. I buy more books than I can finish. I sign up for more online courses than I can complete. I fundamentally believe that if you are not learning new things, you stop doing great and useful things. So family, curiosity and hunger for knowledge all define me."
I read that and grinned wide because that pretty much sums up my life.
Q: What strategy do you employ to make sure that you are focussing on the right rabbit holes for your personal and professional development?
Gautam Ramdurai:
There are no such things as "right rabbit holes".
There are skills that have a more direct connection to your job and life, but mostly I chase things that just seem super interesting. I start with what I'm rabidly curious about vs. what might look good on my resume (although it definitely doesn't hurt to have those two be the same thing.) If you love what you do, you will inherently be curious about things in your own field.
For example, I'm currently curious about what programming language I should pick up next for data analysis because I am dissatisfied with what I am working with.
Gapjumpers: In the article Mike mentions some rabbit holes people should focus on. What rabbit holes are you focusing on?
Gautam Ramdurai:
Modular Origami.
Stephen Hawking's new paper on Black Holes.
Learning basic Spanish.
The economics of fan fiction.
Ongoing social changes in India driven by mobile connectivity.
The concept of entropy and how it unfolds in our lives everyday.
Learning how to sketch comic book characters.
Q: What rabbit holes should students and graduates, looking to up their chances of breaking into the comms industry, focus on? Why?
GAUTAM RAMDURAI: THINK ABOUT 5 THINGS YOU'VE ALWAYS BEEN CURIOUS ABOUT. Doesn't have to do anything with your job or college elective — or maybe it is specifically about that one chapter about Native American history or who coined the word "debugging". Now go on an uncharted Wikipedia/YouTube journey.
Few minutes in you'll find yourself in infinite tab heaven. Now think about how what you just learned applies to your day-to-day — does it help you look at your problems differently? Does it point out new skills you should be learning?
Now the second part of the question is about breaking into the industry — see how you express these new things you just learned about through your unique lens.
Back in grad school, I was very curious about the millions of Justin Bieber followers on twitter. What was driving this craze beyond the obvious fandom?
So I went about exploring as much as I could about the topic and came up with a few hypotheses. These became the most interesting topics to talk about during my job interviews — not the brand-related projects I'd done as part of the curriculum. That stuff is super important, but it's the rabbit holes you choose that rounds of your candidature to be interesting.
Q: With the way that tech, design, comms and product development are merging, what would you advise a 20 year old Gautam , if he asked you where to work and in what location: advertising agency, client, tech startup, something different?
Gautam Ramdurai: At 20, I was studying software engineering, working on my artificial intelligence project but dreaming of becoming the college literary magazine's editor-in-chief and resenting the fact that I didn't go to a journalism school.
The only piece of advice I'd give myself would be to know that life happens when you're busy planning.
That doesn't mean you stop planning. It means you dream, plan and take one step at a time. But it also means that when your path starts veering from what you imagined, you embrace this new vector and know that your goal in life shouldn't be about making "right" choices, but interesting ones.
Thank you Gautam.
"Where the puck is going" is GapJumpers jumping on the content bandwagon ;). We ask people we like and find super interesting to share thoughts, learnings and more. Whenever we find someone willing to answer our questions, we'll feature them. Have someone you want to see featured? Add your comments alongside. If you'd like to stay updated on more stories, follow the collection, obvio...


text:
Dear Writer's Block,
Hello, how have you been? By that I mean you're a jerk and I believe I'd like to kick you in the blocky ding ding.
I think it's time you and I had a chat. After more than a few years of careful deliberation, I'm going to have to let you go. Unfortunately, your days of planting the closing song from "Dirty Dancing" or accidentally doing four crossword puzzles instead of mowing the lawn are officially over.
Ah yes, Mr. Blockhead. Speaking of blockheads … was Charlie Brown ever funny? I understand that Charles Schultz is a cartoon culture icon, but I never really thought "Peanuts" was entertaining. Reading about precocious balding kids with misshapen heads really didn't do much for me in terms of animated amusement.
Anyway, we met somewhere when I was quite young and any type of chore I was responsible for came due. This would usually coincide with a cache of marbles being discovered or perhaps a comic book I thought had long since disappeared would miraculously reappear.
Flash forward to me sitting in front of a computer screen with 19 tabs open on my Internet browser. I think it's a delirious mix of ADD, sloth and procrastination. Speaking of Sloth … that character scared the hell out of me in "Goonies." And Chunk's family just takes in this rampaging monster straight out of a Troma nightmare? I don't think so. I think a fat kid with delusions is enough parenting responsibility.
Back to browsing. Now Google is by far the most dangerous addictive drug for someone with writer's block. It starts off as leisurely stroll among websites to either a) get inspiration for a column, or b) do research for an idea I'd like to write about. The truth is it's actually c) a way for me to play games, buy pointless items online (Pasta Boat anyone?), and catch up on three different websites all the celebrity gossip that's fit to print.
I have a few suggestions about where you can go when you leave here, Writer's Block. I think first and foremost, to that woman who writes the "Twilight" crud. Go there. Now. Stop those books at all costs.
Then maybe it might be a good plan to head over to the political spectrum and do your best to cement the idea that no politician should ever write a book. We don't care. I'm talking to you Alaskan hockey mom. Mr. Writer's Block, it's come to my attention that you need help. You've become a needling parasitic influence that only exists to exacerbate my inability to stay on task and complete a writing project.
I have a responsibility to my employer, my editor, my … hey look! Gummi bears!
Dear Writer's Block,


text:
Ramsey draws some of her influence on this album from the beloved holiday music of The Carpenters. "I'll admit it, I'm a huge Karen Carpenter fan, and something about her voice on their holiday album puts me at peace any time of year. Holiday music is homecoming for me, a return to my roots. I wanted to share my love of this music with the world and do it in a way that invited everyone to enjoy something on the album. You'll hear my favorite songs, my parent's favorite songs, songs to lift your spirit and songs to make you reflect on holidays gone by and holidays to come. Monika was also able to incorporate my love of many different musical styles in the arrangements, so each song is a new experience and a new sound. I hope you enjoy my gift to you this holiday season and share it with your family and friends," said the vocalist.
Ramsey and Herzig are frequent collaborators and the chance to record together was a special event for both ladies. Herzig stated that performing on the album was "a real treat ... for myself, for the musicians, and for everyone who will get to enjoy this.""Peace Like a River" features familiar favorites like "Merry Christmas Darling," "Sleigh Ride" and "I'll be Home for Christmas," while also featuring the Herzig original "Ballad of a Snowman" and a charming duet "This Christmas" with Ramsey's fiancé, Blair Clark, a vocalist that has performed in hundreds of private and public venues throughout Europe and the United States.
1. Merry Christmas Darling
2. Winter Weather
3. Peace Like a River
4. Sleigh Ride
5. Breath of Heaven
6. I'll Be Home for Christmas
7. Ballad of a Snowman
8. River
9. The Christmas Song
10. The Man With the Bag
11. This Christmas (duet with Blair Clark)
More about Heather Ramsey:
The seeds that would begin Heather (Ault) Ramsey's musical career were planted very early in life, mostly being sown as she was surrounded by her musical parents, grandparents and extended family at their small church in northern Indiana. Family members spent hours out–harmonizing one another at family reunions, church gatherings and holiday meals, accompanied by Grandma's sweeping piano flourishes and Grandpa's booming baritone. Heather's knack for harmony, improvisation and vocal styling most certainly grew from these early influences.
Playing piano and flute through high school and early college years brought in a more classical foundation in reading music and understanding theory and musical structure. In her later college years, however, Heather got her first professional performance training from Purdue Musical Organizations, where her directors and peer taught her to engage audiences, improve her vocal technique and love live performance. She was listening to everything from Lauryn Hill to Celine Dion, Janis Joplin to Eva Cassidy, and Ella Fitzgerald to Sarah McLachlan, beginning to develop a simultaneous love for R&B, pop, blues and jazz.
In addition to her regular live appearances, Heather is very active in the education and training of young vocal artists in a variety of modern genres. She also serves as the CEO and co-founder of the Midwest School of Voice and co-founded Isis of Indiana with Dr. Monika Herzig. Isis, serving the surrounding Indianapolis areas, is an organization dedicated to helping women achieve their fullest potential in music.


text:
PORT CHARLOTTE, FL. - Charlotte County Sheriff's undercover Narcotics detectives arrested 2 teens for trafficking Ecstacy in Port Charlotte Monday.
Arrested were 19-year-old Timothy Michael Warnock and a 17-year-old male.
On Feb. 4, detectives first met with the juvenile at an undisclosed location and purchased 7 grams of MDMA for $410. On Monday, detectives were in contact with him again, this time to buy more MDMA or (Ecstacy, Molly, E, X and XTC).
The juvenile asked detectives to meet him in the Wal-Mart parking lot in Murdock and also said he was going to bring along his supplier, Tim Warnock. Roy and Warnock pulled into the parking lot in a "jacked up" Ford Ranger pickup. They got out and got into the undercover detectives vehicle and the exchange was made of 8 grams of MDMA for $410.
Once the deal was completed, other Narcotics detectives moved in and arrested both teens. Detectives located a digital scale, a cellphone, the 8-grams of MDMA and the $410 in marked money.
Both were transported to the Charlotte County Jail. The juvenile was charged with Trafficking in MDMA, Sale and Delivery of MDMA, Possession of MDMA, and two counts of Possession of Drug Paraphernalia. Once booked, he was taken to the Department of Juvenile Justice center in Ft. Myers. Warnock was charged with Trafficking in MDMA, Sale and Delivery of MDMA, Possession of Drug Paraphernalia, and Contributing to the Delinquency of a minor. He remains in jail on $32,000 bond.
Anyone with knowledge of illegal drug activity is asked to please call the CCSO Narcotics Unit at 941-575-2157 or Crime Stoppers at 800-780-TIPS (8477).