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.|
PrerequisitesYour 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 OutlookThe FindTopLevelWindows 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 FindExplorerWindowsand FindExplorerWindowsLegacyOutlook 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 are hWndSibling (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 and FindExplorerWindowsLegacyOutlook 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 and Application.NewExplorer 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 windowThe FMatchWindowToOMObject 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 function.
Message hook handlingBoth message hooks that you install (WndProcHookedWindow and WndProcUnhookedWindow) 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 windowsWhen 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 function).
Handling the WM_WINDOWPOSCHANGING messageThe 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 function.
Handling the WM_NEGOTIATE_WINDOW_PANE messageAll 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.
AddCustomWindowToTopThis 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.