I've been drawing different fractals and I needed to make the fractal re-draw when I change the Value of some TrackBars.
In this case, the Value changes the fractal function recursion depth.
My problem is that after my fractal is done drawing it disappears.
I've tried tried to use:
pictureBox1.Invalidate();
DrawCloverFractal(...); //fractal drawing method
but I couldn't find a way to make it work properly.
I've also tried to use:
if (pictureBox1.InitialImage != null)
{
pictureBox1.InitialImage.Dispose();
pictureBox1.InitialImage = null;
pictureBox1.Invalidate();
}
but it didn't re-draw anything at all.
Complete code:
//Drawing method
int DrawCloverFractal(int x0, int y0, int r, int dir, int iter)
{
var g = pictureBox1.CreateGraphics();
var S = new SolidBrush(Color.Black);
g.FillEllipse(S, x0 - r, y0 - r, 2 * r, 2 * r);
if (iter == 0)
return 0;
int[] x = new int[4];
int[] y = new int[4];
int d = 3 * r / 2;
x[0] = x0 - d;
y[0] = y0;
x[1] = x0;
y[1] = y0 - d;
x[2] = x0 + d;
y[2] = y0;
x[3] = x0;
y[3] = y0 + d;
for (int i = 0; i < 4; i++)
{
if (i - dir == 2 || i - dir == -2)
continue;
DrawCloverFractal(x[i], y[i], r / 2, i, iter - 1);
}
return 0;
}
//Scroll method where I call DrawCloverFractal
private void RecursionLevel_Scroll(object sender, EventArgs e)
{
pictureBox1.Invalidate();
DrawCloverFractal(pictureBox1.Width / 2, pictureBox1.Height / 2, (int)(pictureBox1.Height * 0.17), 1, RecursionLevel.Value);
}
There are a few problems with the current code:
You're using Control.CreateGraphics().
A Graphics object created with this method is not permanent, it's recreated internally every time a Control needs to be repainted; an event that occurs quite often. If you draw on a Control's surface using this object, the drawing won't persists: the Graphics object that describes the Control's device context won't be the same.
See also:
Getting Started with Graphics Programming
How to: Create Graphics Objects for Drawing
Integer values are used to generate the Drawing.
Almost all Graphics measures should be expressed with floating point values. The rounding caused by integer division compromises the calculations. It doesn't matter whether the final Graphics function requires - sometimes - integer arguments: this is a final value, often used in this form because the internal methods - or the Graphics objects - don't support floating point measures (dealing with pixels position is one of the causes).
See for example this: Rotate Point around pivot Point repeatedly
In WinForms Graphics, when a drawing must persist on a Control's surface, the Paint event (or the OnPaint method override) provides the Graphics object used to perform the drawing.
In the example, the PaintEventArgs
of a PictureBox Paint event provides that Graphics object, the DrawCloverFractal()
method then uses it to draw a fractal.
The ValueChanged event of a TrackBar Control provides the level of recursion that the function uses to create nested generations.
The method that generates the fractal has been moved to a class object, which provides public methods to configure the fractal:
► The DrawCloverFractal()
method accepts a Graphics object as argument. This object is passed to method when a Paint
event of the Canvas is generated.
We can force a Control to redraw itself when we need it to, calling its Invalidate() method (which refreshes a Control in an asynchronous manner).
► The public DrawCloverFractal()
method then calls the internal DrawCloverFractalinternal()
, passing to the recursive method the parameter it needs, using the values assigned to the class properties and generating other values that will be constant in the current recursion, as the center of the Canvas.
► When dragging the TrackBar Control's knob, the ValueChanged event is raised. The event handler simply needs to set the class object's Iterations property to the current Value, then call pictureBox1.Invalidate()
to refresh the PictureBox used as Canvas, which in turns call the DrawCloverFractal()
method passing on a fresh Graphics object:
public partial class Form1 : Form
{
private MyFractal fractalDrawing = null;
public Form1()
{
InitializeComponent();
fractalDrawing = new MyFractal(this.pictureBox1);
}
private void trkRecursionLevel_ValueChanged(object sender, EventArgs e)
{
fractalDrawing.Iterations = (sender as TrackBar).Value;
pictureBox1.Invalidate();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (fractalDrawing == null) return;
fractalDrawing.DrawCloverFractal(e.Graphics);
}
}
A visual sample of the results:
The class object that contains all the drawing methods and logic:
public class MyFractal
{
public MyFractal() : this(null) { }
public MyFractal(Control canvas) => this.Canvas = canvas;
public Control Canvas { get; set; }
public Color Color { get; set; } = Color.LightGreen;
public int Direction { get; set; } = 1;
public int Iterations { get; set; } = 1;
public void DrawCloverFractal(Graphics g)
{
if (this.Canvas == null) return;
PointF center = new PointF(this.Canvas.Width / 2.0f, this.Canvas.Height / 2.0f);
float r = this.Canvas.Height * .17f;
DrawCloverFractalinternal(g, center, r, this.Direction, this.Iterations);
}
internal void DrawCloverFractalinternal(Graphics g, PointF center, float r, int dir, int iter)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new SolidBrush(this.Color)) {
g.FillEllipse(brush, center.X - r, center.Y - r, 2 * r, 2 * r);
}
if (iter == 0) return;
float[] x = new float[4];
float[] y = new float[4];
float d = 3 * r / 2;
x[0] = center.X - d;
y[0] = center.Y;
x[1] = center.X;
y[1] = center.Y - d;
x[2] = center.X + d;
y[2] = center.Y;
x[3] = center.X;
y[3] = center.Y + d;
for (int i = 0; i < 4; i++) {
if (i - dir == 2 || i - dir == -2) continue;
DrawCloverFractalinternal(g, new PointF(x[i], y[i]), r / 2, i, iter - 1);
}
}
}