none
Retrieve a window handle whitin outlook RRS feed

  • Question

  • Hi

    I'm working on a custom outlook addin where I need to subclass the appointment window. This works basically pretty well following the principles mentioned in the following ariticle:

    http://www.codeproject.com/Articles/27262/Additional-custom-panel-in-Microsoft-Outlook

    I'm struggeling by getting the handle to the social connector within the new appointment window. 

    I've tryied out a few approaches (FindWindow, FindWindowEx, EnumChildWindow) but none of them gave me the correct handle.

    Here you can find a small addin project to have a quick look at it. Download

    Or have a quick look at this abstract

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using Microsoft.Office.Interop.Outlook;
    using Office = Microsoft.Office.Core;
    
    namespace FindSocialConnector._2013
    {
        public partial class ThisAddIn
        {
            /// <summary>
            /// Delegate for the EnumChildWindows method
            /// </summary>
            /// <param name="hWnd">Window handle</param>
            /// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
            /// <returns>True to continue enumerating, false to bail.</returns>
            public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
    
            /// <summary>
            /// Hält den Klassennamen des NewAppointment Windows.
            /// </summary>
            public const string NewAppointmentWindowClass = "rctrl_renwnd32";
    
            private Inspectors _inspectors;
    
            /// <summary>
            /// The EnumChildWindows function enumerates the child windows that belong to the specified parent window by passing the handle to each child window, 
            /// in turn, to an application-defined callback function. EnumChildWindows continues until the last child window is enumerated or the callback function returns FALSE.
            /// </summary>
            /// <param name="parentHandle">
            /// Handle to the parent window whose child windows are to be enumerated.
            /// If this parameter is NULL, this function is equivalent to EnumWindows.
            /// Windows 95/98/Me: parentHandle cannot be NULL.
            /// </param>
            /// <param name="callbackFunction">
            /// Pointer to an application-defined callback function. For more information, see EnumChildProc.
            /// </param>
            /// <param name="param">
            /// Specifies an application-defined value to be passed to the callback function.
            /// </param>
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern int EnumChildWindows(IntPtr parentHandle, Delegate callbackFunction, IntPtr param);
    
            /// <summary>
            /// Retrieves a handle to the top-level window whose class name and window name match the specified strings. This function does not search child windows.
            /// This function does not perform a case-sensitive search. To search child windows, beginning with a specified child window, use the FindWindowEx function.
            /// <returns>
            /// <param name="className">
            /// The class name or a class atom created by a previous call to the RegisterClass or RegisterClassEx function.
            /// The atom must be in the low-order word of lpClassName; the high-order word must be zero.
            /// If lpClassName points to a string, it specifies the window class name.
            /// The class name can be any name registered with RegisterClass or RegisterClassEx, or any of the predefined control-class names.
            /// If lpClassName is NULL, it finds any window whose title matches the lpWindowName parameter.
            /// </param>
            /// <param name="windowName">The window name (the window's title). If this parameter is NULL, all window names match.</param>
            /// If the function succeeds, the return value is a handle to the window that has the specified class name and window name.
            /// If the function fails, the return value is NULL. To get extended error information, call GetLastError. 
            /// </returns>
            /// </summary>
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern IntPtr FindWindow(string className, string windowName);
    
            /// <summary>
            /// The FindWindowEx function retrieves a handle to a window whose class name and window name match the specified strings.
            /// The function searches child windows, beginning with the one following the specified child window.
            /// This function does not perform a case-sensitive search. 
            /// </summary>
            /// <param name="parentHandle">
            /// Handle to the parent window whose child windows are to be searched. 
            /// If hwndParent is NULL, the function uses the desktop window as the parent window.
            /// The function searches among windows that are child windows of the desktop. 
            /// </param>
            /// <param name="childAfter">
            /// Handle to a child window. The search begins with the next child window in the Z order.
            /// The child window must be a direct child window of hwndParent, not just a descendant window.
            /// If hwndChildAfter is NULL, the search begins with the first child window of hwndParent.
            /// </param>
            /// <param name="className">
            /// Pointer to a null-terminated string that specifies the class name or a class atom created by a previous call to the RegisterClass or RegisterClassEx function.
            /// The atom must be placed in the low-order word of lpszClass; the high-order word must be zero.
            /// </param>
            /// <param name="windowTitle">
            /// Pointer to a null-terminated string that specifies the window name (the window's title).
            /// If this parameter is NULL, all window names match.
            /// </param>
            /// <returns>
            /// If the function succeeds, the return value is a handle to the window that has the specified class and window names.
            /// If the function fails, the return value is NULL. To get extended error information, call GetLastError. 
            /// </returns>
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
    
            /// <summary>
            /// Liefert das Handle des NewAppointmentWindow's.
            /// </summary>
            private static IntPtr FindNewAppointmentWindowByCaption(string caption)
            {
                IntPtr newAppointmentWindowHandle = FindWindow(NewAppointmentWindowClass, caption);
                if (newAppointmentWindowHandle == IntPtr.Zero)
                {
                    throw new ArgumentException("NewAppointment Window wasn't found.");
                }
                return newAppointmentWindowHandle;
            }
    
            private void ThisAddIn_Startup(object sender, EventArgs e)
            {
                _inspectors = Application.Inspectors;
                _inspectors.NewInspector += Inspectors_NewInspector;
            }
    
            private void Inspectors_NewInspector(Inspector inspector)
            {
                AppointmentItem appointmentItem = inspector.CurrentItem as AppointmentItem;
                if (appointmentItem != null)
                {
                    IntPtr appointmentWindow = FindNewAppointmentWindowByCaption(inspector.Caption);
    
                    // Get all child windows based on the new appointment window
                    // Unfortunately the social connector handle isn't returned
                    GetChildWindows(appointmentWindow);
    
                    // Directly get the social connector based on the class name
                    // Unfortunately this returns 0
                    IntPtr socialConnectorWindow = FindWindowEx(appointmentWindow, IntPtr.Zero, "MerenguePane", null);
                }
            }
    
            /// <summary>
            /// Callback method to be used when enumerating windows.
            /// </summary>
            /// <param name="handle">Handle of the next window</param>
            /// <param name="pointer">Pointer to a GCHandle that holds a reference to the list to fill</param>
            /// <returns>True to continue the enumeration, false to bail</returns>
            private static bool EnumWindow(IntPtr handle, IntPtr pointer)
            {
                GCHandle gch = GCHandle.FromIntPtr(pointer);
                List<IntPtr> list = gch.Target as List<IntPtr>;
                if (list == null)
                {
                    throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
                }
                Debug.WriteLine("Current Window Handle: {0}, Hex: {1}", handle, handle.ToString("X"));
                list.Add(handle);
    
                // You can modify this to check to see if you want to cancel the operation, then return a null here
                return true;
            }
    
            /// <summary>
            /// Returns a list of child windows
            /// </summary>
            /// <param name="parent">Parent of the windows to return</param>
            /// <returns>List of child windows</returns>
            public static List<IntPtr> GetChildWindows(IntPtr parent)
            {
                List<IntPtr> result = new List<IntPtr>();
                GCHandle listHandle = GCHandle.Alloc(result);
                try
                {
                    EnumWindowProc childProc = EnumWindow;
                    EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
                }
                finally
                {
                    if (listHandle.IsAllocated)
                    {
                        listHandle.Free();
                    }
                }
                return result;
            }
    
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InternalStartup()
            {
                Startup += ThisAddIn_Startup;
            }
        }
    }

    Btw. the social connector is only displayed if you directly create a meeting request e.g. go to the calendar, right click --> New meeting request

    Any thoughts on this?

    Regards, Lukas


    Monday, August 18, 2014 7:13 AM

All replies

  • Hello Lukas,

    You may find the Creating Adjacent Windows In Outlook sample which adds an adjacent window to the preview pane and inspectors in Outlook. This page describes the method in which an add-in can add an adjacent window to the preview pane in Outlook. It is intended to work for Microsoft Office Outlook 2003, 2007 and 2010.

    BTW Why do you need to subclass windows in Outlook?

    Be aware, you may use Outlook Form Regions instead, for example:

     

    You can read more about this in the Creating Outlook Form Regions section in MSDN.

    Monday, August 18, 2014 9:14 AM
  • Hi Eugene

    Thank you for your reply.
    The point is that I had to develop an addin which should work in Outlook 2003-2013. Therefore form regions weren't possible to use. (I think they exist since outlook 2010).

    As you can see on the screenshot i place this sidebar within the appointment window. If the Button "Buchung tätigen" is clicked i expand the bar to use the full space available within the appointment window. The resize on the "form window" works well, I think I just should resize/reposition the social connector. But I still was not able to get that handle programmatically :-(

    I really stuck here... it's kind of weird I really don't understand why the social connector handle isn't found. Even with some recursive calls I was not able to get the correct handle? What magic is here happening?

    Regards,

    Lukas

    Monday, August 18, 2014 9:35 AM
  • Outlook Form Regions were introduced with Outlook 2007, not 2010.

    You may consider using commercial products like Add-in Express. They support Outlook 2003. See Outlook form regions based on Add-in Express .


    Monday, August 18, 2014 9:49 AM
  • Unfortunately that's not an option. I'm also very interested in the technical detail why I'm not able to retrieve that handle
    Monday, August 18, 2014 9:59 AM
  • I don't think that it is supported scenario. You can use Windows API functions on your own risk, nobody can give any guarantees.

    Monday, August 18, 2014 11:25 AM
  • I'm not searching for garanties. I am aware of that point. Eventhough it's not supported that doesn't mean it doesn't work :-) As I already said, i really want to understand why I can't get that handle even if it's only for my technical education.
    Monday, August 18, 2014 1:46 PM
  • Does it work when you don't put the the code in NewInspector event? Or did you try to create a common windows form applicaion to get the child window successfully?
    Tuesday, August 19, 2014 2:13 AM
  • Hi QuickStar,

    The caption of MerenguePane's parent window is "Untitled - Meeting  " like figure below:

    But as far as I test, the caption in code is "Appointment", I guess that is the reasone why we couldn't find the subwindow. To get more effective response and learn more about Windows development, I suggest that you reopen a new thread in General Windows Desktop Development Issues.

    Best regards

    Fei


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, August 19, 2014 3:22 AM
    Moderator
  • Hi Fei

    Thank you for your response.
    I did recognize this as well. But it seem to be the correct handle because i compared the handles retrieved by code with the ones Spy++ was able to get.

    I will reopen the the case because you're right it's more windows development question i guess.


    Tuesday, August 19, 2014 6:15 AM