C# window forms - moving two balls on a pictureobx - one disappears

72 Views Asked by At

I have two balls (ellipses) in a List of balls which are drawn on a PictureBox.

The balls should be moving around, i.e. I change the position and then call Refresh().

The result is that only the second ball is shown. The first is never shown.

I have searched for a solution but not been able to find anything which helps.

With one ball everything works fine. But the Refresh() seems to "delete" the first ball and only "keeps" the second one.

I would be thankful if anyone could point me in the right direction here (have been searching but not found anything useful).

Thanks

Added some information about the code:

List of balls:

private List<Ball> balls = new List<Ball>();

After adding the balls to a list I call the paint method with

foreach(Ball b in balls)
{
    b.paintBall(e, pictureBox1);
}

The paint method:

public void paintBall(PaintEventArgs e, PictureBox myPictureBox)
{
    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
  
    e.Graphics.Clear(myPictureBox.BackColor);
    e.Graphics.FillEllipse(farbe, ballPosX, ballPosY, ballWidth, ballHeight);

    e.Graphics.DrawEllipse(Pens.Black, ballPosX, ballPosY, ballWidth, ballWidth);
}

fyi - "farbe" is declared as Brush (to be able to set a color).

Then updating the position (by using a timer), calling the move-Method, after which I have the Refresh() method

foreach (Ball b in balls)
{
    b.moveBall(pictureBox1);
}

pictureBox1.Refresh();

The moveBall method:

public void moveBall(PictureBox pictureBox)
{
    ballPosX += moveStepX;

    if (ballPosX < 0 || (ballPosX + ballWidth > pictureBox.ClientSize.Width))
        moveStepX = -moveStepX;

    ballPosY += moveStepY;

    if (ballPosY < 0 || (ballPosY + ballHeigth > pictureBox.ClientSize.Height))
        moveStepY = -moveStepY;
}
2

There are 2 best solutions below

0
Olivier Jacot-Descombes On

You are calling e.Graphics.Clear(myPictureBox.BackColor); for each ball. Therefore, the previous ones will be deleted.

Instead paint balls like this:

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    e.Graphics.Clear(pictureBox1.BackColor);
    foreach (Ball ball in balls) {
        using var farbe = new SolidBrush(ball.Color);
        e.Graphics.FillEllipse(farbe, ball.X, ball.Y, ball.Width, ball.Height);
        e.Graphics.DrawEllipse(Pens.Black, ball.X, ball.Y, ball.Width, ball.Height);
    }
}

And do this in the pictureBox1 Paint event handler. Do not call paint methods directly. pictureBox1.Refresh(); will do that for you. The advantage in doing so is that pictureBox1_Paint will be called automatically when necessary. E.g., when a window is minimized and then restored or when a window is being resized, etc.

You can also extract the ball painting in another method and then do:

private void PaintBall(Graphics g, Ball ball)
{
    using var farbe = new SolidBrush(ball.Color);
    g.FillEllipse(farbe, ball.X, ball.Y, ball.Width, ball.Height);
    g.DrawEllipse(Pens.Black, ball.X, ball.Y, ball.Width, ball.Height);
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    e.Graphics.Clear(pictureBox1.BackColor);
    foreach (Ball ball in balls) {
        PaintBall(e.Graphics, ball);
    }
}

It is even easier if you add a Paint method to the Ball class itself:

public void Paint(Graphics g)
{
    using var farbe = new SolidBrush(Color);
    g.FillEllipse(farbe, X, Y, Width, Height);
    g.DrawEllipse(Pens.Black, X, Y, Width, Height);
}

In pictureBox1_Paint you can write:

e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.Clear(pictureBox1.BackColor);
foreach (Ball ball in balls) {
    ball.Paint(e.Graphics);
}

There are advantages in doing so:

  • This method can access the ball's properties directly with X, Y etc.
  • Now it is easy to add other shapes, because each shape knows how to draw itself. You would create an abstract base class called Shape having all the common properties and an abstract Paint method. Derived classes like Ball or Square would then override Paint and provide their own implementation. Instead of having a List<Ball> you would have a List<Shape>.
0
IV. On

It seems likely that when you call e.Graphics.Clear(myPictureBox.BackColor) that you're deleting everything except for the current ball. Think of it as when you call pictureBox.Refresh it's giving you a blank canvas to draw on every time and do everything you need to do in the handler for the Paint event.

This shows the starting positions and the position after 5 clicks.

on load

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        pictureBox.Paint += (sender, e) =>
        {
            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            foreach (var ball in balls)
            {
                using (var brush = new SolidBrush(ball.Color))
                {
                    e.Graphics.FillEllipse(brush, ball.X, ball.Y, ball.Width, ball.Height);
                }
            }
        };
        buttonMove.Click += (sender, e) =>
        {
            balls[0].Move(10, 5);
            balls[1].Move(-5, -5);
            pictureBox.Refresh();
        };
    }
    List<Ball> balls = new List<Ball>
    {
        new Ball{Color = Color.Blue, X = 50, Y = 50, Height = 50, Width = 50 },
        new Ball{Color = Color.Green, X = 175, Y = 175, Height = 100, Width = 100 },
    };
}

Ball

In OOP, the paradigm is often to give the Ball the intelligence to move itself:

class Ball
{
    public Color Color { get; set; }
    public string? Name { get; set; }
    public int X { get; set; }
    public int Y { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public void Move(int x, int y) 
    {
        X += x;
        Y += y;
    }
}