Click here to Skip to main content
Click here to Skip to main content

Additional custom panel in Microsoft Outlook

, 25 Jun 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
An example of undocumented integration into the user interface of Microsoft Office applications.

Additional panel inside Microsoft Outlook

Introduction

I have been developing applications and add-ins integrated into Microsoft Office for quite a long time now, and no matter what type of application you are working on, at some point, you always face the same frustration: the frustration of limited functionality provided by the "official" Microsoft interfaces, such as Outlook Object Model or Word Object Model. One of the weakest bits in these interfaces is the lack of support for user interface customization - as long as you just want to add your own toolbar, you are fine, but if you want to do anything more than that, you are going to hit a brick wall. Or, are you really? This article should hopefully give you some guidelines on how to overcome these limitations of Word, Excel, Outlook, and other Office applications by using couple of dirty, yet reliable tricks, all from 100% managed code.

To demonstrate the principle of advanced UI integration, I've decided to build a simple panel to Microsoft Outlook which will display some basic information about the email which is selected. I've picked up Microsoft Outlook, but this principle can be applied to any application from the Office family such as Word, Excel, OneNote etc.

Hacking into Outlook

Before we start coding, let's first discuss the principles behind the scenes. Like any other Windows application, the main window of Microsoft Outlook consists of multiple child windows (in .NET analogy, they would be members of the Control.Controls collection), so it should be possible to add your own window to this hierarchy. Before we start adding any new windows, at first, we need to know how the window structure of Outlook looks like. We can use my favorite tool Spy++ to view this structure:

Outlook children window organization

From Spy++, you can see that there is a toolbar panel window (green) on the top, the folder list view window (purple) on the left, and finally, the main grid (red) on the right side of the window.

Now, we know the structure, so we can solve the crucial problem - we have nowhere to place our own window. To get some free space on the dialog, we will have to "borrow" the space from an existing child window. I would like to dock my panel to the right, which makes the main grid the most logical candidate to borrow some space from. So, if we reduce the width of the messages grid, we should have some free space to place our own window:

Outlook resized main grid

But, that is enough with the theory - let's see how we implement this.

Implementation

I wanted to build a generic framework which will allow docking any standard .NET UserControl into Outlook, so I have decided to use the following structure:

Panel implementation

I call the window which we are borrowing the space from, the "sibling window". Any child window of the Outlook main window can become the sibling window; however, for this case, we are going to use the messages grid. As mentioned above, we reduce the width of the sibling window, which creates free space on the dialog. We are going to place our own window called PanelContainer into the newly created free space. The PanelContainer has two purposes: it contains an empty Panel control which can host any .NET control, and it provides Outlook look and feel for the background and borders (otherwise, the background would be white and there would be no border lines). Once we have the PanelContainer in place, we can just assign a standard .NET UserControl to it, and we are done - the PanelContainer will do all the hard work, and the nested control does not have to even know that it is "living" inside the Outlook panel.

The easiest to get our PanelContainer displayed in Outlook is through an Outlook add-in. An Outlook add-in is is a DLL library (either managed or unmanaged) which implements the IDTExtensibility2 COM interface:

public interface IDTExtensibility2
{
 void OnAddInsUpdate (ref Array custom);
 void OnBeginShutdown (ref Array custom);
 void OnConnection (Object Application, ext_ConnectMode connectMode, 
                    Object AddInInst, ref Array custom);
 void OnDisconnection (ext_DisconnectMode RemoveMode, ref Array custom);
 void OnStartupComplete (ref Array custom);
}

When Outlook starts, it will load the DLL, and if it finds out that the IDTExtensibility2 interface is implemented, it will call the OnStartupComplete method. In any process/application, there should always be just a single thread which executes the message loop of all child windows - the UI thread. Thanks for us, the call to OnStartupComplete (like all calls of the IDTExtensibility2 interface) is made from the UI thread, which is very important because we are going to create the PanelContainer window and we will assign it as a child of the Outlook main window. If this call was made from another thread, we would have bigger problems because the PanelContainer instance would be created out of the UI thread, which would cause big stability issues.

