Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Additional custom panel in Microsoft Outlook

4.98/5 (44 votes)
25 Jun 2008CPOL7 min read 6   4.2K  
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:

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
//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.

C#
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)