Add Your Control On Top Another Application – Part 4 (Win32)

12/05/2010

Add Your Control On Top Another Application – Part 4 (Win32)c28d6731fe203d3f80e2774a151c3e8a[1]

And here we are, the last part in this series – How to set Window Event using SetWinEventHook.

In the previous posts we have found the Target window handle and his TitleBar position, we created a new Control(HoverControl) and placed him On Top the Target TitleBar, the only thing that left if to listen Target window events (Example: LocationChange) so we can move our HoverControl accordingly.

We going to use some more NativeMethods to complete this task, get ready…. Go!

Step 1: Add SetWinEventHook & UnhookWinEvent To NativeMethods Class

We need to use SetWinEventHook because this function allows clients to specify which processes and threads they are interested in.Clients can call SetWinEventHook multiple times if they want to register additional hook functions or listen for additional events.

[DllImport("user32.dll")]

internal static extern IntPtr SetWinEventHook(

    AccessibleEvents eventMin, //Specifies the event constant for the lowest event value in the range of events that are handled by the hook function. This parameter can be set to EVENT_MIN to indicate the lowest possible event value.

    AccessibleEvents eventMax, //Specifies the event constant for the highest event value in the range of events that are handled by the hook function. This parameter can be set to EVENT_MAX to indicate the highest possible event value.

    IntPtr eventHookAssemblyHandle, //Handle to the DLL that contains the hook function at lpfnWinEventProc, if the WINEVENT_INCONTEXT flag is specified in the dwFlags parameter. If the hook function is not located in a DLL, or if the WINEVENT_OUTOFCONTEXT flag is specified, this parameter is NULL.

    WinEventProc eventHookHandle, //Pointer to the event hook function. For more information about this function

    uint processId, //Specifies the ID of the process from which the hook function receives events. Specify zero (0) to receive events from all processes on the current desktop.

    uint threadId,//Specifies the ID of the thread from which the hook function receives events. If this parameter is zero, the hook function is associated with all existing threads on the current desktop.

    SetWinEventHookParameter parameterFlags //Flag values that specify the location of the hook function and of the events to be skipped. The following flags are valid:

    );

Removes an event hook function created by a previous call to.

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool UnhookWinEvent(IntPtr eventHookHandle);

 

WinEventProc – Important

An application-defined callback (or hook) function that the system calls in response to events generated by an accessible object. The hook function processes the event notifications as required. Clients install the hook function and request specific types of event notifications by calling SetWinEventHook.

internal delegate void WinEventProc(IntPtr winEventHookHandle, AccessibleEvents accEvent, IntPtr windowHandle, int objectId, int childId, uint eventThreadId, uint eventTimeInMilliseconds);

 

[DllImport("user32.dll")]

internal static extern IntPtr SetFocus(IntPtr hWnd);

 

[Flags]

internal enum SetWinEventHookParameter

{

    WINEVENT_INCONTEXT = 4,

    WINEVENT_OUTOFCONTEXT = 0,

    WINEVENT_SKIPOWNPROCESS = 2,

    WINEVENT_SKIPOWNTHREAD = 1

}

Value Meaning
WINEVENT_INCONTEXT
The DLL that contains the callback function is mapped into the address space of the process that generates the event. With this flag, the system sends event notifications to the callback function as they occur. The hook function must be in a DLL when this flag is specified. This flag has no effect when both the calling process and the generating process are not 32-bit or 64-bit processes, or when the generating process is a console application. For more information, see In-Context Hook Functions.
WINEVENT_OUTOFCONTEXT
The callback function is not mapped into the address space of the process that generates the event. Because the hook function is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed to be in sequential order. For more information, see Out-of-Context Hook Functions.
WINEVENT_SKIPOWNPROCESS
Prevents this instance of the hook from receiving the events that are generated by threads in this process. This flag does not prevent threads from generating events.
WINEVENT_SKIPOWNTHREAD
Prevents this instance of the hook from receiving the events that are generated by the thread that is registering this hook

Step 2: Create SetControl & GetWindowPosition Methods

What you need to go in this step is just to extract the code from btn_get_pos_Click & btn_add_Click methods to external method, this will serve us later.

void SetControl(bool log)

{

    //Creates new instance of HoverControl

    if (OnTopControl != null)

        OnTopControl.Close();

    OnTopControl = new HoverControl();

    OnTopControl.Show();

    //Search for HoverControl handle

    IntPtr OnTopHandle = Helpers.Find(OnTopControl.Name, OnTopControl.Title);

 

    //Set the new location of the control (on top the titlebar)

    OnTopControl.Left = left;

    OnTopControl.Top = top;

 

    if(log)

        Log("Hover Control Added!");

 

    //Change target window to be parent of HoverControl.

    Helpers.SetWindowLong(OnTopHandle, Helpers.GWLParameter.GWL_HWNDPARENT, TargetWnd.ToInt32());

}

 

void GetWindowPosition(bool log)

{

    var pos = Helpers.GetWindowPosition(TargetWnd);

 

    left = pos.Left;

    right = pos.Right;

    bottom = pos.Bottom;

    top = pos.Top;

 

    if(log)

        Log(string.Format("Left:{0} , Top:{1} , Top:{2} , Top:{3}", left, top, right, bottom));

 

    //retrieves the last system error.

    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

}

Step 3: Add Events And Callbacks

First we need to define what types of events we want to listen too and than create a dictionary with AccessibleEvents and Specific CallBack (or generic to all events).

You can find more about WinEvents in –> http://msdn.microsoft.com/en-us/library/system.windows.forms.accessibleevents.aspx.

I’ve add event for LocationChanged and Destroy, LocationChanged will help me to find the position on the target window each time the user changes the position and Destory when the target window closed and we need to close our HoverControl.

private Dictionary<AccessibleEvents, NativeMethods.WinEventProc> InitializeWinEventToHandlerMap()

{

    Dictionary<AccessibleEvents, NativeMethods.WinEventProc> dictionary = new Dictionary<AccessibleEvents, NativeMethods.WinEventProc>();

    //You can add more events like ValueChanged – for more info please read –

    //http://msdn.microsoft.com/en-us/library/system.windows.forms.accessibleevents.aspx

    //dictionary.Add(AccessibleEvents.ValueChange, new NativeMethods.WinEventProc(this.ValueChangedCallback));

    dictionary.Add(AccessibleEvents.LocationChange, new NativeMethods.WinEventProc(this.LocationChangedCallback));

    dictionary.Add(AccessibleEvents.Destroy, new NativeMethods.WinEventProc(this.DestroyCallback));

 

    return dictionary;

}

 

private void DestroyCallback(IntPtr winEventHookHandle, AccessibleEvents accEvent, IntPtr windowHandle, int objectId, int childId, uint eventThreadId, uint eventTimeInMilliseconds)

{

    //Make sure AccessibleEvents equals to LocationChange and the current window is the Target Window.

    if (accEvent == AccessibleEvents.Destroy && windowHandle.ToInt32() == TargetWnd.ToInt32())

    {

        //Queues a method for execution. The method executes when a thread pool thread becomes available.

        ThreadPool.QueueUserWorkItem(new WaitCallback(this.DestroyHelper));

    }

}

 

private void DestroyHelper(object state)

{

    Execute ex = delegate()

    {

        //Removes an event hook function created by a previous call to

        NativeMethods.UnhookWinEvent(g_hook);

        //Close HoverControl window.

        OnTopControl.Close();

    };

    this.Dispatcher.Invoke(ex, null);

}

 

private void LocationChangedCallback(IntPtr winEventHookHandle, AccessibleEvents accEvent, IntPtr windowHandle, int objectId, int childId, uint eventThreadId, uint eventTimeInMilliseconds)

{

    //Make sure AccessibleEvents equals to LocationChange and the current window is the Target Window.

    if (accEvent == AccessibleEvents.LocationChange && windowHandle.ToInt32() == TargetWnd.ToInt32())

    {

        //Queues a method for execution. The method executes when a thread pool thread becomes available.

        ThreadPool.QueueUserWorkItem(new WaitCallback(this.LocationChangedHelper));

    }

}

 

private void LocationChangedHelper(object state)

{

    Execute ex = delegate()

    {

        if(OnTopControl!=null)

            OnTopControl.Close();

        GetWindowPosition(false);

        SetControl(false);

    };

    this.Dispatcher.Invoke(ex, null);

}

 

Step 4: Set WinEventHook

This is the last step and the most important one,you can set as many events as you like but make sure to use GCHandle garbage collector will not move the callback and you will get many errors.

IntPtr g_hook;

private void btn_set_event_Click(object sender, RoutedEventArgs e)

{

    Dictionary<AccessibleEvents, NativeMethods.WinEventProc> events = InitializeWinEventToHandlerMap();

 

    //initialize the first event to LocationChanged

    NativeMethods.WinEventProc eventHandler =

        new NativeMethods.WinEventProc(events[AccessibleEvents.LocationChange].Invoke);

 

    //When you use SetWinEventHook to set a callback in managed code, you should use the GCHandle

    //(Provides a way to access a managed object from unmanaged memory.) structure to avoid exceptions.

    //This tells the garbage collector not to move the callback.

    GCHandle gch = GCHandle.Alloc(eventHandler);

 

    //Set Window Event Hool on Location changed.

    g_hook = NativeMethods.SetWinEventHook(AccessibleEvents.LocationChange,

        AccessibleEvents.LocationChange, IntPtr.Zero, eventHandler

        , 0, 0, NativeMethods.SetWinEventHookParameter.WINEVENT_OUTOFCONTEXT);

 

    //Hook window close event – close our HoverContorl on Target window close.

    eventHandler = new NativeMethods.WinEventProc(events[AccessibleEvents.Destroy].Invoke);

 

    gch = GCHandle.Alloc(eventHandler);

 

    g_hook = NativeMethods.SetWinEventHook(AccessibleEvents.Destroy,

        AccessibleEvents.Destroy, IntPtr.Zero, eventHandler

        , 0, 0, NativeMethods.SetWinEventHookParameter.WINEVENT_OUTOFCONTEXT);

 

    //AccessibleEvents -> http://msdn.microsoft.com/en-us/library/system.windows.forms.accessibleevents.aspx

    //SetWinEventHookParameter -> http://msdn.microsoft.com/en-us/library/dd373640(VS.85).aspx

}

Enjoy.

Add comment
facebook linkedin twitter email

Leave a Reply

one comment

  1. Jecka15/02/2012 ב 08:49

    The decision is great
    But the code style is disgusting.
    Why the control is being created so many times?
    I had to delete some invokes of HoverControl.close to get the decision workable

    In all thank you a lot for the decision