简体   繁体   English

如何绘制动画矩形?

[英]How to draw an animated rectangle?

How to draw a rectangle that could move to the edge of the PictureBox and when it reached the edge, it would unfold.如何绘制一个可以移动到 PictureBox 边缘的矩形,当它到达边缘时,它会展开。 And there could be several such rectangles可能有几个这样的矩形

Here is the code of the class with the Draw function (Pike.cs):下面是带有 Draw 函数的类的代码 (Pike.cs):

using System.Drawing;

namespace course_project
{
    internal class Pike : Fish
    {
        private Point coordinates;
        private Size size = new Size(40, 40);
        private Size speed;

        public Pike(Point data, AquariumForm aquariumForm) : base(Color.Green, aquariumForm)
        {
            Data = data;

            speed = new Size();
        }

        public Pike Next { get; set; }

        public override void Draw(Graphics graphics)
        {
            graphics.FillEllipse(brush, coordinates.X, coordinates.Y, size.Width, size.Height);
        }

        public void UpdateLocation(Rectangle bounds)
        {
            if (!bounds.Contains(coordinates + speed))
            {
                if (coordinates.X + speed.Width < bounds.Left || coordinates.X + speed.Width > bounds.Right - size.Width)
                {
                    speed.Width *= -1;
                }

                if (coordinates.Y + speed.Height < bounds.Top || coordinates.Y + speed.Height > bounds.Bottom - size.Height)
                {
                    speed.Height *= -1;
                }
            }

            coordinates += speed;
        }
    }
}

Linked List (PikeFlock.cs):链表(PikeFlock.cs):

using System.Collections;
using System.Collections.Generic;
using System.Drawing;

namespace course_project
{
    internal class PikeFlock : IEnumerable<Pike>
    {
        Pike head;
        Pike tail;
        int count;

        public void Add(Point data, AquariumForm aquariumForm)
        {
            Pike pike = new Pike(data, aquariumForm);

            if (head == null)
                head = pike;
            else
                tail.Next = pike;

            tail = pike;

            count++;
        }

        public bool Remove(Point data)
        {
            Pike current = head;
            Pike previous = null;

            while (current != null)
            {
                if (current.Data.Equals(data))
                {
                    if (previous != null)
                    {
                        previous.Next = current.Next;

                        if (current.Next == null)
                        {
                            tail = previous;
                        }
                    }
                    else
                    {
                        head = head.Next;

                        if (head == null)
                            tail = null;
                    }

                    count--;
                    return true;
                }

                previous = current;
                current = current.Next;
            }

            return false;
        }

        public int Count { get { return count; } }

        public bool IsEmpty { get { return count == 0; } }

        public void Clear()
        {
            head = null;
            tail = null;
            count = 0;
        }

        public bool Contains(Point data)
        {
            Pike current = head;

            while (current != null)
            {
                if (current.Data.Equals(data))
                    return true;

                current = current.Next;
            }

            return false;
        }

        public void AppendFirst(Point data, AquariumForm aquariumForm)
        {
            Pike pike = new Pike(data, aquariumForm)
            {
                Next = head
            };

            head = pike;

            if (count == 0)
            {
                tail = head;
            }

            count++;
        }

        public IEnumerator<Pike> GetEnumerator()
        {
            Pike current = head;

            while (current != null)
            {
                yield return current;
                current = current.Next;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)this).GetEnumerator();
        }
    }
}

AquariumForm.cs (The form itself): AquariumForm.cs(表单本身):

using System;
using System.Drawing;
using System.Windows.Forms;

namespace course_project
{
    public partial class AquariumForm : Form
    {
        readonly Aquarium aquarium;

        public AquariumForm()
        {
            InitializeComponent();

            DoubleBuffered = true;

            aquarium = new Aquarium(ClientRectangle);
            aquarium_timer.Interval = 100;
            aquarium_timer.Tick += Timer_Tick;
            aquarium_timer.Enabled = true;
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            foreach (Pike pike in aquarium.pikeFlock)
            {
                pike.UpdateLocation(ClientRectangle);
            }

            foreach (Carp carp in aquarium.carpFlock)
            {
                carp.UpdateLocation(ClientRectangle);
            }

            Invalidate();
        }

        private void Add_carp_button_Click(object sender, EventArgs e)
        {
            aquarium.carpFlock.Add(new Point(), this);
        }

        private void Add_pike_button_Click(object sender, EventArgs e)
        {
            aquarium.pikeFlock.Add(new Point(), this);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            aquarium.Paint(e.Graphics);
        }
    }
}

Aquarium.cs:水族馆.cs:

using System.Drawing;

namespace course_project
{
    internal class Aquarium
    {
        readonly public PikeFlock pikeFlock;
        readonly public CarpFlock carpFlock;
        readonly Color waterColor;

        public Aquarium(Rectangle clientRectangle)
        {
            waterColor = Color.LightSkyBlue;
            pikeFlock = new PikeFlock();
            carpFlock = new CarpFlock();
        }

        public void Paint(Graphics graphics)
        {
            graphics.Clear(waterColor);

            foreach (Pike pike in pikeFlock)
                pike.Draw(graphics);

            foreach (Carp carp in carpFlock)
                carp.Draw(graphics);
        }
    }
}

Fish.cs鱼.cs

using System.Drawing;

namespace course_project
{
    internal class Fish
    {
        private protected Brush brush;
        private protected AquariumForm aquarium_form;

