我正在开发一个项目,我需要使用递归在C#中的Windows窗体应用程序中绘制希尔伯特曲线分形。我必须使用GDI +图形,但我是GDI +图形的新手。下面是我实际绘制曲线的Form类的完整代码。在这篇文章的最后,我已经包含了证明我的错误输出和预期输出的照片。
@Override
public int getCount() {
return 1;
}
函数应该将当前[x,y]坐标的下一个线段绘制到新的[x,y]坐标,这些坐标是通过添加DrawRelative()
和{来计算的。传递到xDistance
函数的{1}}值到yDistance
和DrawRelative()
类属性。
xCurrent
第一张照片(下图)是希尔伯特曲线函数输出错误,给出MaxDepth为1。
第二张照片(下方)表示我应该从这组函数中获取的内容(给定MaxDepth值为1)。
因为看起来递归算法编码正确,我怀疑我没有以正确的方式使用GDI +图形,或者我的类属性在递归调用中的某处更新/设置不正确。我该怎么做才能修复绘图算法?提前谢谢。
答案 0 :(得分:2)
说实话,我最初并不了解你为Hilbert曲线生成点的实现。我熟悉几种不同的方法,两种方法都不一样。
但是,这是一个完全不同的问题。您手头的主要问题实际上就是您不了解Winforms中的绘图机制是如何工作的。简而言之:有一个Paint
事件,您的代码应该通过绘制需要绘制的内容来处理。订阅Paint
事件并不会导致发生任何事情;它只是一种注册方式,可以在绘图时发出通知。
通常,人们可以使用Designer订阅活动,方法是导航到"事件" Designer中对象的“属性”窗格的选项卡(例如,您的表单)并选择适当的事件处理程序(或双击事件旁边的空框以使Designer自动插入空的处理程序以供您填写) 。您还可以在处理自己对象中的Paint
事件时,只需覆盖OnPaint()
方法。
在任何一种情况下,正确的技术是建立绘图的先决条件,然后调用Invalidate()
导致框架然后引发Paint
事件,此时你可以实际绘制你想要的画画。
请注意,在评论者TaW和我之间,我们提出了两种不同的绘图方法:我建议预先计算绘制所需的所有数据,然后在引发Paint
事件时绘制; TaW建议从Paint
事件处理程序调用递归方法,并在遍历递归算法时直接绘制。
这两种技术都很好,但当然也有优点和缺点,主要与经典的时间和空间权衡有关。使用前一种技术,当曲线的参数发生变化时,生成曲线的成本仅产生一次。绘图发生得更快,因为所有代码都要绘制预先存在的数据。使用后一种技术,不需要存储数据,因为生成的每个新点都是立即使用的,但当然这意味着每次重绘窗口时都必须重新生成所有点。
对于这个特殊应用,在实践中我并不认为这很重要。在典型的屏幕分辨率下,在开始达到要绘制的点的数据存储限制之前,您很快就无法确定曲线的特征。类似地,算法的执行速度非常快,以至于每次需要重绘窗口时重新计算点都没有坏处。请记住,这些是您可能需要在其他情况下更密切判断的权衡。
那么,那是什么意思呢?好吧,当我将它转换为正确使用Graphics
类的东西时,我无法得到你的实现来绘制希尔伯特曲线,所以我改变了部分代码以使用我知道可行的实现。您可以在此处找到有关此特定实现如何工作的详细讨论:Hilbert Curve
Concepts & Implementation
下面,我提供了两个不同版本的特定希尔伯特曲线实现,第一个使用"保留"方法(即生成数据,然后绘制数据),第二个使用"立即"方法(即每当你想要绘制窗口时生成数据,如图所示):
<强>&#34;保留&#34;方法强>
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private PointF[] _points;
private void FractalDisplay_Load(object sender, EventArgs e)
{
Redraw();
}
private void Redraw()
{
List<PointF> points = new List<PointF>();
GenerateHilbert(0, 0, 1, 0, 0, 1, (int)numericUpDown1.Value, points);
_points = points.ToArray();
Invalidate();
}
private void GenerateHilbert(PointF origin, float xi, float xj, float yi, float yj, int depth, List<PointF> points)
{
if (depth <= 0)
{
PointF current = origin + new SizeF((xi + yi) / 2, (xj + yj) / 2);
points.Add(current);
}
else
{
GenerateHilbert(origin, yi / 2, yj / 2, xi / 2, xj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2, xj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2 + yi / 2, xj / 2 + yj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2 + yi, xj / 2 + yj), -yi / 2, -yj / 2, -xi / 2, -xj / 2, depth - 1, points);
}
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (_points != null)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
e.Graphics.DrawLines(pen, _points);
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
<强>&#34;立即&#34;方法强>
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private void Redraw()
{
Invalidate();
}
private PointF GenerateHilbert(PointF origin, float xi, float xj, float yi, float yj, int depth,
PointF? previous, Graphics graphics, Pen pen)
{
if (depth <= 0)
{
PointF current = origin + new SizeF((xi + yi) / 2, (xj + yj) / 2);
if (previous != null)
{
graphics.DrawLine(pen, previous.Value, current);
}
return current;
}
else
{
previous = GenerateHilbert(origin, yi / 2, yj / 2, xi / 2, xj / 2, depth - 1, previous, graphics, pen);
previous = GenerateHilbert(origin + new SizeF(xi / 2, xj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, previous, graphics, pen);
previous = GenerateHilbert(origin + new SizeF(xi / 2 + yi / 2, xj / 2 + yj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, previous, graphics, pen);
return GenerateHilbert(origin + new SizeF(xi / 2 + yi, xj / 2 + yj), -yi / 2, -yj / 2, -xi / 2, -xj / 2, depth - 1, previous, graphics, pen);
}
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
GenerateHilbert(new PointF(), 1, 0, 0, 1, (int)numericUpDown1.Value, null, e.Graphics, pen);
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
在这两个例子中,我做了一些其他的修改,这些修改并非为了说明技术而严格需要,但仍然有用:
PointF
值传递。这简化了值的重用并为X和Y值添加了新的偏移量。Form
是自包含的,NumericUpDownControl
确定递归深度。我没有包含这个控件的实例化;我假设您可以在Designer中自己添加适当的控件,以进行上述编译。
的附录:强>
我有机会查看您尝试实施的算法在互联网上的其他示例。现在我了解算法的基本机制是什么,我能够修复你的版本以便它可以工作(主要的问题是你使用实例字段来存储算法的增量,但也使用相同的字段来初始化算法,因此一旦算法运行一次,后续执行就不起作用了。所以为了完整起见,这里有第二个&#34;保留&#34;代码的版本,使用您首选的算法而不是我上面使用的算法:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private PointF _previousPoint;
private PointF[] _points;
private void FractalDisplay_Load(object sender, EventArgs e)
{
Redraw();
}
private void Redraw()
{
List<PointF> points = new List<PointF>();
// Start here, to provide a bit of margin within the client area of the window
_previousPoint = new PointF(0.025f, 0.025f);
points.Add(_previousPoint);
int depth = (int)numericUpDown1.Value;
float gridCellCount = (float)(Math.Pow(2, depth) - 1);
// Use only 95% of the available space in the client area. Scale
// the delta for drawing to fill that 95% width/height exactly,
// according to the number of grid cells the given depth will
// produce in each direction.
GenerateHilbert3(depth, 0, 0.95f / gridCellCount, points);
_points = points.ToArray();
Invalidate();
}
private void GenerateHilbert(int depth, float xDistance, float yDistance, List<PointF> points)
{
if (depth < 1)
{
return;
}
GenerateHilbert(depth - 1, yDistance, xDistance, points);
DrawRelative(xDistance, yDistance, points);
GenerateHilbert(depth - 1, xDistance, yDistance, points);
DrawRelative(yDistance, xDistance, points);
GenerateHilbert(depth - 1, xDistance, yDistance, points);
DrawRelative(-xDistance, -yDistance, points);
GenerateHilbert(depth - 1, -yDistance, -xDistance, points);
}
private void DrawRelative(float xDistance, float yDistance, List<PointF> points)
{
// Discover where the new X and Y points will be
PointF currentPoint = _previousPoint + new SizeF(xDistance, yDistance);
// Paint from the current position of X and Y to the new positions of X and Y
points.Add(currentPoint);
// Update the Current Location of X and Y
_previousPoint = currentPoint;
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (_points != null)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
e.Graphics.DrawLines(pen, _points);
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
和以前一样,我稍微修改了你的实现,以便缩放图形以适应所有深度的窗口。这涉及绘制到单位正方形,然后根据窗口大小适当地设置变换。
除了修复Graphics
的基本用法以及xLength
和yLength
字段的问题之外,我还修复了代码中的一个小错误(您在其中复制了一个错误)水平太深了)并稍微清理了递归(没有必要重复深度检查......只需在递归方法的开头做一次)。
当然可以在&#34;立即&#34;中实现这一点。风格也是如此。我想在这个新的代码示例和&#34; immediate&#34;上面的方法示例,我可以把这个练习留给读者。 :)
答案 1 :(得分:0)
这是我听到@Peter Duniho的建议后想出的分形生成器 - 显示的代码不包括实际获取用户请求的递归深度级别(maxDepth)的形式。
public partial class HilbertDisplay : Form
{
private int maxDepth;
private int xCurrent = 0;
private int yCurrent = 0;
private int xNew = 0;
private int yNew = 0;
public HilbertDisplay(int depthEntered)
{
InitializeComponent();
maxDepth = depthEntered;
}
private void HilbertDisplay_Load(object sender, EventArgs e)
{
this.DoubleBuffered = true;
this.Update();
}
// Perform the Drawing
private void HilbertDisplay_Paint(object sender, PaintEventArgs e)
{
// Run the Hilbert Curve Generator
// Use a line segment length of 10 for Y
GenerateHilbertCurve(maxDepth, 0, 10, e);
}
// The Recursive Hilbert Curve Generator
private void GenerateHilbertCurve(int depth, int xDistance, int yDistance, PaintEventArgs e)
{
if (depth < 1)
{
return;
}
else
{
GenerateHilbertCurve(depth - 1, yDistance, xDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(xDistance, yDistance);
e.Graphics.DrawLine(Pens.Red, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, xDistance, yDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(yDistance, xDistance);
e.Graphics.DrawLine(Pens.Blue, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, xDistance, yDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(-xDistance, -yDistance);
e.Graphics.DrawLine(Pens.Green, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, (-1 * yDistance), (-1 * xDistance), e);
}
}
private void FindPointRelative(int xDistance, int yDistance)
{
// Discover where the new X and Y points will be
xNew = xCurrent + xDistance;
yNew = yCurrent + yDistance;
return;
}
private void UpdateCurrentLocation()
{
// Update the Current Location of X and Y
xCurrent = xNew;
yCurrent = yNew;
return;
}
}
与@Peter Duniho不同,此代码不考虑表单的大小。这描绘了一个Hilbert曲线分形,在我的笔记本电脑上的递归深度为6或7(由于我的笔记本电脑屏幕尺寸/分辨率对窗口尺寸的限制)。
我知道我的解决方案并不像@Peter Duniho那样优雅,但由于这是一项任务,我不想简单地复制他的代码。我根据他的建议进行了编辑,特别是关于Paint
事件。