C# drawing on windows
See the question and my original answer on StackOverflowAs said in the comments, you cannot draw on the screen directly. What you can do is build some "overlay" window (transparent and click-through) and draw on it.
Here is a C# Console app sample that demonstrates that and also uses UI Automation that track opened windows and draw a yellow rectangle around them.
// needs a reference to UIAutomationClient, UIAutomationType and WindowsBase
static class Program
{
[STAThread]
static void Main()
{
var overlay = new Overlay();
// track windows open (requires WindowsBase, UIAutomationTypes, UIAutomationClient)
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Subtree, (s, e) =>
{
var element = (AutomationElement)s;
if (element.Current.ProcessId != Process.GetCurrentProcess().Id)
{
Console.WriteLine("Added window '" + element.Current.Name + "'");
overlay.AddTrackedWindow(element);
// track window close
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, element, TreeScope.Element, (s2, e2) =>
{
overlay.RemoveTrackedWindow(element);
});
}
});
Application.Run(overlay);
}
}
// adapted from https://stackoverflow.com/questions/11077236/transparent-window-layer-that-is-click-through-and-always-stays-on-top
public class Overlay : Form // standard Windows Form
{
private readonly HashSet<AutomationElement> _windows = new HashSet<AutomationElement>();
public Overlay()
{
TopMost = true;
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
BackColor = Color.White;
TransparencyKey = BackColor;
}
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
const int WS_EX_TRANSPARENT = 0x20;
const int WS_EX_LAYERED = 0x80000;
const int WS_EX_NOACTIVATE = 0x8000000;
cp.ExStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
foreach (var window in _windows.ToArray())
{
Rect rect;
try
{
rect = window.Current.BoundingRectangle;
}
catch
{
// error, window's gone
_windows.Remove(window);
continue;
}
// draw a yellow rectangle around window
using (var pen = new Pen(Color.Yellow, 2))
{
e.Graphics.DrawRectangle(pen, (float)rect.X, (float)rect.Y, (float)rect.Width, (float)rect.Height);
}
}
}
// ensure we call Invalidate on UI thread
private void InvokeInvalidate() => BeginInvoke((Action)(() => { Invalidate(); }));
public void RemoveTrackedWindow(AutomationElement element)
{
_windows.Remove(element);
InvokeInvalidate();
}
public void AddTrackedWindow(AutomationElement element)
{
_windows.Add(element);
InvokeInvalidate();
// follow target window position
Automation.AddAutomationPropertyChangedEventHandler(element, TreeScope.Element, (s, e) =>
{
InvokeInvalidate();
}, AutomationElement.BoundingRectangleProperty);
}
}
To test it, run it, and open a normal Win32 desktop app like notepad for example (not the new Windows 11 one) or explorer (this one works fine on Windows 11). This is what you should see: