Outlook 2007: How to add a window that is adjacent to the preview pane in Office Outlook
By TechSupport
SUPPORT PROBLEM: How to add a window that is adjacent to the preview pane in Office Outlook
Applications Supported:
COPYRIGHT NOTICE: (c) 2007 Microsoft Corporation. All rights reserved.
SUPPORT SOLUTION:
This article describes how to use an add-in to add a window that is adjacent to the preview pane in Microsoft Office 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:http://code.msdn.microsoft.com/OlAdjacentWindows
(http://code.msdn.microsoft.com/OlAdjacentWindows/)
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..Definitions
Collapse this tableExpand this tableTop-level windowThe Outlook window handle that is parented to the desktop. This is either the frame that contains Explorer or the inspector.Sibling windowIn Explorer, this is the preview pane. In the inspector, this is the window that contains the client area of the inspector (the mail header, in addition to the body). The adjacent window is positioned next to the sibling window.Adjacent windowThe window that the add-in creates next to the preview pane or inspector.ExplorerThe main Outlook window that contains the navigation pane, the message list, and the preview pane, for example.InspectorAn Outlook window that displays one specific item such as mail or appointments.Legacy OutlookOutlook 2003 and Outlook 2007. In this article, these versions are called legacy because their HWND hierarchy differs from that of Outlook 2010..Prerequisites
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 download on the MSDN website that is provided at the beginning of the “More information” section. .Find all instances of Outlook
The 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 Explorer 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 downloads. 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.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 inspector 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 Explorer, this means that the user has elected not to show the preview pane. FindExplorerWindows and FindExplorerWindowsLegacyOutlook install 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 download), and then call the FindTopLevelWindows function again when either message is received. Because Outlook sends these OM 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 OM object that uses this window
The FMatchWindowToOMObject function takes a top-level window and tries to associate an OM object (either an explorer or an inspector) to this window. The function does this by iterating through all explorers and inspectors, by calling QueryInterface on the OM 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 download..Message hook handling
Both 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.Collapse this tableExpand this tableInstalled wndprocSaved off old wndprocAdd-in AWndProcAWndProcO (the original)Add-in BWndProcBWndProcAIn 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 download.) 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 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:Collapse this tableExpand this tableInstalled wndprocOld wndprocAdd-in BWndProcBWndProcO.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. To that end, the rest of this article describes how to cooperatively position windows in Explorers and Inspectors.We introduce 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 the one responsible for listening to the WM_WINDOWPOSCHANGING message of the sibling window (see the WndProcHookedWindow download). .Handling the WM_WINDOWPOSCHANGING message
We install 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 it 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 needs. Then it 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 layout themselves (through the PlaceWindowAdjacentToRect message). See the CAdjacentWindow::SiblingWindowPosChanging function. .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 operation. .AddCustomWindowToTop
This message is sent by a new adjacent window to add itself to the linked list of windows. lParam contains the HWND 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 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 (therefore making it not the controller), and returning its HWND. This the new adjacent window should use 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..ReplacePrevWindow
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 function.).ReplaceNextWindow
This message is sent to replace the pointer to the ‘next’ window with the value of lParam..GetReservedRect
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 how much space it needs. For example, an adjacent window to be positioned below the people pane would add its desired height to the value of bottom. The values of left, top, right, and bottom are not coordinates, but are the total space desired (that is, none of these values should be negative). .PlaceWindowAdjacentToRect
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 CAdjacentWindow::SiblingWindowPosChanging verifies that this is the case. .RecalcPaneLayout
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..
For File Repair and Data Recovery, visit File Repair / Data Recovery