        public Fish(Color color, AquariumForm aquariumForm)
        {
            aquarium_form = aquariumForm;

            brush = new SolidBrush(color);
        }

        public virtual void Draw(Graphics graphics) { }

        public Point Data { get; set; }
    }
}

Confused just at the point where you need to animate it all.只是在您需要为所有动画制作动画时感到困惑。 By clicking the button everything is created as it should be, but animation does not work, even I do not know how :(通过单击按钮,一切都按原样创建,但动画不起作用,即使我不知道如何:(

You didn't post fish.cs so without testing I would suggest the following changes:您没有发布fish.cs ,因此未经测试我建议进行以下更改:

  • Do not use PictureBox , it just complicates things if you perform custom animations this way.不要使用PictureBox ,如果您以这种方式执行自定义动画,它只会使事情复杂化。 So instead of extracting the Graphics from its assigned Image property, which is backed by a Bitmap that has the same size as your form (btw. what happens if you resize your form?) simply just use the Graphics that is already there when your form is redrawn.因此,不要从其分配的Image属性中提取Graphics ,该属性由与表单大小相同的Bitmap支持(顺便说一句。如果调整表单大小会发生什么?)只需使用表单时已经存在的Graphics被重绘。
  • Unlike PictureBox , a Form does not use double buffering by default so you might want to set DoubleBuffered = true in the form constructor to avoid flickering.PictureBox不同, Form默认情况下不使用双缓冲,因此您可能希望在表单构造函数中设置DoubleBuffered = true以避免闪烁。
  • Override OnPaint in the form and initiate the whole repaint session from there:覆盖表单中的 OnPaint 并从那里启动整个重绘会话:
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    aquarium.Paint(e.Graphics); // instead of your Init() method
}
  • It means you don't need bitmap , graphics and aquarium_form fields in your Aquarium class.这意味着您的Aquarium类中不需要bitmapgraphicsaquarium_form字段。 Instead, you can pass the form's Graphics to a Paint method:相反,您可以将表单的Graphics传递给Paint方法:
// in Aquarium.cs:
public void Paint(Graphics graphics)
{
    // Please note that graphics can have any size now so it works even if you
    // resize the form. Please also note that I treat pikeFlock as a Pike
    // enumeration instead of just coordinates
    g.Clear(waterColor);
    foreach (Pike pike in pikeFlock) // instead of int[] items
        pike.Draw(graphics); // instead of Draw(int[])
}
  • Your PikeFlock is just IEnumerable , I would change it to IEnumerable<Pike> to make it strongly typed.您的PikeFlock只是IEnumerable ,我会将其更改为IEnumerable<Pike>以使其具有强类型。
  • It also means that PikeFlock.GetEnumerator should yield Pike instances instead of int arrays: yield return current instead of current.Data这也意味着PikeFlock.GetEnumerator应该产生Pike实例而不是 int 数组: yield return current而不是current.Data
  • Pike has now int[] as coordinates. Pike现在有int[]作为坐标。 I would change it to Point .我会将其更改为Point And do not pass new random coordinates in every drawing iteration because every fish will just randomly 'teleport' here and there.并且不要在每次绘图迭代中传递新的随机坐标,因为每条鱼都会在这里和那里随机“传送”。 Instead, maintain the current horizontal and vertical speed.相反,保持当前的水平和垂直速度。
// in Pike.cs:
private Point coordinates;
private Size size = new Size(40, 40);

// speed is declared as Size because there is an operator overload for Point + Size
// Do not forget to initialize speed in constructor
private Size speed; 

public override void Draw(Graphics graphics)
{
    // no Refresh is needed because graphics comes from the form's OnPaint now
    graphics.FillEllipse(brush, coordinates.X, coordinates.Y, size.Width, size.Height);
}

public void UpdateLocation(Rectangle bounds)
{
    // if a fish would go out of bounds invert its vertical or horizontal speed
    // TODO: if you shrink the form an excluded fish will never come back
    // because its direction will oscillate until you enlarge the form again.
    if (!bounds.Contains(coordinates + speed))
    {
        if (coordinates.X + speed.Width < bounds.Left
            || coordinates.X + speed.Width > bounds.Right - size.Width)
        {
            speed.Width *= -1;
        }
        if (coordinates.Y + speed.Height < bounds.Top
            || coordinates.Y + speed.Height > bounds.Bottom - size.Height)
        {
            speed.Height *= -1;
        }
    }

    coordinates += speed;
}


  • Now the only thing is missing is the animation itself.现在唯一缺少的是动画本身。 You can add a Timer component to the form from the Toolbox.您可以从 Toolbox 将Timer组件添加到表单中。
// form:
public AquariumForm()
{
    InitializeComponent();
    DoubleBuffered = true;

    // No need to pass the Form anymore. Pass the bounds instead to
    // generate the initial coordinates within the correct range.
    // No Random field is needed here, use one in Aquarium constructor only.
    aquarium = new Aquarium(ClientRectangle);
    timer.Interval = 100;
    timer.Tick += Timer_Tick;
    timer.Enabled = true;
}

private void Timer_Tick(object sender, EventArgs e)
{
    // Updating coordinates of all fish. We just pass the current bounds.
    foreach (Pike pike in aquarium.pikeFlock)
        pike.UpdateLocation(ClientRectangle);

    // Invalidating the form's graphics so a repaint will be automatically
    // called after processing the pending events.
    Invalidate();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM