This article describes how to use an add-in to add a window that is adjacent to the preview pane in Microsoft Outlook. This method is intended for use with Outlook 2003, with Outlook 2007, and with Outlook 2010. After the add-in creates the window, the add-in can call Windows APIs in the window exactly as any other window handle (HWND) can.
This article provides code samples and related documentation. To download this content, visit the following MSDN website:Important
Windows API (HWND) integration in Outlook is not a supported platform technology.
We understand that customers are already using the Windows API approach to integrate with Outlook, and the Outlook Social Connector also leverages this type of integration. Documentation for adjacent windows in Outlook provides "best practices" guidelines to help avoid conflicts with other programs that use this approach, including the Outlook Social Connector.
This sample code and the corresponding documentation are not supported by Microsoft. The Outlook product group does not consider this overall approach to be part of Outlook-supported architecture in terms of developing custom solutions with Outlook. Instead, we recommend that you use other approaches that have been more fully designed, tested, and documented to work with Outlook. Depending on the version of Outlook, these other approaches include Outlook custom forms, form regions, folder home pages, custom task panes, and the Outlook Social Connector (OSC) extensibility architecture. One key advantage of using these supported approaches is that developers will have a much greater chance that they will not encounter compatibility issues when a newer version or service pack of Outlook is released.
If you encounter issues when you use the information that is provided in the sample code, you can post comments on the MSDN Code Gallery page for this download. However, we cannot guarantee that support will be provided for your issue.
|Top-level window||Sibling window||Adjacent window||Explorer||Inspector||Legacy Outlook|
|The Outlook window handle that is parented to the desktop. This is either the frame that contains the explorer window or the inspector window.||In the explorer window, this is the preview pane. In the inspector window, this is the window that contains the client area of the inspector windows (the mail header, in addition to the body). The adjacent window is positioned next to the sibling window.||The window that the add-in creates next to the preview pane or next to the inspector window.||The main Outlook window that contains the navigation pane, the message list, and the preview pane, for example.||An Outlook window that displays one specific item such as mail or appointments.||Outlook 2003 and Outlook 2007. In this article, these versions are called legacy because their HWND hierarchy differs from that of Outlook 2010.|
Your add-in must have a mechanism to run tasks at idle. For example, you can do this by creating a hidden window that receives WM_TIMER messages. This works because WM_TIMER is a low-priority message that runs when there are no higher priority messages to process. See the Idlecall.cpp
file in download on the MSDN website that is provided at the beginning of the "More information" section.
Find all instances of Outlook
function looks for all top-level windows (by using the rctrl_renwnd32
Outlook message class). This function is first called when the add-in is initialized.
To find the explorer window frames, the function traverses the window hierarchy of the top-level window to locate the view pane and the preview pane (this functionality differs slightly between legacy Outlook installations and Outlook 2010). See the FindExplorerWindows
functions in the download. In this case, if the function does find the view and preview panes, the preview pane is considered the sibling window.
If the function does not find the preview pane, it tries to find a window that uses the AfxWndW
message class. This is the sibling window for an inspector window.
If the function finds the sibling window in either the explorer or inspector case, it calls FCreateHookedAdjacentWindow
. This creates the adjacent window. The parameters to FCreateHookedAdjacentWindow
(the sibling window), hWndParent
(the parent to hWndSibling
, which should also be the parent of the adjacent window), and hWndTopLevelWindow
(the top-level window). In addition to creating a window, FCreateHookedAdjacentWindow
also installs a message hook (WndProcHookedWindow
). You can use this message hook to determine when the preview pane or the inspector window has been resized and when the adjacent window must therefore also be adjusted.
If you find an explorer window but not a preview pane in the explorer, this means that the user has elected not to show the preview pane. FindExplorerWindows
installs a message hook (WndProcUnhookedWindow
) that listens for the WM_PARENTNOTIFY message that is sent when a child window is created. When you receive this message, you can call FindTopLevelWindows
again to determine whether the preview pane has been created.
You can also listen to the Application.NewInspector
Outlook Object Model events (see the Connect.cpp
file in the download), and then call the FindTopLevelWindows
function again when either message is received. Because Outlook sends these object model events before the corresponding explorer and inspector window handle is actually created, you must wait until idle before FindTopLevelWindows
is called by using the idle function mechanism.
Create the adjacent window, and associate the corresponding object model object that uses this window
function takes a top-level window and tries to associate an object in the object model (either an explorer or an inspector) to this window. The function does this by iterating through all explorers and all inspectors by calling QueryInterface
on the object model's IDispatch object for the IOleWindow interface, by calling IOleWindow::GetWindow
, and by then comparing the window handle that is returned by that function to the top-level window handle. See the HwndFromIDispatch
Message hook handling
Both message hooks that you install (WndProcHookedWindow
) must respond to the WM_REMOVING_WNDPROC message. For example, consider the following scenario:
Add-in A installs a message hook by calling SetWindowLong(GWLP_WNDPROC). This saves from the previous wndproc. Add-in B installs a message hook and then saves from the previous wndproc (installed by Add-in A). The following table shows the outcome.
|Installed wndproc||Saved off old wndproc|
|Add-in A||WndProcA||WndProcO (the original)|
In a typical situation in which a message hook is overridden, when add-in A is unloaded, this sets the message hook to WndProcO. And when add-in B is unloaded, this sets the message hook to WndProcA. Both actions are incorrect, and the second action triggers a crash if the add-in that contains WndProcA is unloaded.
To address this problem, each WndProc that is described in this article must support the WM_REMOVING_WNDPROC message. When an add-in is unloading, and it wants to restore the old WndProc, it first calls GetWindowLongPtr(GWLP_WNDPROC)
and then compares the current WndProc with the WndProc it installed. If these WndProc instances are equal, the add-in calls SetWindowLongPtr(GWLP_WNDPROC)
by using the saved old WndProc. If they are not equal, the add-in sends the WM_REMOVING_WNDPROC message to the HWND, with wParam == the old WndProc and lParam == the WndProc we installed. (See the WndProcInfo::Destroy
function.) The return value of sending the WM_REMOVING_WNDPROC message should always be 1 to signal that the message is handled. Asserts are added in code to detect whether this procedure was done, for your debugging (although there is nothing the add-in can do if the message was not handled).
On receipt of the WM_REMOVING_WNDPROC message (see the FHandleRemovingWndProc
function in the download), if lParam == our old WndProc, we swap our old WndProc with wParam. If this operation happens, then the WndProc returns the value 1. If the two values do not match, the message is forwarded to the old WndProc through CallWindowProc, as usual.
For an example: if we examine the same situation as mentioned earlier with Add-in A and Add-in B, if Add-in A were to unload, it would send the WM_REMOVING_WNDPROC message to the window with wParam = WndProcO and lParam = WndProcA. Because WndProcB is the registered window handler, WndProcB would process the message first. Because lParam = WndProcA and the old WndProc stored by Add-in B is also WndProcB, then Add-in B would set its old WndProc to WndProcO, and return 1. Add-in A takes no additional action. After this, this is the schematic of Add-in B:
|Installed wndproc||Old wndproc|
Overview of hosting multiple adjacent windows
When multiple add-ins intend to position windows adjacent to the preview pane, they have to work cooperatively so that all the windows occur in an orderly manner and do not overlap one another. The rest of this article describes how to cooperatively position windows in the explorer and inspector windows.
The code sample introduces the concept of the doubly linked list of adjacent windows. The linked list is not maintained by Outlook or by one single add-in. The "next" and "prev" links are maintained individually by each adjacent window. When an add-in intends to create an adjacent window, it adds itself to the head of the linked list (that is, prev = NULL). The head of the linked list is the controller; this is the window that directs all the other windows in how to position themselves. The controller is also responsible for listening to the WM_WINDOWPOSCHANGING message of the sibling window (see the WndProcHookedWindow
Handling the WM_WINDOWPOSCHANGING message
The code sample installs a message hook (WndProcHookedWindow
) for the sibling window. The purpose of handling this message for the sibling window is to adjust its location and size when the window is being sized so that room is made for the adjacent windows. When it handles this message, the controller asks all the adjacent windows (through the GetReservedRect
message to be detailed later) how much room each window requires. Then, the controller subtracts that space from the sibling window's rect (through the WINDOWPOS structure from the WM_WINDOWPOSCHANGING message), and then tells all the adjacent windows to move and lay out themselves (through the PlaceWindowAdjacentToRect
message). See the CAdjacentWindow::SiblingWindowPosChanging
Handling the WM_NEGOTIATE_WINDOW_PANE message
All adjacent windows must process this message. For this message, the wParam parameter is set to a subcode that additionally specifies what the message is. See the CAdjacentWindow::OnNegotiateWindow
function. This message is sent by adjacent windows to communicate with one another. The next set of sections detail the different subcodes and their operations.
This message is sent by a new adjacent window to add itself to the linked list of windows. lParam contains the window handle of the new adjacent window. See the CAdjacentWindow::FindTopMostInjectedPane
function for the process of sending this message. The function steps through all child windows of the parent of the sibling window, and then the function sends this message to each window, in turn, until it receives a nonzero result.
Non-controller windows respond to this message by returning 0. The controller responds to this message by setting its "prev" window to lParam (thereby making it not the controller) and by returning its window handle. The new adjacent window should use this as the "next" window (and the new window becomes the controller).
If there are no existing controller windows, the new adjacent window becomes the controller.
This message is sent to replace the pointer to the "prev" with the value of lParam. This is used by adjacent windows to maintain the linked list of windows when they are unloaded (See the CAdjacentWindow::OnDestroy
This message is sent to replace the pointer to the "next" window with the value of lParam.
This message is sent to a window to determine how much space to reserve for it. This message must be forwarded on to the "next" window, if present. lParam contains a RECT structure that is initialized to 0,0,0,0 by the first caller. Each adjacent window adds to this structure the volume of space that it requires. For example, an adjacent window to be positioned below the people pane would add its intended height to the value of bottom. The values of left, top, right, and bottom are not coordinates, but they are the total intended space (that is, none of these values should be negative).
This message is sent to an adjacent window when the adjacent window has to position itself. lParam contains a RECT structure that the original caller initializes to the client RECT of the sibling window. Each window positions itself (possibly through SetWindowPos
), adjusts the RECT structure so that the RECT now includes the adjacent window, and then forwards the message on to the "next" window. For example, an adjacent window to be positioned below the preview pane with height CY would position itself at (rc.left, rc.bottom, rc.right – rc.left, CY), and then adjust the RECT structure to (rc.left, rc.top, rc.right, rc.bottom + CY). When all adjacent windows have completed their layouts, the RECT structure should be identical to the client RECT of the parent window. The last assert in the CAdjacentWindow::SiblingWindowPosChanging
function verifies that this is the case.
This message is sent to ask the controller to do a full layout pass. Non-controller windows should forward this message up the linked list of adjacent windows. The controller (see the CAdjacentWindow::RecalcPreviewPaneLayoutController
function) determines the current available space (the client area of the parent window), the required space (by sending a GetReservedRect message), then resizes the sibling window, and then sends the PlaceWindowAdjacentToRect message to have all the adjacent windows do their layout.