public void OnStartupComplete(ref System.Array custom)
{            
 ...
            
 //Find Outlook window handle (HWND)
 IntPtr outlookWindow = FindOutlookWindow();
                    
 //Create new container instance
 _panelContainer = new PanelContainer();
            
 //Set the parent window of the panel container to be Outlook main window
 SafeNativeMethods.SetParent(_panelContainer.Handle, outlookWindow); 

 ...
}

We have made the PanelContainer instance a child window; now, we need to move it to its place. But, before we do that, we need to reduce the width of the sibling window (i.e., the message grid). To find the sibling window, we are going to use the FindWindowEx API (see the MSDN documentation) method, which will return the first child window of the Outlook main window with the specified window class. That is when Spy++ comes handy again - we can see that the window class of the message grid is rctrl_renwnd32.

private const string SIBLING_WINDOW_CLASS = "rctrl_renwnd32";

IntPtr siblingWindow = SafeNativeMethods.FindWindowEx(outlookWindow, 
                       IntPtr.Zero, SIBLING_WINDOW_CLASS, null);

Now, we have all window handles we need; so, we can finally resize the sibling window and move the PanelContainer window on its place.

private void ResizePanels()
{  
           
 //Get size of the sibling window and main parent window
 Rectangle siblingRect = SafeNativeMethods.GetWindowRectange(this.SiblingWindow);
 Rectangle parentRect = SafeNativeMethods.GetWindowRectange(this.ParentWindow);

 //Calculate position of sibling window in screen coordinates
 SafeNativeMethods.POINT topLeft = 
   new SafeNativeMethods.POINT(siblingRect.Left, siblingRect.Top);
 SafeNativeMethods.ScreenToClient(this.ParentWindow, ref topLeft);

 //Decrease size of the sibling window
 int newWidth = parentRect.Width - topLeft.X - _panelContainer.Width;
 SafeNativeMethods.SetWindowPos(this.SiblingWindow, IntPtr.Zero, 0, 0, newWidth, 
                                siblingRect.Height, SafeNativeMethods.SWP_NOMOVE | 
                                SafeNativeMethods.SWP_NOZORDER);

 //Move the container to correct position
 _panelContainer.Left = topLeft.X + newWidth;
 _panelContainer.Top = topLeft.Y;

 //Set correct height of the panel container
 _panelContainer.Height = siblingRect.Height;            
}

We are almost done; but, we need to handle one more situation. The ResizePanels method calculates the correct placement for the PanelContainer; but, as soon as the user resizes the Outlook window, it is not valid anymore because, by default, the width of the sibling window would be restored and the PanelContainer would stay in place, which is not what we want. The solution is, however, quite simple; we just need to make sure to call the ResizePanels every time the sibling window is resized.

In this situation, we cannot use the standard Control.SizeChanged event because the sibling window is just a native window and we have nothing but its window handle. Fortunately, that is where the NativeWindow class comes in place. The NativeWindow class allows us to subclass a window procedure of any window (no matter if it is managed or unmanaged). By subclassing a window, we are going to receive all window messages into our own WndProc method; so, by looking for the WM_SIZE message, we can detect when the window is resized. We will first let the default window procedure process the message, and then we will do our own processing. I created a small derived class, SubclassedWindow, for this purpose - it accepts a native window handle, and it will raise a SizeChanged event every time the window receives the WM_SIZE message.

sealed class SubclassedWindow : NativeWindow
{
 public event EventHandler SizeChanged;

 protected override void WndProc(ref Message m)
 {
  base.WndProc(ref m);

  if (m.Msg == (int)SafeNativeMethods.WindowsMessages.WM_SIZE)
   OnSizeChanged();
 }

 private void OnSizeChanged()
 {
  if (SizeChanged != null)
   SizeChanged(this, null);
 }
}

