Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Hosting and Changing Controls in Other Applications

4.98/5 (34 votes)
28 Oct 2015MIT8 min read 51.6K   7.3K  
How to change the visual appearance of other processes by adding and changing controls in managed C#

Introduction

The main motivation of this article was the limitation of winforms to the own process. Wouldn't it be great to be able to change the appearance of other processes during their lifetime?

This won't work:

C#
Process[] processes = null;
processes = Process.GetProcessesByName("notepad");
Process Proc = processes[0];
Control notepad = Control.FromHandle(Proc.MainWindowHandle);

This will:

C#
using System.Windows

Win32Window control = Win32Window.FromProcessName("notepad");
            control.Children[0].Text = "HELLO WORLD";

Background

All windows you see on the desktop are managed by the operating system. Every button you see in those windows (at least in most applications) and every other control is just another window. Many things are only possible if you call procedures in Windows.h such as writing process memory or changing other windows.

This class is just that. A wrapper around many Pinvoke calls with some extra events and written in native C#.

It is also a way to create native WinAPI windows and buttons and listboxes and so on in managed C# as well as manipulating the appearance of other windows.

The main window is called Form in .NET. It uses a pointer of the type IntPtr called MainwindowHandle or HWND in C++ to identify a window. Every button and every textcontrol you see in old applications (which don't use custom buttons or other non standard GUI elements) are windows too.

Every window can be changed from any other application with the Windows API.

Of course, there are some security restrictions regarding process privileges but these do not affect most processes. Keep in mind that most of this is as old as WindowsNT (1993).

WindowsNT does use a structure called Message to enable user interaction with the computer. Every time you use the mouse or press a key, a message is generated. This message is put into the message queue of the currently active window.

For example: If you press a mouse button, a WM_LBUTTONDOWN message is sent to your application. If you release the mousebutton, a WM_LBUTTONUP is generated. If both messages were generated above a button, the button sends a WM_COMMAND message to its parent window.

Every application with a window does need some sort of messageloop (a place where mouse and keyboard events are handled):

C++
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
     TranslateMessage(&Msg);
     DispatchMessage(&Msg);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   //Do stuff here
}

In C++, this would be the main loop in a window application.

In C#, this loop is hidden to the programmer but you can still override it in your mainform:

C#
protected override void WndProc(ref Message m)
{
    //Do stuff here

    base.WndProc(ref m);
}

Now you can do some interesting stuff if you know the HWND or MainwindowHandle of a window:

  • Send your own messages to the window
  • Change the window style (Toolbox, no minimize button, no X button)
  • Change the title of the window
  • Change the text of the window
  • Change the state of the window (even for restricted apps like taskmgr)
  • Make it invisible

Using the Code

Adding Controls

This class is called Win32Window and was made to be as intuitive as a Windows Forms application (which is a wrapper around the Win32 API and more).

For example, you can grab a Win32Window from any existing process (which has to have a mainwindow) by calling:

C#
using System.Windows;

//inside main:
Win32Window.FromProcessName(name);

To see it in action, let's put an extra button into CMD:

C#
var window = Win32Window.FromProcessName("cmd");
            window.Title = "A CMD WINDOW";

            var Button= new Win32Button();
            Button.Pos_X=window.Width/2-Button.Width;
            Button.Pos_Y=80;
            Button.Text = "Press Me";
window.AddControl(Button);

Which gets us the result:

Image 1

Every button has the click event as one would expect. This can be used to add extra buttons to existing dialogue options or any other application.

This Class Win32Window has some derived classes:

  • Win32Button
  • Win32CheckBox
  • Win32Label
  • Win32ListBox
  • Win32TextBox

Each of these classes have some extra properties but can be used like a very minimal ultra backwards compatible control object.

Removing Controls

Now we look at how we can remove GUI elements from other applications. Let's have a look at Skype:

I do not know if this is globally the same but the desktop app on Windows 8.1 has advertisments at the top.

Image 2

Now we can use the WindowExplorer project attached to this article to identify the banner by some properties.

Image 3

We can clearly see that the ClassName is Shell Embedding. We could use as many known properties as we like to find our window.

To remove this from skype, just call:

C#
using System;
using System.Windows.Forms;
using System.Windows; // WINAPI and all Win32Controls/events are here

namespace System.Windows.Native
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            var window = Win32Window.FromWindowWhere(x =>x.ClassName == "Shell Embedding");
            window.Visible = false;
            //or --> window.Destroy();
        }
    }
}

Now you can remove advertisments in other applications with 2 lines of code.

I would not recommend destroying controls in other applications as that might make the other software unstable.
Setting the Visibility to false gives us similar results anyway.

The class name may be the same in other applications so I would recommend double checking or extending the predicate with && x.Process.Id == WantedProcess.Id.

Voila:

Image 4

What if the Window Doesn't Exist Yet?

When you want to keep the user from opening up the settings of his antivirus software (for whatever reason) or other windows, there is no HWND to grab because the window is not created yet. Luckily, windows does have a method called WindowsEventHook.

Do not confuse them with Window Hooks.

I have copied the proper Pinvoke calls from Pinvoke.net and put it in the helper class Win32WindowEvent.

There are many notifications which are very useful if you want to do something if another application is minimizing or you start drag and drop. Here are the event type descriptions:

The details are hidden for the next person to use it, so if you want to wait for a window which fulfills a predicate, just call:

C#
Win32WindowEvents.WaitForWindowWhere(x => x.Text=="I like pie");

void Win32WindowEvents_WindowFound(Process Process, Win32Window Window, 
                                   Win32WindowEvents.EventTypes type)
{
     Window.Text = "No you dont";
}

So now everytime a Notepad exists (or a button or a title) which is equal to "I like pie", the text is changed to "No you dont". This works as long as the application is running, for every application which is not running at a higher security level. If you want to see every WindowEvent for every application, just call:

C#
Win32WindowEvents.GlobalWindowEvent += Win32WindowEvents_GlobalWindowEvent;

void Win32WindowEvents_GlobalWindowEvent(Process Process, Win32Window Window, 
                                         Win32WindowEvents.EventTypes type)
{
   //do stuff here. Maybe called 1000x a second if a new application is started 
}

How It Works (New Window)

Every constructor of a new Win32Window is exactly the same code as you would need to create a window in native C++ with the Windows API. Of course, the class code is composed of almost 70% pinvoke declarations. The following code is used to generate any window (as told before, buttons and other controls are just windows with a special style set) This creates an IntPtr or Windowhandle (HWND).

C#
static IntPtr CreateWindow(string class_name, IntPtr hWndParent, 
       WndProc CallBack, WindowStyles Style, 
       WindowStylesEx ExStyle = WindowStylesEx.WS_EX_CLIENTEDGE)
{
    //checks if classname is a standard system class like BUTTON or EDIT
    IntPtr hWnd;
    if (class_name == null) throw new System.Exception("class_name is null");
    if (class_name == String.Empty) throw new System.Exception("class_name is empty");
    var n = typeof(Win32ControlType).GetFields(BindingFlags.Static | BindingFlags.Public);
    for (int i = 0; i < n.Length;i++)
    {
        if (n[i].GetValue(null).ToString() == class_name) { goto Create;}
    }

    WNDCLASS wc = new WNDCLASS();
    
    //If class is not there register as window
    wc.style = ClassStyles.HorizontalRedraw | ClassStyles.VerticalRedraw;
    wc.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate
                     (CallBack);
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = Process.GetCurrentProcess().Handle;
    wc.hIcon = LoadIcon(Process.GetCurrentProcess().Handle, "IDI_APPLICATION");
    wc.hCursor = LoadCursor(IntPtr.Zero, 32512);
    wc.hbrBackground = new IntPtr(6);
    wc.lpszMenuName = "Menu";
    wc.lpszClassName = class_name;

    UInt16 class_atom = RegisterClassW(ref wc);
    if (class_atom == 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
            
    // Create window

    Create:
    hWnd = CreateWindowExW(
        (uint)ExStyle,
        class_name,
        String.Empty,
        (uint)Style,
        0,
        0,
        0,
        0,
        hWndParent,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero
    );
    if (hWnd == IntPtr.Zero) { throw new Win32Exception(Marshal.GetLastWin32Error()); }

    return hWnd;
}

This is the same code which would be needed to create a window in C++. Notice that the messageloop is still missing. So if we want to create a Button:

C#
public Win32Button() : base(IntPtr.Zero)
{
    var Wait = new ManualResetEvent(false);
    new Thread(() =>
    {
        base.hWnd = WinAPI.CreateWindow(WinAPI.WindowTypes.Button, base.WindowProcedure);
        Wait.Set();
        base.MessageLoop();
        base.Destroy();
    }).Start();

    Wait.WaitOne();
}

Now you probably think why a new thread is created for every single button. You have to know that the messageloop is a blocking method until the window (button) is destroyed. There is one major drawback to this design. WM_COMMAND messages are sent to the parent window. We do not own the parent window, so there is no WM_BUTTONCLICK message we get in the messageloop. This is why the code for the real click event is generated by saving the last buttonclick and much more. For the real implementation, please see the demo project.

C#
public void MessageLoop()
{
    MSG msg;

    while (GetMessage(out msg, hWnd, 0, 0) != 0)
    {
        WndProc(this.hWnd, msg.message, msg.wParam, msg.lParam);
        TranslateMessage(ref msg);
        DispatchMessage(ref msg);
               
        switch((WinAPI.WM)msg.message)
        {
            //create all messages here (see in demo project)
        }
    }
}

Now we have created a window and have an unique HWND. The creation part is not needed for existing windows. The class constructor of Win32Window is really just the setter of the HWND property.

How It Works (Existing Window)

As mentioned before, any window has a unique HWND which is an IntPtr. If we want to change the Title of the window, we need to send a special Message to the window.

Every property is implemented somewhat differently but most of them use native WinApi calls. This is probably bad practise as properties normally should not have side effects, but this way you can call:

Mywindow.Title="MY TEXT HERE";

C#
public string Title
        {
            get
            {
                StringBuilder sb = new StringBuilder(GetWindowTextLength(hWnd) + 1);
                GetWindowText(hWnd, sb, sb.Capacity);
                return sb.ToString();
            }

            set
            {
                SetWindowText(hWnd, value);
                UpdateWindow(hWnd);
            }
        }

        public string Text
        {
            get
            {
                StringBuilder data = new StringBuilder(32768);
                SendMessage(hWnd, WM_GETTEXT, data.Capacity, data);
                return data.ToString();
            }
            set
            {
                SendMessage(hWnd, WM_SETTEXT, 0, value);
            }
        }

The Demo Project(s)

The demo project comes with an awesome window explorer. Here, you can explore what you can change in which window.

The second app is more a fun app to play with as you can have a "conversation" with any Notepad. Just type in any sentence with a dot at the end and the app will respond.

Source code of both are at the top.

Real World Scenarios

  • Automate certain GUI processes
  • Adding buttons to applications where the source code got lost or are very old
  • Make Windows invisible for silent printing
  • Open up secret formulars if the users open up any Notepad and enter a secret passphrase
  • Removal of the taskbar or unwanted advertisements in other applications

Things Left Undone

There are many things left undone in this version:

Modern Visual Styles

To enable your application to use visual styles, you must use ComCtl32.dll version 6 or later.
If you write your winforms app with C#, you can call Application.EnableVisualStyles(); When you use C++, you have to use a manifest for your application. If you use C# and create controls via Pinvoke, you only have Windows 2000 style controls. I haven't found a solution yet.

Background Color Property

Although it is possible to set many properties of other windows by calling the corresponding WinApi methods, I haven't found a way to change something as obvious as the background color.

Icon Change in Taskbar

Changing the small and the big icon is no problem, but it does not automatically change the icon visible in the taskbar.

If you have found a way to solve any of the above problems or have some questions, please leave a comment.

History

  • 9th November, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License