Reputation: 113
How to draw a rectangle that could move to the edge of the PictureBox and when it reached the edge, it would unfold. And there could be several such rectangles
Here is the code of the class with the Draw function (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):
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):
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:
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
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 :(
Upvotes: 1
Views: 208
Reputation: 18013
You didn't post fish.cs
so without testing I would suggest the following changes:
PictureBox
, it just complicates things if you perform custom animations this way. 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.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.protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
aquarium.Paint(e.Graphics); // instead of your Init() method
}
bitmap
, graphics
and aquarium_form
fields in your Aquarium
class. Instead, you can pass the form's Graphics
to a Paint
method:// 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[])
}
PikeFlock
is just IEnumerable
, I would change it to IEnumerable<Pike>
to make it strongly typed.PikeFlock.GetEnumerator
should yield Pike
instances instead of int arrays: yield return current
instead of current.Data
Pike
has now int[]
as coordinates. I would change it to 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;
}
Timer
component to the form from the Toolbox.// 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();
}
Upvotes: 2