0

I am using this code to draw a rectangle that will be re-draw again after 500ms at mouse position.

Everything is fine, the rectangle draw works perfectly, but with this code, the old rectangles that have already been draw on screen keep on screen forever, they don't be erased.

I need that the old rectangles to be erased after the new one is made. The new rectangle is made with while loop.

int i = 0;
while (i != 1)
{
    int x = Cursor.Position.X;
    int y = Cursor.Position.Y;
    Graphics g = Graphics.FromHwnd(IntPtr.Zero);
    Rectangle mouseNewRect = new Rectangle(new Point(x, y), new Size(30, 30));
    g.DrawRectangle(new Pen(Brushes.Chocolate), mouseNewRect);
    Wait(500);
}

EDIT: I need to erase the rectangles that are both inside and OUTSIDE the form. Ideas?

LarsTech
  • 80,625
  • 14
  • 153
  • 225
Gabriel Weiss
  • 21
  • 1
  • 7
  • 1
    Before you draw the new rectangle, draw the previous rectangle with a Pen with a background color brush. – Ton Plooij Sep 26 '18 at 19:43
  • 2
    Is this WinForms? Don't hard-code your rectangles and drawing, nor draw in your mouse handlers; draw in the `Paint` handler. Consult [How to draw shapes in WinForms](https://stackoverflow.com/questions/49991039/). – Dour High Arch Sep 26 '18 at 19:43
  • Please refer to this [question](https://stackoverflow.com/questions/4124638/how-to-delete-a-drawn-circle-in-c-sharp-windows-form). It shows how to remove a drawn graphic. – Adas Sep 26 '18 at 19:44
  • @TonPlooij EDIT: I need to erase the rectangles that are both inside and OUTSIDE the form. Ideas? – Gabriel Weiss Sep 26 '18 at 20:04
  • @DourHighArch EDIT: I need to erase the rectangles that are both inside and OUTSIDE the form. Ideas? – Gabriel Weiss Sep 26 '18 at 20:04
  • @Adas, EDIT: I need to erase the rectangles that are both inside and OUTSIDE the form. Ideas? – Gabriel Weiss Sep 26 '18 at 20:05

3 Answers3

0

You need to cause the window you are drawing to, to repaint itself between your iterations. An alternative solution would have you capture the window to an in-memory image; draw your rectangle on a copy of that image, then draw the image to the window. Repeating this process each time, so that the target window doesn't have artifacts from previous iterations.

Nate
  • 30,286
  • 23
  • 113
  • 184
  • EDIT: I need to erase the rectangles that are both inside and OUTSIDE the form. Ideas? – Gabriel Weiss Sep 26 '18 at 20:04
  • @GabrielWeiss you can't just 'erase' them because once they're drawn you don't know what's behind them so there's nothing to erase; you need to repaint/redraw the area behind the rectangle each time before you paint the next rectangle. – Nate Sep 26 '18 at 20:16
  • Nate, First i paint on screen the rectangle of Chocolate color... How can i take-off this chocolate rectangle of the screen, turn it invisible? – Gabriel Weiss Sep 26 '18 at 20:21
  • 1
    @GabrielWeiss Did you try what I proposed in my answer by using the P/Invoke method to redraw the entire screen..? – Emcrank Sep 26 '18 at 20:27
  • @Emcrank, i tried, but i am really new with c# and i cant make it working.. i add InvalidateRect(hWnd, IntPtr.Zero, true); and appear: hWnd dont exist in actual context. I also import: [DllImport("user32.dll")] static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase); – Gabriel Weiss Sep 26 '18 at 20:34
0

You could call the Invalidate() method which will request the region be redrawn by calling the paint method again.

See Control.Invalidate() on MSDN

In your scenario, something like this?

int i = 0;
while (i != 1)
{
    int x = Cursor.Position.X;
    int y = Cursor.Position.Y;
    Graphics g = Graphics.FromHwnd(IntPtr.Zero);
    Rectangle mouseNewRect = new Rectangle(new Point(x, y), new Size(30, 30));
    g.DrawRectangle(new Pen(Brushes.Chocolate), mouseNewRect);
    Wait(500);
    Invalidate();
}

As for refreshing the entire screen, you could use this p/invoke. https://www.pinvoke.net/default.aspx/user32.invalidaterect

[DllImport("user32.dll")]
static extern bool InvalidateRect(IntPtr hWnd, RECT lpRect, bool bErase);

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
   public int Left, Top, Right, Bottom;

   public RECT(int left, int top, int right, int bottom)
   {
     Left = left;
     Top = top;
     Right = right;
     Bottom = bottom;
   }

   public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }

   public int X
   {
     get { return Left; }
     set { Right -= (Left - value); Left = value; }
   }

   public int Y
   {
     get { return Top; }
     set { Bottom -= (Top - value); Top = value; }
   }

   public int Height
   {
     get { return Bottom - Top; }
     set { Bottom = value + Top; }
   }

   public int Width
   {
     get { return Right - Left; }
     set { Right = value + Left; }
   }

   public System.Drawing.Point Location
   {
     get { return new System.Drawing.Point(Left, Top); }
     set { X = value.X; Y = value.Y; }
   }

   public System.Drawing.Size Size
   {
     get { return new System.Drawing.Size(Width, Height); }
     set { Width = value.Width; Height = value.Height; }
   }

   public static implicit operator System.Drawing.Rectangle(RECT r)
   {
     return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
   }

   public static implicit operator RECT(System.Drawing.Rectangle r)
   {
     return new RECT(r);
   }

   public static bool operator ==(RECT r1, RECT r2)
   {
     return r1.Equals(r2);
   }

   public static bool operator !=(RECT r1, RECT r2)
   {
     return !r1.Equals(r2);
   }

   public bool Equals(RECT r)
   {
     return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
   }

   public override bool Equals(object obj)
   {
     if (obj is RECT)
       return Equals((RECT)obj);
     else if (obj is System.Drawing.Rectangle)
       return Equals(new RECT((System.Drawing.Rectangle)obj));
     return false;
   }

   public override int GetHashCode()
   {
     return ((System.Drawing.Rectangle)this).GetHashCode();
   }

   public override string ToString()
   {
     return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
   }
}

