Click here to Skip to main content
Click here to Skip to main content

Add Your Control On Top Another Application

, 22 May 2010
Rate this:
Please Sign up or sign in to vote.
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 wayBig Grin | :-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, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Shai Raiten
Architect Sela
Israel Israel
Shai Raiten is VS ALM MVP, currently working for Sela Group as a ALM senior consultant and trainer specializes in Microsoft technologies especially Team System and .NET technology. He is currently consulting in various enterprises in Israel, planning and analysis Load and performance problems using Team System, building Team System customizations and adjusts ALM processes for enterprises. Shai is known as one of the top Team System experts in Israel. He conducts lectures and workshops for developers\QA and enterprises who want to specialize in Team System.
 
My Blog: http://blogs.microsoft.co.il/blogs/shair/
Follow on   Twitter

Comments and Discussions

 
QuestionControlBehindWindowWithTransparentBackgroud PinmemberMember 104378321-Dec-13 11:17 
GeneralMy vote of 4 PinmemberMilan Jose5-Mar-13 1:54 
GeneralMy vote of 5 PinmemberSergio Andrés Gutiérrez Rojas15-Oct-12 13:18 
QuestionGreat! PinmemberGrimlock10-Aug-12 2:48 
GeneralMy vote of 5 PinmemberChristian Amado3-Aug-12 7:57 
QuestionPinning? PinmemberAlois Kraus21-Sep-11 23:23 
GeneralMy vote of 5 PinmemberBill SerGio, Infomercial King5-Jul-11 9:33 
GeneralPoor coding and your sample doesn't compile PinmemberTV Mogul2-Feb-11 1:37 
GeneralRe: Poor coding and your sample doesn't compile PinmemberShai Raiten2-Feb-11 2:56 
GeneralRe: Poor coding and your sample doesn't compile PinmemberTV Mogul2-Feb-11 4:33 
GeneralRe: Poor coding and your sample doesn't compile PinmemberShai Raiten2-Feb-11 4:53 
GeneralIn 2010? Why does it open in vs2008 ??? PinmemberTV Mogul2-Feb-11 5:20 
GeneralMy vote of 1 PinmemberTV Mogul2-Feb-11 1:35 
GeneralRe: My vote of 1 PinmemberShai Raiten2-Feb-11 2:50 
QuestionCan I fill data inside control Pinmembermk_rao24-May-10 21:15 
GeneralThanks Pinmembermartyn_mcfly22-May-10 21:51 
GeneralRe: Thanks PinmemberShai Raiten14-Jul-10 7:46 
GeneralDownload link not working Pinmemberlargenqcd18-May-10 15:26 
GeneralRe: Download link not working PinmemberShai Raiten22-May-10 19:10 
GeneralGreat!! Pinmemberburak29910-May-10 10:26 
GeneralRe: Great!! PinmemberShai Raiten12-May-10 1:50 
GeneralHi Shai PinmemberMember 473749310-May-10 6:06 
GeneralRe: Hi Shai PinmemberShai Raiten12-May-10 1:51 
GeneralAwesome Cool Fantabulous. PinmemberSom Shekhar10-May-10 5:43 
I love it. 5 from me.
GeneralRe: Awesome Cool Fantabulous. PinmemberShai Raiten27-Jul-10 17:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 23 May 2010
Article Copyright 2010 by Shai Raiten
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid