Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Add Your Control On Top Another Application

0.00/5 (No votes)
22 May 2010 1  
How To Use Win32 To Add Your Control On Top Another Application Using Win32 Hooks.

Introduction

Couple of days ago, I got an email asked me to help with creating a button on top all open applications, this reminded me of the CodedUI Recorder.
As you can see from the picture, when using CodedUI Testing, you will see the “Currently Recording” notification on every active application you are recording.

How?

The design is to make the title bar window of the target application as the parent\owner window of the Control you want to add.
But this is not enough, we need to listen to many events like size change, style etc.. But I’ll get there later.

Here is what we need to do:

  1. Find window handle with FindWindow
  2. Get window position and title bar info using GetTitleBarInfo
  3. Set window as owner\parent using SetWindowLong
  4. SetWinEventHook for couple of events of the target application.

Using the Code

Step 1: Create Project

Create a WinForm or WPF project and add the following classes:

  • We will use this class to combine native methods so we can write our code more efficiently.
    public static class Helpers
  • Native methods collection.
    static class NativeMethods 

Step 2: Get Running Processes

Create new class called “PItem” and copy this code:

public class PItem
{
    public string ProcessName { get; set; }
    public string Title { get; set; }
 
    public PItem(string processname, string title)
    {
        this.ProcessName = processname;
        this.Title = title;
    }
 
    public override string ToString()
    {
        if (!string.IsNullOrEmpty(Title))
            return string.Format("{0} ({1})", this.ProcessName, this.Title);
        else
            return string.Format("{0}", this.ProcessName);
    }
}

Now, getting all active processes:

Process[] pro_list = e.Result as Process[];
foreach (Process pro in pro_list)
{
    try
    {
        //When using 64bit OS pro.MainModule.ModuleName will throw exception
        // for each 32bit, so Instead of ModuleName I've used ProcessName
        ProcessList.Items.Add(new PItem(pro.ProcessName, pro.MainWindowTitle));
    }
    catch (Exception)
    {
        //Security\ Permissions Issue
    }
}

Step 3: Add Find Native Methods

FindWindow function 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. Add below methods to NativeMethods.

using System.Runtime.InteropServices;

// Get a handle to an application window.
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
 
// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
internal static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName); 

Add the below code to Helpers. I’ve created a Find method to handle both FindWindow and FindWindowByCaption (much easier to search that way:-D)

public static IntPtr Find(string ModuleName, string MainWindowTitle)
{
    //Search the window using Module and Title
    IntPtr WndToFind = NativeMethods.FindWindow(ModuleName, MainWindowTitle);
    if (WndToFind.Equals(IntPtr.Zero))
    {
        if (!string.IsNullOrEmpty(MainWindowTitle))
        {
            //Search window using TItle only.
            WndToFind = NativeMethods.FindWindowByCaption(WndToFind, MainWindowTitle);
            if (WndToFind.Equals(IntPtr.Zero))
                return new IntPtr(0);
        }
    }
    return WndToFind;
}

Step 4: Find Window Handle From Process

Using PItem, we can use the ModuleName\Process Name and if the window exists, we can also use window title. Calling our Helpers class and using Find method with ProcessName and WindowTitle.

PItem pro = ProcessList.SelectedItem as PItem;
 
string ModuleName = pro.ProcessName;
string MainWindowTitle = pro.Title; ;
 
TargetWnd = Helpers.Find(ModuleName, MainWindowTitle);
 
if (!TargetWnd.Equals(IntPtr.Zero))
    Log(ModuleName + " Window: " + TargetWnd.ToString()); // We Found The Window
else
    Log(ModuleName + " Not found"); // No Window Found

Now I can assume that we have the window handle (If not, read Part 1). Now we need to get TitleBarInfo from the TargetWindow. Using that data, we can get the position of the window and more.

Step 5: Add GetTitleBarInfo and GetLastError

Using GettitleBarInfo will allow us to get information from the Target application.

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool GetTitleBarInfo(IntPtr hwnd, ref TITLEBARINFO pti);
 
//GetLastError- retrieves the last system error.
[DllImport("coredll.dll", SetLastError = true)]
internal static extern Int32 GetLastError();

Step 6: Add TitleBarInfo & RECT Properties

Before we can use GetTitleBarInfo, let’s add the following properties to the NativeMethods class.

[StructLayout(LayoutKind.Sequential)]
internal struct TITLEBARINFO
{
    public const int CCHILDREN_TITLEBAR = 5;
    public uint cbSize; //Specifies the size, in bytes, of the structure. 
    //The caller must set this to sizeof(TITLEBARINFO).
 
    public RECT rcTitleBar; //Pointer to a RECT structure that receives the 
    //coordinates of the title bar. These coordinates include all title-bar elements
    //except the window menu.
 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
 
    //Add reference for System.Windows.Forms
    public AccessibleStates[] rgstate;
    //0    The title bar itself.
    //1    Reserved.
    //2    Minimize button.
    //3    Maximize button.
    //4    Help button.
    //5    Close button.
}
 
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
    internal int left;
    internal int top;
    internal int right;
    internal int bottom;
}

Step 7: Add GetWindowPosition