Then use it by calling

InvalidateRect(IntPtr.Zero, null, true);

Passing null and true as the second and third parameter should cause the entire screen to be redrawn.

Emcrank
  • 310
  • 2
  • 12
  • I used: this.Refresh(); this.Invalidate();, but the rectangles that i draw OUTSIDE the form are not erased. Ideas on how to erase rectangles that are out form1? – Gabriel Weiss Sep 26 '18 at 19:52
  • 1
    You shouldn't need to call both, Invalidate() requests that it is redrawn when it is next due and Refresh() will force it to be redrawn immediately. As for drawing outside the form I will have to have a think, been a while. – Emcrank Sep 26 '18 at 19:58
  • If you take a look at this - https://www.pinvoke.net/default.aspx/user32.invalidaterect, that looks like it will do what you are after. I don't think you will be able to do it in managed code outside of the bounds of the form. I have updated my answer to include this. – Emcrank Sep 26 '18 at 20:00
  • Error happened: Argument2: Its not possible convert "" for "IntPtr" – Gabriel Weiss Sep 26 '18 at 20:42
  • Edit: i think i could make it... InvalidateRect(IntPtr.Zero, IntPtr.Zero, true); – Gabriel Weiss Sep 26 '18 at 20:46
  • If you want to pass null, you will need to define the RECT structure. I've also added that to the answer. – Emcrank Sep 26 '18 at 20:58
  • Thanks for your Answer @Emcrank, helped a lot!! – Gabriel Weiss Sep 27 '18 at 18:34
0

The solution posted by @Emcrank works.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Load += async delegate
        {
            var g = Graphics.FromHwnd(IntPtr.Zero);
            var mouseNewRect = new Rectangle(Point.Empty, new Size(30, 30));
            var pen = new Pen(Brushes.Chocolate);
            while (true)
            {
                mouseNewRect.Location = Cursor.Position;
                g.DrawRectangle(pen, mouseNewRect);
                await Task.Delay(500);
                InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
            }
        };
    }

    [DllImport("user32.dll")]
    static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
}

Anyway, if you want to paint the mouse position inside the form, there are a much better solution.

public partial class Form1 : Form
{
    private Rectangle _rect = new Rectangle(0, 0, 30, 30);
    private readonly Pen _pen = new Pen(Brushes.Chocolate);
    public Form1()
    {
        InitializeComponent();
        SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        _rect.Location = e.Location;
        Invalidate(ClientRectangle);
        base.OnMouseMove(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //--improve graphics quality
        var g = e.Graphics;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.SmoothingMode = SmoothingMode.AntiAlias;

        _rect.Offset(-15, -15); //--center rect

        e.Graphics.DrawRectangle(_pen, _rect);

        base.OnPaint(e);
    }
}

I hope it helps.

denys-vega
  • 3,522
  • 1
  • 19
  • 24