在C#中的指定圆圈内移动
Moving within a designated circle in C#
我正在尝试用 C# 创建一个简单的项目,但由于我是新手,遇到了很多麻烦。
我想做的是模拟一只苍蝇在一个圆圈内的运动。
它应该与鼠标点击一起使用 - 第一次点击,你选择了一个圆的中心。第二次单击,您选择了圆的半径并绘制了它。第三次单击(在圆圈内)时,您应该画一只苍蝇(现在让它成为一个小的、填充的椭圆),它应该立即开始四处移动。
我已经尝试了一个多星期,但几乎没有任何结果。
我正在使用面板作为 canvas。
这是我目前的情况:
private int start_x = 0;
private int start_y = 0;
private int end_x = 0;
private int end_y = 0;
private int fly_x = 0;
private int fly_y = 0;
private int clicks = 0;
private int distance = 0;
public Form1()
{
InitializeComponent();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
clicks++;
if (clicks == 1)
{
start_x = e.X;
start_y = e.Y;
}
else if (clicks == 2)
{
end_x = e.X;
end_y = e.Y;
}
else if (clicks == 3)
{
fly_x = e.X;
fly_y = e.Y;
}
else if (clicks == 4)
{
}
this.panel1.Refresh();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Random r = new Random();
Thread t = new Thread(new ThreadStart(Fly));
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
t.Start();
}
else if (clicks == 4)
{
t.Abort();
clicks = 0;
}
}
private int Distance(int a_x, int a_y, int b_x, int b_y)
{
int a, b;
a = a_x - b_x;
b = a_y - b_y;
return (Convert.ToInt32(Math.Sqrt(a * a + b * b)));
}
private void Fly()
{
Random r = new Random();
Graphics e = CreateGraphics();
distance = Distance(start_x, start_y, end_x, end_y);
while (clicks < 4)
{
e.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
fly_x = fly_x - 5 + r.Next(1, 11);
fly_y = fly_y - 5 + r.Next(1, 11);
Invalidate();
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
}
}
但是,显然,它根本不起作用。我可以画圆圈,但是一旦我再次点击面板,圆圈就会消失。你能指出我正确的方向吗?我的意思是,我做错了什么?
我试着在不使用线的情况下做到这一点,我什至画了苍蝇,但在那一点上,整个事情都冻结了。
编辑:
我删除了线程并放置了一个计时器:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Random r = new Random();
this.DoubleBuffered = true;
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
}
else if (clicks == 4)
{
clicks = 0;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
Random r = new Random();
fly_x = fly_x - 5 + r.Next(1, 11);
fly_y = fly_y - 5 + r.Next(1, 11);
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
panel1.Invalidate();
}
现在它可以工作了,但是图像仍然闪烁很多,即使我使用了双缓冲。我从我的同事那里得到了一个解决方案,不知何故,他并没有以任何方式闪烁。除了双缓冲区,还有其他方法可以解决这个问题吗?
在 Winforms 中避免闪烁并不难,但确实需要坚持。 Winforms 没有任何类型的合成引擎 — 它是非托管 Windows user32.dll API 之上的薄层,window 中的每个控件本身就是 window 是单独绘制的——因此每个可能闪烁的控件都需要进行双缓冲。
这是您的代码的一个版本,它不会闪烁:
public partial class Form1 : Form
{
private readonly Random r = new Random();
private int start_x = 0;
private int start_y = 0;
private int end_x = 0;
private int end_y = 0;
private int fly_x = 0;
private int fly_y = 0;
private int clicks = 0;
private int distance = 0;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
label1.Text = "clicks: 0";
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
clicks++;
if (clicks == 1)
{
start_x = e.X;
start_y = e.Y;
}
else if (clicks == 2)
{
end_x = e.X;
end_y = e.Y;
}
else if (clicks == 3)
{
fly_x = e.X;
fly_y = e.Y;
timer1.Start();
}
else if (clicks == 4)
{
timer1.Stop();
clicks = 0;
}
label1.Text = "clicks: " + clicks;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
}
}
private int Distance(int a_x, int a_y, int b_x, int b_y)
{
int a, b;
a = a_x - b_x;
b = a_y - b_y;
return (Convert.ToInt32(Math.Sqrt(a * a + b * b)));
}
private void timer1_Tick(object sender, EventArgs e)
{
fly_x = fly_x + r.Next(-4, 5);
fly_y = fly_y + r.Next(-4, 5);
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
Invalidate();
}
}
请注意,我没有在此处绘制到 Panel
对象,而只是使用了 Form1
对象本身。您可以将相同的基本技术应用于 Panel
或其他自定义控件来实现相同的基本结果,但这意味着创建您自己的 Panel
sub-class(或常规自定义控件,继承 Control
) 并将上述所有代码放入其中而不是表单中。 IE。您仍将在 OnMouseDown()
和 OnPaint()
方法上使用 override
而不是附加事件处理程序;您仍然可以使用设计器将 Timer
对象拖放到您的自定义 class.
关键是实际绘制的那个控件需要把DoubleBuffered
属性设为true
(默认是false
).由于 属性 不是 public
,正确的方法是子 class 类型并在构造函数中设置它(您可以在普通的 Panel
上使用反射对象,但这很丑陋且不明显)。在上面,Form1
class 已经是我控制的子class,所以在那里设置 属性 比创建一个新的自定义控件更容易为了这个目的。
关于代码的其他几点:
- 您可能要考虑使用
MouseClick
事件而不是 MouseDown
进行鼠标交互。这种行为可能更符合用户的期望;两个主要区别是,事件发生在鼠标弹起而不是鼠标按下时,如果用户在释放鼠标按钮之前拖离控件则根本不会发生(即为用户提供一种改变他们的方式的方法)注意并避免点击)。
- 我注意到你的随机游走在我看来是一个错误。您生成的值介于 -4 和 +5 之间,这当然会导致 "fly" 到达圆圈的右下角。我更改了您的随机化代码,以便它生成从 -4 到 4 的值,从而在所有方向上均匀分布。我在上面的代码示例中通过向
Next()
方法重载提供正确的值并从计算 中删除不需要的 - 5
来修复此问题
- 最重要的是,您的随机化代码有一个严重但常见的错误:每次您需要一个新值时,您都创建了一个新的
Random
对象。这会产生 非随机 结果,因为默认情况下随机数生成器使用系统时钟作为种子。在最坏的情况下,代码选择值的速度如此之快,以至于每个新的 Random
对象都被相同地初始化,因此 returns 相同的值;即使在最好的情况下,代码在随机值之间有延迟,返回值仍然与时钟相关,而不是真正的随机分布。我在上面的代码示例中通过将 Random
对象移动到带有初始值设定项的私有字段来修复此问题,以便生成良好的随机值序列(技术上,"pseudo-random")。
我正在尝试用 C# 创建一个简单的项目,但由于我是新手,遇到了很多麻烦。 我想做的是模拟一只苍蝇在一个圆圈内的运动。 它应该与鼠标点击一起使用 - 第一次点击,你选择了一个圆的中心。第二次单击,您选择了圆的半径并绘制了它。第三次单击(在圆圈内)时,您应该画一只苍蝇(现在让它成为一个小的、填充的椭圆),它应该立即开始四处移动。
我已经尝试了一个多星期,但几乎没有任何结果。
我正在使用面板作为 canvas。
这是我目前的情况:
private int start_x = 0;
private int start_y = 0;
private int end_x = 0;
private int end_y = 0;
private int fly_x = 0;
private int fly_y = 0;
private int clicks = 0;
private int distance = 0;
public Form1()
{
InitializeComponent();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
clicks++;
if (clicks == 1)
{
start_x = e.X;
start_y = e.Y;
}
else if (clicks == 2)
{
end_x = e.X;
end_y = e.Y;
}
else if (clicks == 3)
{
fly_x = e.X;
fly_y = e.Y;
}
else if (clicks == 4)
{
}
this.panel1.Refresh();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Random r = new Random();
Thread t = new Thread(new ThreadStart(Fly));
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
t.Start();
}
else if (clicks == 4)
{
t.Abort();
clicks = 0;
}
}
private int Distance(int a_x, int a_y, int b_x, int b_y)
{
int a, b;
a = a_x - b_x;
b = a_y - b_y;
return (Convert.ToInt32(Math.Sqrt(a * a + b * b)));
}
private void Fly()
{
Random r = new Random();
Graphics e = CreateGraphics();
distance = Distance(start_x, start_y, end_x, end_y);
while (clicks < 4)
{
e.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
fly_x = fly_x - 5 + r.Next(1, 11);
fly_y = fly_y - 5 + r.Next(1, 11);
Invalidate();
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
}
}
但是,显然,它根本不起作用。我可以画圆圈,但是一旦我再次点击面板,圆圈就会消失。你能指出我正确的方向吗?我的意思是,我做错了什么? 我试着在不使用线的情况下做到这一点,我什至画了苍蝇,但在那一点上,整个事情都冻结了。 编辑: 我删除了线程并放置了一个计时器:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Random r = new Random();
this.DoubleBuffered = true;
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
}
else if (clicks == 4)
{
clicks = 0;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
Random r = new Random();
fly_x = fly_x - 5 + r.Next(1, 11);
fly_y = fly_y - 5 + r.Next(1, 11);
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
panel1.Invalidate();
}
现在它可以工作了,但是图像仍然闪烁很多,即使我使用了双缓冲。我从我的同事那里得到了一个解决方案,不知何故,他并没有以任何方式闪烁。除了双缓冲区,还有其他方法可以解决这个问题吗?
在 Winforms 中避免闪烁并不难,但确实需要坚持。 Winforms 没有任何类型的合成引擎 — 它是非托管 Windows user32.dll API 之上的薄层,window 中的每个控件本身就是 window 是单独绘制的——因此每个可能闪烁的控件都需要进行双缓冲。
这是您的代码的一个版本,它不会闪烁:
public partial class Form1 : Form
{
private readonly Random r = new Random();
private int start_x = 0;
private int start_y = 0;
private int end_x = 0;
private int end_y = 0;
private int fly_x = 0;
private int fly_y = 0;
private int clicks = 0;
private int distance = 0;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
label1.Text = "clicks: 0";
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
clicks++;
if (clicks == 1)
{
start_x = e.X;
start_y = e.Y;
}
else if (clicks == 2)
{
end_x = e.X;
end_y = e.Y;
}
else if (clicks == 3)
{
fly_x = e.X;
fly_y = e.Y;
timer1.Start();
}
else if (clicks == 4)
{
timer1.Stop();
clicks = 0;
}
label1.Text = "clicks: " + clicks;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (clicks == 1)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
}
else if (clicks == 2)
{
distance = Distance(start_x, start_y, end_x, end_y);
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
}
else if (clicks == 3)
{
e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4);
e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);
e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6);
}
}
private int Distance(int a_x, int a_y, int b_x, int b_y)
{
int a, b;
a = a_x - b_x;
b = a_y - b_y;
return (Convert.ToInt32(Math.Sqrt(a * a + b * b)));
}
private void timer1_Tick(object sender, EventArgs e)
{
fly_x = fly_x + r.Next(-4, 5);
fly_y = fly_y + r.Next(-4, 5);
if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1)
{
if (fly_x < start_x)
{
fly_x = fly_x + 5;
}
else
{
fly_x = fly_x - 5;
}
if (fly_y < start_y)
{
fly_y = fly_y + 5;
}
else
{
fly_y = fly_y - 5;
}
}
Invalidate();
}
}
请注意,我没有在此处绘制到 Panel
对象,而只是使用了 Form1
对象本身。您可以将相同的基本技术应用于 Panel
或其他自定义控件来实现相同的基本结果,但这意味着创建您自己的 Panel
sub-class(或常规自定义控件,继承 Control
) 并将上述所有代码放入其中而不是表单中。 IE。您仍将在 OnMouseDown()
和 OnPaint()
方法上使用 override
而不是附加事件处理程序;您仍然可以使用设计器将 Timer
对象拖放到您的自定义 class.
关键是实际绘制的那个控件需要把DoubleBuffered
属性设为true
(默认是false
).由于 属性 不是 public
,正确的方法是子 class 类型并在构造函数中设置它(您可以在普通的 Panel
上使用反射对象,但这很丑陋且不明显)。在上面,Form1
class 已经是我控制的子class,所以在那里设置 属性 比创建一个新的自定义控件更容易为了这个目的。
关于代码的其他几点:
- 您可能要考虑使用
MouseClick
事件而不是MouseDown
进行鼠标交互。这种行为可能更符合用户的期望;两个主要区别是,事件发生在鼠标弹起而不是鼠标按下时,如果用户在释放鼠标按钮之前拖离控件则根本不会发生(即为用户提供一种改变他们的方式的方法)注意并避免点击)。 - 我注意到你的随机游走在我看来是一个错误。您生成的值介于 -4 和 +5 之间,这当然会导致 "fly" 到达圆圈的右下角。我更改了您的随机化代码,以便它生成从 -4 到 4 的值,从而在所有方向上均匀分布。我在上面的代码示例中通过向
Next()
方法重载提供正确的值并从计算 中删除不需要的 - 最重要的是,您的随机化代码有一个严重但常见的错误:每次您需要一个新值时,您都创建了一个新的
Random
对象。这会产生 非随机 结果,因为默认情况下随机数生成器使用系统时钟作为种子。在最坏的情况下,代码选择值的速度如此之快,以至于每个新的Random
对象都被相同地初始化,因此 returns 相同的值;即使在最好的情况下,代码在随机值之间有延迟,返回值仍然与时钟相关,而不是真正的随机分布。我在上面的代码示例中通过将Random
对象移动到带有初始值设定项的私有字段来修复此问题,以便生成良好的随机值序列(技术上,"pseudo-random")。
- 5
来修复此问题