Into the Helpers class, add GetWindowPosition (below), GetWindowPosition first will initialize TITLEBARINFO (Make sure you set the sbSize) than use GetTitleBarInfo to get the TitleBar position on the screen.

public static WinPosition GetWindowPosition(IntPtr wnd)
{
    NativeMethods.TITLEBARINFO pti = new NativeMethods.TITLEBARINFO();
    pti.cbSize = (uint)Marshal.SizeOf(pti);	//Specifies the size, in bytes, 
					//of the structure. 
    //The caller must set this to sizeof(TITLEBARINFO).
    bool result = NativeMethods.GetTitleBarInfo(wnd, ref pti);
 
    WinPosition winpos;
    if (result)
        winpos = new WinPosition(pti);
    else
        winpos = new WinPosition();
 
    return winpos;
}

Now let’s see how to use this information to add our own control on top of another application (See picture).

Step 8: Create HoverControl

The HoverControl is basically a control. I’ve created a new Windows control called HoverControl with the following attributes:

WindowStyle="None" AllowsTransparency="True" Background="{Binding Null}" 

This is important because we want the window to be transparent and without window borders. Below is the full XAML of the HoverControl.

<Window x:Class="Win32HooksDemo.HoverControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="HoverControl" Height="10" Width="150"
        WindowStyle="None" AllowsTransparency="True" Background="Transparent">
  <Grid>
    <Rectangle ToolTip="Click To Close" MouseDown="rectangle1_MouseDown"  
	Name="rectangle1"
         Stroke="#FFC7FF00" Fill="Red" RadiusY="10" RadiusX="10" StrokeThickness="2"/>
    <TextBlock FontSize="9" FontWeight="Bold" HorizontalAlignment="Center"
               Text="This is my Hover Control" />
  </Grid>
</Window> 

Mouse down event should close the HoverControl.

Step 9: Add SetWindowLong To NativeMethod Class

The SetWindowLongPtr function changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory. To write code that is compatible with both 32-bit and 64-bit versions of Windows, also add SetWindowLongPtr.
///The SetWindowLongPtr function changes an attribute of the specified window
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
internal static extern int SetWindowLong32
	(HandleRef hWnd, int nIndex, int dwNewLong);
 
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
internal static extern int SetWindowLong32
    (IntPtr windowHandle, Win32HooksDemo.Helpers.GWLParameter nIndex, int dwNewLong);
 
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
internal static extern IntPtr SetWindowLongPtr64
    (IntPtr windowHandle, Win32HooksDemo.Helpers.GWLParameter nIndex, IntPtr dwNewLong);
 
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
internal static extern IntPtr SetWindowLongPtr64
    (HandleRef hWnd, int nIndex, IntPtr dwNewLong);

Step 10: Add SetWindowLong In Helpers Class

The only attribute we need to change in this case is - GWL_HWNDPARENT, our agenda is to set target window as parent\owner of our HoverControl.

//Specifies the zero-based offset to the value to be set.
//Valid values are in the range zero through the number of bytes of extra window memory, 
//minus the size of an integer.
public enum GWLParameter
{
    GWL_EXSTYLE = -20, //Sets a new extended window style
    GWL_HINSTANCE = -6, //Sets a new application instance handle.
    GWL_HWNDPARENT = -8, //Set window handle as parent
    GWL_ID = -12, //Sets a new identifier of the window.
    GWL_STYLE = -16, // Set new window style
    GWL_USERDATA = -21, //Sets the user data associated with the window. 
                        //This data is intended for use by the application 
                        //that created the window. Its value is initially zero.
    GWL_WNDPROC = -4 //Sets a new address for the window procedure.
}

Instead of checking if this is 64bit or 32bit from our UI class, add SetWindowLong inside the helpers class.

public static int SetWindowLong(IntPtr windowHandle, GWLParameter nIndex, int dwNewLong)
{
    if (IntPtr.Size == 8) //Check if this window is 64bit
    {
        return (int)NativeMethods.SetWindowLongPtr64
		(windowHandle, nIndex, new IntPtr(dwNewLong));
    }
    return NativeMethods.SetWindowLong32(windowHandle, nIndex, dwNewLong);
}

Step 11: Add HoverControl On Top Target Application

First, we need to create new instance of our new HoverControl, set the position based on Target window TitleBar position (Part 2) and set the HoverControl as Child of Target window. Add button click event and let’s add the following code:

if (OnTopControl != null)
    OnTopControl.Close();
//Creates new instance of HoverControl
HoverControl 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;
 
//Change target window to be parent of HoverControl.
Helpers.SetWindowLong(OnTopHandle, Helpers.GWLParameter.GWL_HWNDPARENT, 
TargetWnd.ToInt32());
 
Log("Hover Control Added!");

And here we are, the last part in this series – How to set Window Event using SetWinEventHook. the only thing that is left is to listen to Target window events (Example: LocationChange) so we can move our HoverControl accordingly. We are going to use some more NativeMethods to complete this task.

Step 12: 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.

HoverControlEvent.gif

[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
} 

Step 13: 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 14: 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 here. 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 Destroy when the target window is 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 15: 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> <accessibleevents>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
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here