如何使用 TrackBar Value 重绘 PictureBox 以影响绘图

How to redraw a PictureBox using a TrackBar Value to affect the drawing

我一直在绘制不同的分形,当我更改某些 TrackBars 的值时需要重新绘制分形。
在这种情况下,值会改变分形函数递归深度。
我的问题是在我的分形绘制完成后它消失了。

我试过尝试使用:

pictureBox1.Invalidate();
DrawCloverFractal(...);    //fractal drawing method

但我找不到让它正常工作的方法。

我也试过使用:

if (pictureBox1.InitialImage != null)
{
    pictureBox1.InitialImage.Dispose();
    pictureBox1.InitialImage = null;
    pictureBox1.Invalidate(); 
}

但它根本没有重新绘制任何东西。

完整代码:

//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);
}

当前代码存在一些问题:

  1. 您正在使用 Control.CreateGraphics()
    使用此方法创建的 Graphics 对象不是永久性的,每次需要重新绘制 Control 时都会在内部重新创建它;经常发生的事件。如果您使用此对象在 Control 的表面上绘图,绘图将不会持久存在:描述 Control 的设备上下文的 Graphics 对象将不相同。

    另请参阅:
    Getting Started with Graphics Programming
    How to: Create Graphics Objects for Drawing

  2. 整数值用于生成绘图
    几乎所有图形度量都应该用浮点值表示。整数除法引起的舍入会影响计算。最终的 Graphics 函数是否需要 - 有时 - 整数参数并不重要:这是一个最终值,通常以这种形式使用,因为内部方法 - 或 Graphics 对象 - 不支持浮点测量(处理像素位置是原因之一)。

    例如:

在 WinForms Graphics 中,当绘图必须保留在 Control 的表面上时,Paint event (or the OnPaint 方法覆盖)提供用于执行绘图的 Graphics 对象。

在示例中,PictureBox Paint 事件的 PaintEventArgs 提供了该 Graphics 对象,然后 DrawCloverFractal() 方法使用它来绘制分形。

TrackBar 控件的 ValueChanged 事件提供函数用于创建嵌套生成的递归级别。

生成分形的方法已移至 class 对象,该对象提供 public 方法来 配置 分形:

  • 将提供绘图表面的控件(Canvas)
  • 绘图方向
  • 迭代次数
  • 画笔的颜色

DrawCloverFractal() 方法接受一个 Graphics 对象作为参数。当生成 Canvas 的 Paint 事件时,此对象将传递给方法。

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).

► public DrawCloverFractal() 方法然后调用内部 DrawCloverFractalinternal(),将它需要的参数传递给递归方法,使用分配给 class 属性的值,并生成在当前递归中保持不变的其他值,作为 Canvas 的中心。

► 拖动 TrackBar 控件的旋钮时,会引发 ValueChanged 事件。事件处理程序只需将 class 对象的 Iterations 属性 设置为当前值,然后调用 pictureBox1.Invalidate() 刷新用作 Canvas 的 PictureBox,后者又调用 DrawCloverFractal() 方法传递 fresh 图形对象:

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);
    }
}

结果的视觉示例:

包含所有绘制方法和逻辑的class对象:

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);
        }
    }
}