I am trying to draw some lines and rectangles on an existing window. I found the following code to draw on the desktop that works perfectly fine.
class Drawing {
[DLLImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hWnd);
public static void draw(Rectangle r, Brush b, IntPtr hWnd) {
using(Graphics g = Graphics.FromHdc(hwnd)) {
g.DrawRectangle(b, r);
}
}
}
Drawing.draw(new Rectangle(0, 0, 40, 80), Brushes.Red, Drawing.GetDC(IntPtr.Zero));
This is the code how I modified it to draw on a specific window.
class Drawing {
public static IntPtr WinGetHandle(string wName) {
foreach (Process pList in Process.GetProcesses())
if (pList.MainWindowTitle.Contains(wName))
return pList.MainWindowHandle;
return IntPtr.Zero;
}
public static void draw(Rectangle r, Brush b, IntPtr hwnd) {
using(Graphics g = Graphics.FromHwnd(hwnd)) {
g.DrawRectangle(p, r);
}
}
}
Drawing.draw(new Rectangle(0, 0, 40, 80), Brushes.Red, Drawing.WinGetHandle("DrawingWindow"));
With this code nothing happens and in some cases it throws OutOfMemoryException and crashes right after. In the Internet I found some other variants of getting the window handle and then draw on the window but most of the crash with the same exception or just do nothing. I also tried it in a loop because I thought the app may overdraw it every frame.
So my question is has anyone an idea how I could fix this or had the same problem and an other method do this.
As 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: