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:
Process[] processes = null;
processes = Process.GetProcessesByName("notepad");
Process Proc = processes[0];
Control notepad = Control.FromHandle(Proc.MainWindowHandle);
This will:
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):
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
}
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:
protected override void WndProc(ref Message m)
{
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:
using System.Windows;
Win32Window.FromProcessName(name);
To see it in action, let's put an extra button into CMD:
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:
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.
Now we can use the WindowExplorer
project attached to this article to identify the banner by some properties.
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:
using System;
using System.Windows.Forms;
using System.Windows;
namespace System.Windows.Native
{
static class Program
{
[STAThread]
static void Main()
{
var window = Win32Window.FromWindowWhere(x =>x.ClassName == "Shell Embedding");
window.Visible = false;
}
}
}
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:
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:
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:
Win32WindowEvents.GlobalWindowEvent += Win32WindowEvents_GlobalWindowEvent;
void Win32WindowEvents_GlobalWindowEvent(Process Process, Win32Window Window,
Win32WindowEvents.EventTypes type)
{
}
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
).
static IntPtr CreateWindow(string class_name, IntPtr hWndParent,
WndProc CallBack, WindowStyles Style,
WindowStylesEx ExStyle = WindowStylesEx.WS_EX_CLIENTEDGE)
{
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();
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:
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
:
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.
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)
{
}
}
}
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";
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