I have a method which drawing Fractals
public static void DrawFractal(int x, int y, int len, double angle, PaintEventArgs e,Panel panel1)
{
Graphics g = e.Graphics;
double x1, y1;
x1 = x len * Math.Sin(angle * Math.PI * 2 / 360.0);
y1 = y len * Math.Cos(angle * Math.PI * 2 / 360.0);
g.DrawLine(new Pen(Color.Black), x, panel1.Height - y, (int)x1, panel1.Height - (int)y1);
if (len > 2)
{
DrawFractal((int)x1, (int)y1, (int)(len / 1.5), angle 30, e,panel1);
DrawFractal((int)x1, (int)y1, (int)(len / 1.5), angle - 15, e,panel1);
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
FractalTree.DrawFractal(panel1.Width / 2, panel1.Height / 2, 80, 0, e, panel1);
}
Now this method draws when the window is opened. I would like it to be drawn on button click.
private void button2_Click(object sender, EventArgs e)
{
}
I would also like to add the ability to stop rendering later. Therefore, I would be very happy if you could suggest the idea of working with handlers and events related to rendering.
[This is how it will look like][1]: https://i.stack.imgur.com/OGcC0.png [2]: https://i.stack.imgur.com/mQeHc.png
CodePudding user response:
The main issue that you have here is the cpu-bound operations. Executing this kind of lengthy-recursive routines in the UI thread freezes it until the completion of the execution. Hence clicking the Stop
button - as you mentioned - does nothing.
To keep the UI responsive, create Task
versions of your methods to run them in worker threads so they can be awaited to complete or canceled. I have here two examples to suggest.
Draw Bitmap
Draw the shapes on a Bitmap
then draw it in the Paint
event.
public class FractalTree
{
public static void DrawFractal(
Graphics g, Rectangle canvas, int x, int y, int len, double angle)
{
double x1 = x len * Math.Sin(angle * Math.PI * 2 / 360.0);
double y1 = y len * Math.Cos(angle * Math.PI * 2 / 360.0);
g.DrawLine(Pens.Black, x, canvas.Height - y, (int)x1, canvas.Height - (int)y1);
if (len > 2)
{
DrawFractal(g, canvas, (int)x1, (int)y1, (int)(len / 1.5), angle 30);
DrawFractal(g, canvas, (int)x1, (int)y1, (int)(len / 1.5), angle - 15);
//DrawFractal(g, canvas, (int)x1, (int)y1, (int)(len / 1.5), angle 15);
//DrawFractal(g, canvas, (int)x1, (int)y1, (int)(len / 1.5), angle - 15);
}
}
public static async Task DrawFractalAsync(
Graphics g, Rectangle canvas,
int x, int y, int len, double angle,
CancellationToken token)
{
await Task.Run(async () =>
{
double x1 = x len * Math.Sin(angle * Math.PI * 2 / 360.0);
double y1 = y len * Math.Cos(angle * Math.PI * 2 / 360.0);
g.DrawLine(Pens.Black, x, canvas.Height - y, (int)x1, canvas.Height - (int)y1);
if (len > 2)
{
await DrawFractalAsync(g, canvas,
(int)x1, (int)y1, (int)(len / 1.5), angle 30, token);
await DrawFractalAsync(g, canvas,
(int)x1, (int)y1, (int)(len / 1.5), angle - 15, token);
//await DrawFractalAsync(g, canvas,
//(int)x1, (int)y1, (int)(len / 1.5), angle 15, token);
//await DrawFractalAsync(g, canvas,
//(int)x1, (int)y1, (int)(len / 1.5), angle - 15, token);
}
}, token);
}
}
Implementation example...
public partial class YourForm : Form
{
private Bitmap bmp;
private CancellationTokenSource cts;
public YourForm()
{
InitializeComponent();
// Input controls...
cmbTemplates.SelectedIndexChanged = (s, e) => Draw();
nudLen.ValueChanged = (s, e) => Draw();
nudAngle.ValueChanged = (s, e) => Draw();
nudOffsetX.ValueChanged = (s, e) => Draw();
nudOffsetY.ValueChanged = (s, e) => Draw();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
bmp?.Dispose();
cts?.Cancel();
cts?.Dispose();
}
private void pnlCanvas_Paint(object sender, PaintEventArgs e)
{
if (bmp != null)
{
var r = pnlCanvas.ClientRectangle;
e.Graphics.DrawImage(bmp, r, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);
}
}
private void btnDraw_Click(object sender, EventArgs e) => Draw();
private void btnCancel_Click(object sender, EventArgs e)
{
cts?.Cancel();
bmp?.Dispose();
bmp = null;
pnlCanvas.Invalidate();
}
private async void Draw()
{
if (!btnDraw.Enabled) return;
btnDraw.Enabled = false;
var canvas = pnlCanvas.ClientRectangle;
try
{
using (var cancelTS = new CancellationTokenSource())
{
cancelTS.Token.ThrowIfCancellationRequested();
cts = cancelTS;
if (tcMain.SelectedTab == tpFractal)
{
bmp?.Dispose();
bmp = null;
bmp = new Bitmap(canvas.Width, canvas.Height);
switch (cmbTemplates.SelectedIndex)
{
case 0:
var len = (int)nudLen.Value;
var angle = (double)nudAngle.Value;
var xo = (int)nudOffsetX.Value;
var yo = (int)nudOffsetY.Value;
using (var g = Graphics.FromImage(bmp))
await FractalTree.DrawFractalAsync(g, canvas,
xo canvas.Width / 2, yo canvas.Height / 2,
len, angle, cts.Token);
break;
case 1:
// Call another FractalTree algorithm...
break;
}
}
else if (tcMain.SelectedTab == tpPyramid)
{
// Pyramid algorithms
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Canceld...!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
btnDraw.Enabled = true;
pnlCanvas.Invalidate();
cts = null;
}
}
}
Demo
Draw the Shape
Make the tasks calculate and return lists of structures that define the shapes and pass them to the relevant Graphics.Draw...
or Graphics.Fill...
methods.
public class FractalTree
{
public static void GetFractalPoints(Rectangle camvas,
int x, int y, int len, double angle, List<List<Point>> lines)
{
double x1 = x len * Math.Sin(angle * Math.PI * 2 / 360.0);
double y1 = y len * Math.Cos(angle * Math.PI * 2 / 360.0);
var pt1 = new Point(x, camvas.Height - y);
var pt2 = new Point((int)x1, camvas.Height - (int)y1);
lines.Add(new List<Point> { pt1, pt2 });
if (len > 2)
{
GetFractalPoints(camvas, (int)x1, (int)y1, (int)(len / 1.5), angle 30, lines);
GetFractalPoints(camvas, (int)x1, (int)y1, (int)(len / 1.5), angle - 15, lines);
//GetFractalPoints(camvas, (int)x1, (int)y1, (int)(len / 1.5), angle 15, lines);
//GetFractalPoints(camvas, (int)x1, (int)y1, (int)(len / 1.5), angle - 15, lines);
}
}
public static async Task GetFractalPointsAsync(
Rectangle camvas, int x, int y, int len, double angle,
List<List<Point>> lines,
CancellationToken token)
{
return await Task.Run(async () =>
{
double x1 = x len * Math.Sin(angle * Math.PI * 2 / 360.0);
double y1 = y len * Math.Cos(angle * Math.PI * 2 / 360.0);
var pt1 = new Point(x, camvas.Height - y);
var pt2 = new Point((int)x1, camvas.Height - (int)y1);
lines.Add(new List<Point> { pt1, pt2 });
if (len > 2)
{
await GetFractalPointsAsync(camvas, (int)x1, (int)y1,
(int)(len / 1.5), angle 30, lines, token);
await GetFractalPointsAsync(camvas, (int)x1, (int)y1,
(int)(len / 1.5), angle - 15, lines, token);
await GetFractalPointsAsync(camvas, (int)x1, (int)y1,
(int)(len / 1.5), angle 15, lines, token);
//await GetFractalPointsAsync(camvas, (int)x1, (int)y1,
// (int)(len / 1.5), angle - 15, lines, token);
}
}, token);
}
}
... and edit the implementation example as follows...
public partial class YourForm : Form
{
private CancellationTokenSource cts;
private readonly List<List<Point>> lines = new List<List<Point>>();
public YourForm()
{
InitializeComponent();
cmbTemplates.SelectedIndexChanged = (s, e) => Draw();
nudLen.ValueChanged = (s, e) => Draw();
nudAngle.ValueChanged = (s, e) => Draw();
nudOffsetX.ValueChanged = (s, e) => Draw();
nudOffsetY.ValueChanged = (s, e) => Draw();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
cts?.Cancel();
cts?.Dispose();
}
private void pnlCanvas_Paint(object sender, PaintEventArgs e)
{
foreach (var line in lines.Where(l => l.Count == 2).ToList())
e.Graphics.DrawCurve(Pens.Black, line.ToArray());
}
private void btnDraw_Click(object sender, EventArgs e) => Draw();
private void btnCancel_Click(object sender, EventArgs e)
{
cts?.Cancel();
lines.Clear();
pnlCanvas.Invalidate();
}
private async void Draw()
{
if (!btnDraw.Enabled) return;
btnDraw.Enabled = false;
lines.Clear();
var canvas = pnlCanvas.ClientRectangle;
try
{
using (var cancelTS = new CancellationTokenSource())
{
cancelTS.Token.ThrowIfCancellationRequested();
cts = cancelTS;
if (tcMain.SelectedTab == tpFractal)
{
switch (cmbTemplates.SelectedIndex)
{
case 0:
var len = (int)nudLen.Value;
var angle = (double)nudAngle.Value;
var xo = (int)nudOffsetX.Value;
var yo = (int)nudOffsetY.Value;
await FractalTree.GetFractalPointsAsync(canvas,
xo canvas.Width / 2, yo canvas.Height / 2,
len, angle, lines, cts.Token);
break;
case 1:
// Call another FractalTree algorithm...
break;
}
}
else if (tcMain.SelectedTab == tpPyramid)
{
// Pyramid algorithms
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Canceld...!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
btnDraw.Enabled = true;
pnlCanvas.Invalidate();
cts = null;
}
}
}
Which produces some nice and simple animation: