System.Drawing.Graphics.FromHwnd drawing flickering

Viewed 159

I'm drawing a circle as a second cursor in another application using System.Drawing.Graphics.FromHwnd(IntPtr). The shape draws properly beneath my cursor, but it flickers. I'm familiar with double buffering but this code is in an "Input" class I made, and not in a form. This is in a game so I'm pretty sure this is happening because the canvas in the game is being redrawn every frame and painting over my circle shape.

Is there a way to:

  • Properly cause double buffering in this situation
  • Eliminate flicker by some other means

Thank you

Using C# and .NET 5 if that matters (also since I know there will be inevitable comments on my class structure, this was quick and dirty to demonstrate the issue - I know that some of what I did here is not best practice).

public class Input
{
    private ThreadTimer timer;
    private IntPtr hwnd;
    private Graphics? winGraphics = default;

    public Input() 
    {
        timer = new ThreadTimer();
        timer.Interval = 15;
        timer.AutoReset = true;
        timer.Elapsed += Timer_Elapsed;
    }

    private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        User32.POINT clientPt = new User32.POINT(Cursor.Position.X, Cursor.Position.Y);
        User32.ScreenToClient(hwnd, ref clientPt);

        var cursor = new Rectangle(clientPt, new Size(5, 5));
        winGraphics = Graphics.FromHwnd(hwnd);
        winGraphics.FillEllipse(new SolidBrush(Color.FromArgb(50, Color.Red)), new Rectangle(clientPt.X-15, clientPt.Y-15, 30, 30));
    }

    public void StartDrawMouse(IntPtr hwnd)
    {
        this.hwnd = hwnd;
        timer.Start();
    }

    public void StopDrawMouse()
    {
        this.hwnd = IntPtr.Zero;
        timer.Stop();
    }
}

ScreenToClient and its pinvoke data structure for POINT:

[DllImport("user32.dll")]
public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }

    public static implicit operator System.Drawing.Point(POINT p)
    {
        return new System.Drawing.Point(p.X, p.Y);
    }

    public static implicit operator POINT(System.Drawing.Point p)
    {
        return new POINT(p.X, p.Y);
    }
}
1 Answers

I think the problem is because when drawing in another applications device context, you're crossthreading their UI thread.

So you might want to try to execute your code on their thread. Windows uses a messageloop to handle certain events like keyboard/mouse. You could use a SetWindowsHook to attach your (native code) dll-library which draws the circle. Then you should check for something like a WM_PAINT message and append your draw routines behind it.

Related