Tuesday 17 April 2007

Task Pane & Ribbon Combination on Outlook 2007 Walkthrough with VSTO 2005 SE Add-in project






If you are looking for beginner guide on developing either customized ribbon or task pane. This article might not be the one for you. The easiest and also the quickest way to jump start is to watch the MSDN nugget Customising the Ribbon in 2007 Microsoft Office Applications,Dynamically Updating the Ribbon in the 2007 Microsoft Office System, and Working with Images in the 2007 Microsoft Office System Ribbon and Building a Custom Task Pane for the 2007 Microsoft Office System by Martin Perry.

So to start, please click the picture on top of the first paragraph,in this article, I just want to introduce the combination of ribbon and task pane for Outlook only. It contains only one toggle button on the ribbon to control show/hide of my task pane which contains only a treeview and couple of buttons. Joanna Bichsel has a posted an interesting article about ribbon/task pane combination with a cute name of "Mr. Task Pane, meet Mrs. Ribbon" ;) If you are looking for a MS Word 2007 solution, that's definitely the one for you. However, Outlook , as a maverick species in Office System , holds its ribbon only when you open up the mail item in the main explorer window. Therefore,you somehow need to obtain the handle of the mail item window-- Inspector window, so MS calls it. So by doing this, you can attach the task pane to individual mail item instead of the main explorer window. So we start by creating our own Inspector Wrapper class to contain both our own customized taskpane in project and the Outlook.Inspector object. Please note we need a inspector collection as well as the custom task pane collection to contain all the mail items in Outlook explorer so we can handle the init and cleaning up for every individual mail item. Later on , we use a dictionary to implement inspector collection.

So we add a new class in our project , say MyInspectorWrapper, and put in following reference before your class definition(I assume you know how to create an add-in project using VSTO SE already so you get the correct reference):



using Microsoft.Office.Tools;
using Outlook = Microsoft.Office.Interop.Outlook;


then in your class, add in following private member variables for our Inspector and our customtaskpane object which will point to our own task pane where the useful controls are living in



private Outlook.Inspector myInspector;
private CustomTaskPane myTaskPane ;


Then we pass in the runtime inspector through an overloaded constructor:



public InspectorWrapper(Outlook.Inspector insp)
{
myInspector = insp;
//handle the form closing event of the inspector window
((Outlook.InspectorEvents_Event)myInspector).Close += new Outlook.InspectorEvents_CloseEventHandler(InspectorWrapper_Close);

//this is where you pass in your own task pane to //customitaskpane object at runtime
myTaskPane = Globals.ThisAddIn.CustomTaskPanes.Add(new YourTaskPane(), "Your Pane", myInspector);

//update the visual appearance of ribbon button when the
//task pane is shown or hidden

myTaskPane.VisibleChanged += new EventHandler(MyTask_VisibleChanged);

}

then we handle the close event of the run time inspector.



public void InspectorWrapper_Close()
{
if (myTaskPane != null)
{
Globals.ThisAddIn.CustomTaskPanes.Remove(myTaskPane);

}
//destroy the taskPane
myTaskPane = null;

Globals.ThisAddIn.inspectorCols.Remove(myInspector);

//unload the close event of inspector
((Outlook.InspectorEvents_Event)myInspector).Close -= new Outlook.InspectorEvents_CloseEventHandler(InspectorWrapper_Close);

myInspector = null;

}

then handle the visual appearance change, i.e. when you hide the task pane from certain mail item inspector window, we need to popup the toggle button pressed in beforehand.



public void MyTask_VisibleChanged(object sender, EventArgs e)
{
//method to refresh button will be written in the ribbon //class, the dangerous bit is using public access ribbon class object to use your RefreshControl method
Globals.ThisAddIn.ribbon.RefreshControl("MyToggleButton");

}

And naturally, you might be wondering where the "ribbon" property is from. It's actually just a public member variable in your ThisAddin partial class together within your ribbon cs file

you can put it in like



public YourRibbon ribbon;


at last, expose your custom task pane, so it will be called out when press in the ribbon toggle button.



public CustomTaskPane CustTaskPane
{
get { return myTaskPane; }
}


At this point, you have a wrapper contains your runtime inspector window object and your task pane to be shown in the individual mail item. Now we need to handle the new inspector event in the insepctor collection that we are going to make in the ThisAddin.cs file

add these namespaces in front of your partial class ThisAddin



using System;
using System.Windows.Forms;
using System.Collections.Generic;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;

create the runtime inspectors object and the collection of all inspectors corresponding to mail items in your inbox.



private Outlook.Inspectors inspectors;

public Dictionary inspectorCols = new Dictionary();




in the start_up method of the addin, write code to wire up the event of new inspector event of Outlook.Inspectors and bind all the mail items inspector window to the inspector collection.




private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);

//bind all messages with inspector event handler
foreach (Outlook.Inspector insp in inspectors)
{

Inspectors_NewInspector(insp);
}

}


and clean up in the addin's shutdown event handler



//clean up the memory allocated in startup
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
inspectors.NewInspector -= new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
inspectors = null;
inspectorCols = null;
}

then we write the event handler for newinspector event



public void Inspectors_NewInspector(Outlook.Inspector insp)
{
//initiate inspector
if (insp.CurrentItem is Outlook.MailItem)
{
inspectorCols.Add(insp, new InspectorWrapper(insp));

//if ribbon is initialized,create new task pane for each email item
if (ribbon != null && ribbon.RibbonUI != null)
{
ribbon.RefreshControl("MyToggleButton");
}
}
}


The RefreshControl method will be written in your ribbon class to update the appearance of your togglebutton, and the content of this method will be simply calling invalidate control on your IRibbonUI's control, i.e. your toggle button



public void RefreshControl(string controlID)
{
//refresh the toggle button
ribbon.InvalidateControl(controlID); ;

}

and write your toggle button callback to handle the onAction event



public void onTogglebuttonClick(Office.IRibbonControl control, bool isPressed)
{


Outlook.Inspector insp = (Outlook.Inspector)control.Context;
InspectorWrapper thisWrapper = null;

if (Globals.ThisAddIn.inspectorCols.ContainsKey(insp))
//get the inspector containing the taskpane
thisWrapper = Globals.ThisAddIn.inspectorCols[insp];

if (thisWrapper != null)
{
//get your own task pane's instance and show it when button is pressed
Microsoft.Office.Tools.CustomTaskPane individualTaskPane = thisWrapper.CustTaskPane;
if (individualTaskPane != null)
{
individualTaskPane.Visible = isPressed;
}

}

}

Now we have event handler for toggle button, our own inspector instance which is implemented using an Instance wrapper, we just need to tell the ribbon class when to fireup the ribbon UI. In our case, I only want to show it when you click on the existing email item , so the magic word is "Microsoft.Outlook.Mail.Read" and let's put that check conditin into our GetCustomUI callback which is created by the VS. Bear with me a moment, we are almost there...



/**only show it when reading mails.
*if your want it in new email as well. put in check for "Microsoft.Outlook.Mail.Compose"
*/

public string GetCustomUI(string ribbonID)
{
string ribbonXML = String.Empty;

if (ribbonID == "Microsoft.Outlook.Mail.Read")
{

ribbonXML = GetResourceText("RibbonAndCTP.YourRibbon.xml");


}

return ribbonXML;



}

That's it. All done. Now if you compile your project and you should have a neat task pane with your useful controls when you press down the toggle button in every email window. BTW, if you have a "Close" button on the task pane (9 out of 10 you have)to close/hide the task pane, you will need to handle the pressstate event, so you can write GetPressedState callback in your ribbon class to handle the property in your ribbon xml "getPressed" for a final touch-up.



public bool GetPressedState(Office.IRibbonControl control)
{
Outlook.Inspector insp = (Outlook.Inspector)control.Context;
//if inspector window is in inpector collection, return the current state of taks pane,
//otherwise pop up the toggle button

if (Globals.ThisAddIn.inspectorCols.ContainsKey(insp))
{
//get the inspector wrapper contains the task pane
InspectorWrapper thisWrapper = Globals.ThisAddIn.inspectorCols[insp];
Microsoft.Office.Tools.CustomTaskPane thisPane = thisWrapper.CustTaskPane;
return thisPane.Visible;


}
else
{
return false;
}

}

2 comments:

Ale said...

Some strange behaviors:
Did you test it?
try to open mail message, close it and reopen, task pane replicate itself.

Jay said...

No Alex, I didn't have this problem as you described. Did you copy the steps as I posted in my blog? Yes, I did test this and the screendump you see on top of the article is the result.