在C#中的指定圆圈内移动

时间:2015-04-20 13:07:01

标签: c# drawing

我正在尝试在C#中创建一个简单的项目,但是,由于我有点新鲜,我遇到了很多麻烦。 我想要做的是模拟一个圆圈内苍蝇的运动。 它应该与鼠标点击一起工作 - 首先点击,你选择了圆心。第二次单击,您选择圆的半径并绘制它。在第三次点击(在圆圈内),你应该绘制一个苍蝇(现在让它变成一个小的,填充的椭圆),它应该立即开始移动。

我一直试图让这项工作超过一个星期,但几乎没有任何结果。

我正在使用面板作为画布。

这是我到目前为止所做的:

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

现在它可以正常工作,但是,即使我使用双缓冲,图像也会闪烁很多。我有一个同事的解决方案,不知怎的,他不会以任何方式闪烁。有没有其他方法来纠正这个比双缓冲区?

1 个答案:

答案 0 :(得分:1)

避免Winforms中的闪烁并不难,但确实需要持久性。 Winforms没有任何类型的合成引擎 - 它是非托管Windows user32.dll API之上的薄层,窗口中的每个控件本身都是一个单独绘制的窗口 - 因此每个控件可能闪烁的东西需要双重缓冲。

以下是闪烁的代码版本:

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子类(或常规自定义控件,继承{{1将所有上述代码放入其中而不是在表单中。即您仍然会在Controloverride方法上使用OnMouseDown(),而不是附加事件处理程序;您仍然可以使用Designer将OnPaint()对象拖放到自定义类中。

关键是实际绘图所在的控件需要将Timer属性设置为DoubleBuffered(默认情况下为true)。由于属性不是false,所以正确的方法是对类型进行子类化并在构造函数中设置它(你可以在普通的public对象上使用反射,但这很丑陋并且不明显)。在上面,Panel类已经是我可以控制的子类,因此在那里设置属性比为此目的创建新的自定义控件更容易。

关于代码的其他几点:

  • 您可能需要考虑使用Form1事件代替MouseClick进行鼠标互动。这种行为的方式可能更符合用户的期望;两个主要区别是,事件发生在鼠标上升而不是鼠标按下,如果用户在释放鼠标按钮之前拖离控件,它根本不会发生(即向用户提供一种方式改变主意,避免点击)。
  • 我注意到你的随机游走在我看来是一个错误。您生成的值介于-4和+5之间,这当然会导致&#34; fly&#34;一路走到圆圈的右下角。我更改了您的随机化代码,以便生成从-4到4的值,从而在所有方向上均匀分布。我在上面的代码示例中解决了这个问题,只是为MouseDown方法重载提供了正确的值,并从计算中删除了不需要的Next()
  • 最重要的是,您的随机化代码中有一个严重但常见的错误:每次您想要一个新值时,您都创建了一个新的- 5对象。这会产生非随机结果,因为默认情况下,随机数发生器以系统时钟播种。在最坏的情况下,代码选择值如此之快以至于每个新的Random对象都被相同地初始化,因此返回相同的值;即使在最佳情况下,代码在随机值之间延迟,返回值仍然与时钟相关,而不是真正的随机分布。我在上面的代码示例中通过将Random对象移动到具有初始化程序的私有字段来修复此问题,以便生成良好的随机值序列(技术上,&#34;伪随机&#34;)。