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.
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.
[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
(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
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:
DrawXXX
methods all have a FillXXX
method.GraphicsPath
, which also has both a DrawXXX
methods and a FillXXX
method.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..