| Matt's profileMatt Epstein's spaceBlogGuestbookNetwork | Help |
Matt Epstein's spaceMostly (Unmanaged) Outlook Programming |
|||||
|
November 04 Creating a Form Region Add-in in C++ -- Part 1Since I've done several form region add-in projects in unmanaged C++ and I haven't seen it documented anywhere (yet), I thought I'd take a crack at explaining this topic and offering a simple example project: IntroductionOne of the exciting features in Outlook 2007 is the new Form Regions functionality. What are Form Regions, you say? Well, they are a new, easier way of creating custom UI for Outlook items (e-mail, appointments, tasks, etc.). They are great when you want to add a little bit of custom UI to the existing, default Outlook form instead of having to make an entirely new form – although you can do that too if you really want to. If you want the full story on Form Regions, you should check out the relevant documentation on MSDN: Ryan Gregg’s article on Form Regions is a great primer, but you’ll notice that it’s all in managed code. Which leads us to the subject of this article: I’m going to show you how easy it can be to implement a custom form region solution in unmanaged C++. And so you don’t have to keep jumping back and forth, I’ll include my own detailed how-to steps and lots of screenshots. (But I might forget a detail or two, so keep Ryan’s article handy.) I’m not going to assume too much experience with Office COM add-ins, but I may gloss over a few details for the sake of keeping this article readable. (If I’m leaving out something obvious, please let me know – after doing this for several years, it’s easy to forget what’s strange or mysterious to the uninitiated.) So, for those of you who need a quick primer, here’s the 30-second version of COM add-in architecture: All Office COM add-ins implement the _IDTExtensibility2 interface. It’s the core COM interface that Office applications use to load, initialize and unload Office add-ins. Additionally, you tell an Office app to load your add-in by writing some information to the registry hive under the Office application registry keys. You can actually do this through the Office app’s UI, but most people use an installer or .reg file to manually write the registry info into the hive in the appropriate place. I’ll show you where to put it for Outlook add-ins later in the article. If you were just doing a simple COM add-in that listened for new mail and did some simple processing, you could really stop there and implement your business logic and be done, but we’re going to keep going and implement a Form Region, which means implementing a second COM interface new in Outlook 2007: _FormRegionStartup. I’ll show you how to do that too. Here's what our form region will look like: Creating the ProjectFor this sample project, I am using Visual Studio 2008 Professional, but you don’t need to be on the latest version of VS. We won’t be using any of the new VSTO support – although that stuff is really handy for spinning up quick C# prototype add-ins and is well worth it if you do a lot of Office development. The only real requirement here is ATL, so you probably should be using at least VS 2005 for good ATL support, but you could really go all the way back to VC++ 6.0 if you wanted to be old-school. Beware though: you might have to do some ATL_COM_MAP tweaking if you go back to a really old version of ATL. OK, caveats aside, let’s get started! First, create a new ATL project in Visual Studio: Next, in the ATL Project Wizard, accept the default project settings to create a DLL. You do not need to check any of the additional options for merging proxy/stub code, supporting MFC or COM+. (Since this add-in will be running in-proc, we won’t need to worry about any marshalling.) Implementing _IDTExtensibility2Now we need to create a new class that implements our main add-in class. As I mentioned earlier, the core interface for COM add-ins is _IDTExtensibility2. This interface is actually defined not in Outlook or the shared Office type library (MSO.DLL) but in a separate type library, Microsoft Add-in Designer 1.0 (MSADDNDR.DLL, typically found in the %Program Files%\Common Files\DESIGNER folder). Add a new ATL Simple Object to the project using the Add->New Class menu item. In the ATL Simple Object Wizard, give it a nice friendly class name (I'm using FRAddin as the short name) and hit Next. On the Options page, check the box to add stubs for the ISupportErrorInfo interface. Doing this allows your add-in to play nice and return good error information to the caller via COM. Click Finish and the ATL wizard will generate the code for a simple coclass. Now we need to implement the _IDTExtensibility2 interface, so switch to the Class View, right-click the CFRAddin class (assuming you’ve copied the class name I used) and click Add->Implement Interface. In the Implement Interface Wizard, select “Microsoft Add-in Designer <1.0>” as the type library, use the right-arrow button to select the _IDTExtensibility2 interface and click Finish. If you go look at your FRAddin.h file, you’ll see something like this: // _IDTExtensibility2 Methods These methods are the way that Office apps communicate with COM add-ins. Most of the magic happens in OnConnection: you’ll get an IDispatch pointer to the Application object that represents the Office application in its respective object model – in our case, a reference to the Outlook Application object. From that object, you can get to pretty much everything else you need in the object model (OM) – except to the form region. (We’ll deal with that later.) First, change the HRESULTs from E_NOTIMPL to S_OK so all the methods return immediately with a success code. Second, open the.rgs file (aka the Registrar script) for your add-in class, i.e., the one that implements _IDTExtensibility2) – in my project, the file is named FRAddin.rgs. We’re going to add our add-in registration data here so subsequent calls to regsvr32 will automatically write those values when adding the COM registration to the registry. (When you build the project in Visual Studio, it will include a post-build step to call regsvr32 by default.) Assuming you’re following my naming scheme, you can simply append this to file just under the existing registry code: HKCU All you’re really trying to do is write a subkey under HKCU\SOFTWARE\Microsoft\Office\Outlook\Addins that matches the name of the ProgID of your add-in class and includes the four expected values listed above. FriendlyName and Description are strings, and LoadBehavior and CommandLineSafe are DWORDs. LoadBehavior = 3 means load the add-in on startup. Instead of using the .rgs file, you could also use an installer to write this registry data. (In fact, many setup engineers will tell you that using the Registry table in Windows Installer is preferred to using regsvr32, but the .rgs file will be fine for our purposes here.) I should also note here that putting this registry info under HKCU means that the add-in will only load for the current user. Anyone else logging into the machine will not be able to use the add-in. If you want the add-in to run for all users of the computer, put this registry info in the same place in the HKLM hive. Bear in mind that non-Administrators will not be able to adjust the load behavior of add-ins registered in the HKLM hive; you might want this or you might not. OK, so if you build the project now, your COM objects will be registered and your add-in will be set up to run when Outlook starts. At this point, you’ve got an empty but operational Outlook add-in. Go ahead and build the solution and then launch Outlook. To verify that our add-in is loaded, open the Tools menu and click Trust Center…. In the Trust Center dialog, click on the Add-ins section and you should see our “Greeting Outlook Add-in” listed under the Active Application Add-ins. Assuming all is well and you see the above, you’re all set. As I’ve said already, if you wanted to skip the Form Region and just implement some simple business logic with minimal UI, you’d be almost done. You’d only need to add a reference to the Outlook type library (which we’ll do in a later section) so you can use the interfaces defined therein. Creating Your Form RegionBefore we do any coding, we should go make our form region in the designer. (Actually, I typically do this part after implementing the basic code in my add-in, but for the sake of this exercise it makes more sense for us to do it now.) Form Regions are created using the form designer in Outlook 2007, not Visual Studio. So launch Outlook 2007 and open the Tools menu. Click Forms->Design a Form and then select Message in the Design Form dialog that opens. Click Open and you’ll see the old Outlook form designer UI. But here’s the twist: On the Ribbon, select Form Regions->New Form Region. A blank form surface will open and you can drop pre-bound controls onto it using Field Chooser, or you can open the Control Toolbox and drop generic Outlook form controls that you can load with your own data. You can even specify additional ActiveX controls, like any custom controls that you write yourself, by right-clicking on the Control Toolbox surface and selecting the Custom Controls menu item that appears. One warning though: Outlook has created their own controls (actually wrappers around the old Forms 2.0 controls) and you’ll want to use these new Outlook controls. However, they are not on the Control Toolbox by default – the old Forms 2.0 controls are. So you’ll need to right-click, select the Custom Controls menu item and add the Microsoft Office Outlook Label Control and the Microsoft Office Outlook Text Box control. Now you should see this in the Control Toolbox. Notice the blue-colored big ‘A’ and ‘ab|’ controls icons at the bottom of the toolbox. OK, let’s do something really simple to start with: select the blue big ‘A’ for the Outlook label control and double-click on the form surface to create a label with the default size. Then do the same with the Outlook textbox (the blue ‘ab|’). Position the label in the upper-left corner of the form and then the textbox just below it, as shown: Right-click on the label control and click ‘Properties’ to open the main Properties dialog. Change the Name field to something more informative than “OlkLabel1” – I’m going to just do a hello world sort of thing with a greeting so I’m using “GreetingLabel” as the name and “Greeting:” as the caption. Now the Layout tab on the Properties dialog is pretty interesting – notice the “Automatic Layout” section on the dialog. The Outlook team took the time to create an auto-layout engine that will do its best to arrange the controls on the form for you in a clean, well-aligned way. It may not seem interesting for our simple little two-control form region but trust me: when you’ve got a lot of controls and you want everything to resize cleanly, it’s a lifesaver! All we’re going to ask Outlook to do is keep our label control anchored in the upper left corner, so just accept the defaults as shown here. For the textbox control, let’s go ahead and rename it also. I’m going to call it “GreetingTextbox”. But now hit OK and return to the form designer before going to the Layout tab. Drag the right corner of the textbox control all the way out to the right edge of the form surface. Now open the Properties dialog and go to the Layout tab. Under the Automatic Layout section, set the Horizontal property to “Grow/shrink with form”. Set the minimum width to 30 and hit OK. What this will do is tell Outlook to make sure that your textbox spans the entire width of the form region no matter what size the user has resized the window to. It makes your UI look a look more professional if the controls resize properly regardless of what the user likes to do with their windows! All that’s left now is to save the file somewhere convenient. On the Ribbon, click Form Region->Save Form Region to bring up the Save dialog. Notice that the file extension is .OFS on the Save Form Region dialog – that’s the extension for an Outlook Form Region layout file, so when I refer to the OFS file, this is what I’m talking about. I’m going to save my OFS file as GreetingSample.ofs and stick it down in the Debug output folder of my VS project – you’ll see later why I’m choosing that location. At this point, we’re done in the Outlook Form Designer so you can just close the form region using the close (X) button. Outlook will ask you if you want to save changes – you can ignore this and click No since we’ve already saved our OFS file. (I think Outlook is actually asking you here if you want to save the other form surface you opened when you first clicked Tools->Forms->Design a Form, so don’t worry about it.) Creating the Manifest FileThe XML file that Outlook uses with form regions is called the “manifest” file. It’s essentially a simple .config file for the form region, including things like the form region type (adjacent, replaceAll, etc.), any custom icons to use, strings for localization, etc. Getting into all the specifics of the manifest file would take a while, so just check it out on MSDN (actually a download in this case). Here’s the XML that I’ve got saved in GreetingSample.xml: <?xml version="1.0" encoding="utf-8" ?> In this XML, we’re providing Outlook with:
Since this is a simple demonstration, I’m just going to leave the XML file on the hard drive – but, as I noted earlier, you could (and probably should) embed the file in the resources of your DLL. Save the file into your VS project’s Debug output directory (like we did with the .OFS) but this time, name it after the output DLL with the .xml file extension appended, e.g., C:\Projects\Fortis\Prototypes\CppFormRegionAddin\CppFormRegionAddin\Debug\CppFormRegionAddin.dll.xml. Strictly speaking, you don’t have to name it after your DLL, but this allows us to do a nice shortcut with the .rgs file when doing the next bit of registration. Registering Message ClassesNext, we need to tell Outlook when to use this form region. We do this by associating the form region with one or more message classes using registry keys. For each message class for which you want to register, add a key under HKLM|HKCU\Software\Microsoft\Office\Outlook\FormRegions. Then in that subkey, you add a value named for your form region and set the data to either:
If you do this latter approach, you must implement the GetFormRegionManifest method in your add-in class. I recommend it – especially if you are going to ship your form region implementation to an untrusted audience – because you can keep your manifest file safe in your resource DLL and load it from memory at runtime. We’ll add our registry data by using the .rgs just like we did before. Edit the FRAddin.rgs file so it looks like this: HKCU By doing this, we will display our form region for all mail messages with the standard IPM.NOTE message class. As I noted in the last section, naming the manifest file after our DLL lets us use the registrar to preprocess the .rgs file and automatically set the path correctly, using the %MODULE% token. After you rebuild the project, you should see something like this in the registry: Note that by adding our registry info to HKCU, we’ll only get our form region when logged in with the current username. If you wanted to have the form region work for all users on the machine, you will need to write the data to the HKLM hive. Implementing _FormRegionStartupNow we need to implement a second COM interface so Outlook can call some methods specific to loading and initializing a Form Region. We’ll be adding the interface support to our existing CFRAddin class, so again right-click on it in the Class View and click Add->Implement Interface but this time choose the “Microsoft Outlook 12.0 Object Library <9.3>” type library and select the _FormRegionStartup interface. Now you should see some new methods stubbed out in your header file: // _FormRegionStartup Methods The first two methods will be called by Outlook as it loads and initializes your form region. The last two can be used to load your manifest file from the resources and any custom icons you want to use for the mail list, but you don’t have to implement the stubs – they won’t be called by Outlook unless you take some additional steps. The GetFormRegionStorage method will be called immediately upon the user attempting to open an item for which you are providing a form region. You can either return the path to your Form Region file or the actual binary stream of the file contents. The BeforeFormRegionShow method is your opportunity to initialize your controls after Outlook has loaded the form region and instantiated all the controls. If you attempt to build the project at this point, it will not compile. For some reason, the definition for the BeforeFormRegionShow method is incorrect: the type of the argument is FormRegion, but the real interface name in the Outlook 12.0 type library is _FormRegion. (Notice the leading underscore?) So if you just add the missing underscore to the beginning of the interface name, it will then compile successfully. You’ll still have some warnings about some needing to rename some identifiers, which you can fix by using the rename attribute. Wiring It All UpOK, now we have to wire up these methods and get the form region initialized in code. First, we need to include an additional type library. Despite the fact that we’re using the Outlook form control wrappers, we still need to include the Forms 2.0 type library because we’ll have to use some interfaces defined in that typelib. So go into your stdafx.h file and look for the #import statements that we’ve already automatically added when adding interfaces via the wizard. You want to add the following line to import the Forms 2.0 type library: #import "C:\WINDOWS\system32\fm20.dll" raw_interfaces_only, raw_native_types, auto_search Important: For some reason, the MIDL compiler that ships with VS2008 crashes if I put the above line below the existing #import statements, so I’ve put it before the other two #import statements and that seems to handle it. (If someone knows what I’m doing wrong, please drop me a note!) In Ryan’s article, he creates a nice wrapper class for each possible instance of his form region. This is because the user could open multiple messages at the same time, and each one would need its own form region if there was any message-specific state that needed to be stored or events to be handled (e.g. button-clicks).Since we’re only going to initialize the textbox control with an initial value, we don’t need to do that – but I’ll come back to this topic in Part 2 of this article and show you a basic pattern of implementation because it’s a pretty typical scenario. All we need to do for now is implement two_FormRegionStartup methods: GetFormRegionStorage and BeforeFormRegionShow. First, I’m going to move their definitions over into my FRAddin.cpp file. Then you want to implement them as shown (of course, this is quick and dirty code with minimal error handling): STDMETHODIMP CFRAddin::GetFormRegionStorage(BSTR FormRegionName, LPDISPATCH Item, long LCID, OlFormRegionMode FormRegionMode, OlFormRegionSize FormRegionSize, VARIANT * Storage) // Set our greeting message in the textbox HRESULT CFRAddin::HrGetReceivedByName(_FormRegion * pFormRegion, BSTR *pbstr) (I’m using a couple helper methods here, HrGetReceivedByName and HrGetGreetingTextbox, for which you’ll need to add declarations in your header file.) The first method, GetFormRegionStorage, is where Outlook asks you for your .OFS file. It passes you a lot of info, like the name of the FormRegion it wants, whether the message is being opened in the Reading Pane or the Inspector (and whether that’s read mode or compose mode), etc. If you have different form regions depending on the values of these arguments – for example, I’ve had to show a different form in the Reading Pane – then you could add conditional logic here to choose the appropriate .OFS file. You can also choose to not return anything; just return an empty VARIANT and Outlook will default to its regular rending for the item. (Nice when you want to disable your form region based on the user’s configuration or security privileges.) If you look at my sample code, I’m just returning the path to the .OFS file in my output directory. Outlook will accept the path and go load the file from disk for you (assuming it’s really where you say it is). As long as you deploy the .OFS file in the same local folder as the .DLL, this code will work for you. However, for production solutions, you probably want to load the .OFS into an IStorage and return that instead. This is another topic that I will cover in Part 2. The second method, BeforeFormRegionShow, is where most of the work happens. This method is your chance to initialize controls on the form region after they are instantiated but before it is rendered in the Outlook user interface. My code, despite its relative length (compared to Ryan’s managed code), is doing very little: I retrieve the name of the recipient and load this into the form region textbox (with a little added text for a friendly greeting). Most of the code is propgets and QueryInterface calls that you gloss right over in managed code (or VB for that matter – yes, you could do all this in VB6 if you wanted to). If you have the courage to let it fly, just launch Outlook and open a message. You should see something like this: If you have any issues – e.g. the form region doesn’t appear or Outlook crashes on you when you open the message, put breakpoints at the beginning of all of the methods we've implemented, attach VS to the Outlook process and reopen the message. You should find the issue pretty quickly; we’re not doing rocket science here. (If you get stuck, leave me a comment and I'll do my best to get back to you quickly.) Additional Areas of ImprovementThat's all we're going to do for now but in Part 2 of this article, I’ll show you some things that you probably want to do for a production-quality solution: Technorati Tags: Outlook add-in,_IDTExtensibility2
Should you run into errors, omissions or some other unbelievably stupid and completely unforgivable mistake, let me know so I can improve this content. And if this helped you out or you just liked it, leave me a note for that too. I'll try to get to Part 2 in the next week or two... October 28 Missing Attachment Outlook 20007 Add-inProbably my favorite Outlook add-in out of all the ones that I've written is one of the simplest: the Missing Attachment Add-in for Outlook 2007. Who hasn't at some point sent off an email that was supposed to have some important attachment -- only you forgot to attach it! (I probably do this at least once a month.) The functionality is simple: If you click 'Send' on a newly-composed email and the add-in notices any of your user-defined keywords, it will prompt you and allow you to cancel the send and return to the draft of the message. I pre-populate the keywords collection for you with some generally useful ones, but you can go into Tools->Options and edit the list on a custom property page that I've added there. I did this one as a paid project for Fortis Software, but we're giving it away on our website as a free powertoy. The only caveat is that it's Outlook 2007 only. (Not my requirement, sorry.) The other nifty thing about this add-in is that it's written in unmanaged C++ so there's no .NET Framework requirement, no VSTO runtime being installed. It's been tested on XP SP2 and Vista RTM (at the time, the two supported O/Ses for Office 2007). Technorati Tags: Outlook add-in October 24 Ryan's BlogConsidering that I just gave Ryan Gregg a great plug on his book, I should also mention his blog on MSDN: http://blogs.msdn.com/rgregg Personally, I don't think he updates it as much as he should (cough, cough), but he's a pretty busy guy and he's working hard behind the scenes to make Outlook as good to us programmers (and end users) as he can... so I guess I can cut him some slack. ;) Good Place to Start on Outlook 2007 ProgrammingThere's a lot of decent resources on the web for Outlook Programming these days -- so much more than just a few years ago -- but I think you still want to get it from the horse's mouth when possible. So the bible is really MSDN's Outlook Developer Portal. I probably visit their Outlook 2007 Developer Reference daily, just because the Outlook Object Model is so rich now. And with the smart folks managing the OM in Redmond these days, I'm sure it will just continue to get better. Speaking of those smart guys... Outlook PMs Randy Byrne and Ryan Gregg (Go Jayhawks!) have put out a truly impressive book that is head and shoulders above anything previously published on Outlook programming: Programming Applications for Microsoft® Office Outlook® 2007. I've picked up a couple of these for the office and they're great -- usually I can point out the answer in this book when someone on my team has an OM question that's not clearly answered in MSDN. Of course, there are other great resources on the web like www.slipstick.com and www.cdolive.com, but honestly I don't have to go there as much these days because MS has really spent some time and money on improving their own developer resources, both on the web and in print. The one thing that I think is missing out there is much content on how to write Outlook extensibility code in unmanaged C++. And that's something that I think I'm going to help with on this blog. Hello, World!Hi there!
This is my first blog post on my new Windows Live Space. I think I have some old content out there in various places but as of October 2008, this is will be my home for interesting techincal factoids, random musings and some programming tips on Outlook programming (my forté these days).
I work for Fortis Software in Clearwater, FL, USA. Fortis is a boutique consulting firm specializing in software engineering for other software companies (ISVs). I do a lot of work with Outlook via C++ for some very cool clients.
This is just my first post, but I'll put up some link to some cool things I've written very soon!
-m |
||||
|
|