Search code examples
c#refreshrepaintflood-fill

Preserve painting after resize or refresh


How can I preserve the painting I drew on a picturebox?

I draw a circle, and fill it via the ExtFloodFill API. This works fine.

When I resize the form (or minimize it) and resize it back to its original size part of the painting is gone.

CompleteResizedMissing part

When I refresh the picturebox the painting will be gone completely

I tried to repaint it in the Paint event, but this caused it to be repainted continuously as the painting itself triggered the Paint event as well.

See below for a test project.

  • When you click on the picturebox the painting will be drawn.
  • When you doubleclick the picturebox will be refreshed.

[1 form with 1 picturebox named pictureBox1]

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace FloodFill
{
    public partial class Form1 : Form
    {
        [DllImport("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateSolidBrush(int crColor);
        [DllImport("gdi32.dll")]
        public static extern bool ExtFloodFill(IntPtr hdc, int nXStart, int nYStart, int crColor, uint fuFillType);
        [DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);
        [DllImport("gdi32.dll")]
        public static extern int GetPixel(IntPtr hdc, int x, int y);
        public static uint FLOODFILLSURFACE = 1;

        public Form1()
        {
            InitializeComponent();
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            DrawCircle();
            FillGreen();
        }

        private void DrawCircle()
        {
            Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
            graBox.DrawEllipse(Pens.Red, 10, 10, 100, 100);
        }

        private void FillGreen()
        {
            Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
            IntPtr ptrHdc = graBox.GetHdc();
            IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
            SelectObject(ptrHdc, ptrBrush);
            ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
            DeleteObject(ptrBrush);
            graBox.ReleaseHdc(ptrHdc);
        }

        private void pictureBox1_DoubleClick(object sender, EventArgs e)
        {
            pictureBox1.Refresh();
        }

    }
}

How can I preserve the painting I make when my form or the picturebox is resized or in any other way to be refreshed?

[EDIT]

I changed my Paint event to the following:

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        DrawCircle();
        FillGreen();
    }

And now the circle is being redrawn after a resized, but the FloodFill isn't

paint event

(I also gave the picturebox a lightblue background for another test)

[EDIT2]

I changed the Paint event to use the Graphics g as follows:

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        DrawCircle(g);
        FillGreen(g);
    }

    private void DrawCircle(Graphics g)
    {
        g.DrawEllipse(Pens.Red, 10, 10, 100, 100);
    }

    private void FillGreen(Graphics g)
    {
        IntPtr ptrHdc = g.GetHdc();
        IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
        SelectObject(ptrHdc, ptrBrush);
        ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
        DeleteObject(ptrBrush);
        g.ReleaseHdc(ptrHdc);
    }

But when I resize back to the original size some lines of the FloodFill are skipped, especially when I resize slowly

graphics missing lines


Solution

  • Using GDI+ methods for drawing the code is simply:

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        Rectangle rect = new Rectangle(10, 10, 100, 100);
        e.Graphics.FillEllipse(Brushes.Green, rect);
        using (Pen pen = new Pen(Color.Red, 2f))
        {
          e.Graphics.DrawEllipse(pen , rect);
        }
    }
    

    None of your DllImport or constants is needed at all.

    Note that I chose to use a Pen with a width of 2f to demonstrate the use of the using clause to correctly create and dispose of the GDI+ (non-standard) object Pen

    This will persist as it is drawn whenever needed. To draw it initially you must call pictureBox1.Invalidate(); once!

    If you want to change the coordinates you should move the variable rect to class level, set it to new data and the call Invalidate on the PictureBox! The same goes for Colors or the Pen.Width: use class level variables and after each change trigger the Paint event by calling Invalidate()..

    Once you understand that, the most important part of learning drawing in GDI+ is done..

    For filling any drawings you have three options:

    • simple shapes draw with DrawXXX methods all have a FillXXX method.
    • complex shapes can be created with a GraphicsPath, which also has both a DrawXXX methods and a FillXXX method.
    • areas not created by shapes but by drawn pixels must be filled with a floodfill method. There is non buit-in in GDI+, but can simply write your own. maybe like the Fill4 in this answer..

    Update: If you feel better using the FloodFill from gdi32.dll you can do so, but please change the code to use the Graphics object from the Paint event:

       FillGreen(e.Graphics);
    
    private void FillGreen(Graphics graBox)
    {
        IntPtr ptrHdc = graBox.GetHdc();
        IntPtr ptrBrush =  CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
        SelectObject(ptrHdc, ptrBrush);
        ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
        DeleteObject(ptrBrush);
        graBox.ReleaseHdc(ptrHdc);
    }
    

    for better flexibility you can probably make the params all dynamic, but I'm not used to that old library..

    Note that the order of calls matters: FillXXX wil cover some of the pixels drawn by DrawXXX, so it must come first. FloodFill however depends on the bounding lines, in your case the circle, being drawn first, so it must come after..