|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI 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 OutlookBefore 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
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:
But, that is enough with the theory - let's see how we implement this. ImplementationI 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:
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 The easiest to get our 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 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 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 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 In this situation, we cannot use the standard 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 //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 Recipients Panel DemonstrationSince we have our generic
To link this control to Outlook, I need to capture the 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 WordI 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
|
||||||||||||||||||||||