The only remaining bit is to call the ResizePanels method every time the sibling window size changes.

//Subclass sibling window to monitor SizeChange event
_subclassedSiblingWindow = new SubclassedWindow();
_subclassedSiblingWindow.AssignHandle(this.SiblingWindow);
_subclassedSiblingWindow.SizeChanged += 
    new EventHandler(subclassedSiblingWindow_SizeChanged);
            
...
        
private void subclassedSiblingWindow_SizeChanged(object sender, EventArgs e)
{
 //Since sibling has changed its size, we need to resize both windows again
 ResizePanels();
}

The PanelContainer will now be placed in the correct position, and its dimensions will always match the free space.

Recipients Panel Demonstration

Since we have our generic PanelContainer in place, it is time to create some demonstration control and nest it into the container. I chose a simple, yet interesting, demo of Outlook integration - I am going to show the subject and recipients of the selected email in the panel. If the user clicks on the recipient of the mail in the panel, it will open a new web browser window and search for the person's name on Google. And, all this will be achieved through a standard UserControl, which I call MyPanel.

MyPanel user control

To link this control to Outlook, I need to capture the SelectionChange event (we finally use the Outlook Object Model for this) and update the controls according to the email which is selected in the message grid.

private void outlookExplorer_SelectionChange()
{           
 //Take the first selected item 
 MailItem mailItem = _outlookExplorer.Selection[1] as MailItem;
 
 //Populate the labels
 string senderName = mailItem.SenderName;
 this.lblSender.Text = String.Format("{0} writes regarding", senderName);
 this.lblSubject.Text = mailItem.Subject;
 
 ...
}

And, that's it - we're done!

Final Word

I hope this example proved to you that there are further possibilities for Office integration than the ones officially mentioned by Microsoft.

This code could, however, still use some improvements - for example, the ability to resize the width of the PanelContainer is missing; also, a more generic docking mechanism would be nice. I hope to eventually implement this, once I need it...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Lukas Neumann
Software Developer (Senior) MEMOS Software (www.memos.cz)
Czech Republic Czech Republic
I started developing software in Quick Basic on my very first PC running 8086 CPU @ 8MHz. Then I moved to Visual Basic, followed by MFC and for last 4 years i am stuck with C# and Microsoft .NET. Now I work as a senior developer for MEMOS Software. My hobby is Office integration, especially Microsoft Outlook.
 

Check out my blog at
http://blog.memos.cz

Comments and Discussions

 
QuestionNot able to retrieve social connector handle PinmemberMember 269401615-Aug-14 0:00 
QuestionHow bad are Microsoft PinmemberMember 804748926-Nov-12 1:49 
QuestionSupport for outlook 2010 Pinmembershahshi1-Aug-11 4:40 
AnswerRe: Support for outlook 2010 PinmemberLukas Neumann27-Feb-13 22:12 
GeneralRe: Support for outlook 2010 PinmemberMember 803260020-Jun-13 23:26 
QuestionTemp attachment in OLK folder not being removed after assigning ref to MailItem [modified] PinmemberDanny Mosquito12-Apr-11 14:44 
AnswerRe: Temp attachment in OLK folder not being removed after assigning ref to MailItem PinmemberDanny Mosquito12-Apr-11 23:51 
Questionno other way with standard outlook api? PinmemberMember 141487315-Mar-11 3:44 
GeneralOneNote customization Pinmemberumarinam15-Feb-11 3:17 
Questionhow to use your project source code Pinmembersppradip10-Aug-10 20:38 
Generalfrom 2007 there is a new solution Pinmemberyoavs2-Jun-10 23:28 
QuestionHow do i add a panel to the Calendar? Pinmemberyoavs25-Apr-10 22:12 
Hi,
This is working great when i want to add\dock it to the inbox\mail window,
But i want to dock it when calendar is open.
The problem is that the calendar was not resizing properly and the panel seems not properly docked.
I tried to use spy++ ,but i can't find the right handle/class name.
Can u help me?
GeneralYou rock Lukas Neumann Pinmembertiger20054-Feb-10 11:38 
GeneralRe: You rock Lukas Neumann Pinmembertiger20055-Feb-10 7:45 
GeneralRe: You rock Lukas Neumann Pinmembertiger200518-Feb-10 6:27 
GeneralRe: You rock Lukas Neumann PinmemberTOMTEFAR66616-Apr-10 2:36 
GeneralRe: You rock Lukas Neumann PinmemberTOMTEFAR66616-Apr-10 3:29 
QuestionIssue when resizing [modified] Pinmemberinshaf19-Aug-09 1:28 
GeneralNo load before install Project Setup PinmemberHermannClay14-Aug-09 11:54 
GeneralHide panel PinmemberHermannClay12-Aug-09 8:44 
GeneralRe: Hide panel Pinmembermahesh_134_s7-Oct-09 0:49 
GeneralRe: Hide panel PinmemberJim Norman11-Nov-09 12:45 
GeneralRe: Hide panel PinmemberJim Norman24-Nov-09 5:31 
QuestionAdding Splitter Pinmemberinshaf8-Jun-09 2:13 
GeneralThe ability to resize the width of the PanelContainer Pinmembersergey_serebryakov14-Apr-09 4:32 
GeneralTab is not working Pinmembermahesh_134_s22-Mar-09 23:12 
GeneralGreate article... PinmemberMember 269378012-Feb-09 22:15 
Generalusing TextBox on the panel Pinmemberskris_0112-Nov-08 5:02 
GeneralRe: using TextBox on the panel Pinmembermahesh_134_s4-Mar-09 19:36 
GeneralRe: using TextBox on the panel Pinmembermahesh_134_s4-Mar-09 20:09 
GeneralRe: using TextBox on the panel PinmemberMax Lynch22-Jun-09 13:47 
GeneralRe: using TextBox on the panel PinmemberMember 26940161-Dec-09 23:56 
QuestionCustom Navigation Panel Bar Button PinmemberMember 386861912-Oct-08 3:02 
AnswerRe: Custom Navigation Panel Bar Button PinmemberLukas Neumann19-Oct-08 22:41 
GeneralPanelContainer resize PinmemberMember 59536422-Sep-08 3:28 
GeneralRe: PanelContainer resize PinmemberLukas Neumann22-Sep-08 4:18 
GeneralRe: PanelContainer resize PinmemberMember 59536423-Sep-08 4:02 
GeneralRe: PanelContainer resize PinmemberLukas Neumann30-Sep-08 1:00 
GeneralRe: PanelContainer resize Pinmembersiquylee12-Jun-09 8:10 
GeneralRe: PanelContainer resize PinmemberMax Lynch22-Jun-09 17:16 
GeneralUnable to load the add-in outlook PinmemberLaksDeepsRishi16-Sep-08 20:27 
GeneralRe: Unable to load the add-in outlook PinmemberMember 59536417-Sep-08 2:35 
GeneralRe: Unable to load the add-in outlook PinmemberLukas Neumann17-Sep-08 22:37 
GeneralRe: Unable to load the add-in outlook PinmemberLaksDeepsRishi18-Sep-08 21:00 
GeneralRe: Unable to load the add-in outlook PinmemberLukas Neumann18-Sep-08 22:44 
GeneralRe: Unable to load the add-in outlook PinmemberLaksDeepsRishi19-Sep-08 16:35 
GeneralRe: Unable to load the add-in outlook PinmemberLukas Neumann20-Sep-08 1:24 
GeneralRe: Unable to load the add-in outlook PinmemberHermannClay14-Aug-09 12:36 
GeneralRe: Unable to load the add-in outlook PinmemberMember 461302420-May-10 0:51 
GeneralThanks For Nice Code PinmemberIzzet Kerem Kusmezer8-Jul-08 12:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 25 Jun 2008
Article Copyright 2008 by Lukas Neumann
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid