Binarynova
Binarynova

Reputation: 35

WinForms (C#) only updating PictureBoxes on every other loop through the Timer

I'm working on a video poker style arcade game using WinForms in C#. When the player clicks the "Deal" button, the 5 PictureBoxes on the form switch from a face down card image to face up card images. I have no trouble getting them to flip all at once, but I wanted there to be a slight delay between each card so they reveal from left-to-right.

I found an example from the official Microsoft .NET docs (second example on this page) which uses the Timer in such a way that it will only loop through a set number of times, perfect for me since I just want to flip the 5 cards.

When I use that example in my game though, something odd happens. The cards reveal in pairs of two from left-to-right instead of one at a time. When I set a breakpoint and step through, my counter is indeed incrementing by one, but the form only updates the images on every other pass.

Why is this happening? And what can I do to fix it?

Deal Button Click:

private void dealdraw_button1_Click(object sender, EventArgs e)
{
  // If we are dealing a new hand...
  if (dealPhase == "deal")
  {
    // Change game phase
    dealPhase = "draw";

    // Generate a new deck of cards
    deck = new Deck();

    // Shuffle the deck
    deck.Shuffle();

    // Deal five cards to the player
    playerHand = deck.DealHand();

    // Start timer loop
    InitializeTimer();

    // Enable "Hold" buttons
    for (int i = 0; i < 5; i++)
    {
      playerHoldButtons[i].Enabled = true;
    }
  }
}

InitializeTimer():

private void InitializeTimer()
{
  counter = 0;
  timer1.Interval = 50;
  timer1.Enabled = true;
  // Hook up timer's tick event handler.  
  this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
}

Timer1_Tick:

private void timer1_Tick(object sender, EventArgs e)
{
  if(counter >= 5)
  {
    // Exit loop code
    timer1.Enabled = false;
    counter = 0;
  }
  else
  {
    playerHandPictureBoxes[counter].Image = cardFaces_imageList1.Images[playerHand[counter].imageListIndex];
    counter = counter + 1;
  }
}

Upvotes: 1

Views: 280

Answers (3)

Biju Kalanjoor
Biju Kalanjoor

Reputation: 552

The issue in your InitializeTimer method. the cultprit is you are adding Tick event handler again and again in each click.

public partial class Form1 : Form
{
    PictureBox[] playerHandPictureBoxes = new PictureBox[5];
    int counter = 0;
    public Form1()
    {
        InitializeComponent();
        CreateCards();
    }
    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void CreateCards()
    {
        int pWidth = 50;
        int left = 10;
        for (int i = 0; i < playerHandPictureBoxes.Length; i++)
        {
            playerHandPictureBoxes[i] = new PictureBox
            {
                Top = 10,
                Left = left + (left * i) + (i * pWidth),
                BackColor = Color.Red,
                Width = pWidth,
                Height = 50,
                BackgroundImage = imageList1.Images[0],
                BackgroundImageLayout = ImageLayout.Stretch
            };
        }

        this.Controls.AddRange(playerHandPictureBoxes);
    }

    private void InitializeTimer()
    {
        counter = 0;
        timer1.Interval = 500;
        timer1.Enabled = true;
        // Detach any previouly added event handlers
        this.timer1.Tick -= Timer1_Tick;
        // Hook up timer's tick event handler.  
        this.timer1.Tick += Timer1_Tick;

    }

    private void Timer1_Tick(object sender, EventArgs e)
    {
        if (counter >= 5)
        {
            // Exit loop code
            timer1.Enabled = false;
            counter = 0;
        }
        else
        {
            playerHandPictureBoxes[counter].BackgroundImage = imageList1.Images[1];
            counter += 1;
        }
    }

    private void btnDeal_Click(object sender, EventArgs e)
    {
        InitializeTimer();

        // Enable "Hold" buttons
        //for (int i = 0; i < 5; i++)
        //{
        //    playerHoldButtons[i].Enabled = true;
        //}
    }
}

Upvotes: 1

Jeremy Thompson
Jeremy Thompson

Reputation: 65584

I think you want the Invalidate method causes a Repaint event to occur:

playerHandPictureBoxes[counter].Image = cardFaces_imageList1.Images[playerHand[counter].imageListIndex];
playerHandPictureBoxes[counter].Invalidate();

Ref: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.update?view=netcore-3.1#System_Windows_Forms_Control_Update

There are two ways to repaint a form and its contents:

  • You can use one of the overloads of the Invalidate method with the Update method.
  • You can call the Refresh method, which forces the control to redraw itself and all its children. This is equivalent to setting the Invalidate method to true and using it with Update.

The Invalidate method governs what gets painted or repainted. The Update method governs when the painting or repainting occurs. If you use the Invalidate and Update methods together rather than calling Refresh, what gets repainted depends on which overload of Invalidate you use. The Update method just forces the control to be painted immediately, but the Invalidate method governs what gets painted when you call the Update method.

You might want to either increase the Timers Interval or the counter, 250 ms is a bit quick/short to see an animation.

Upvotes: 1

ndogac
ndogac

Reputation: 1245

I wanted to add this as a comment but my reputation does not allow this.

You can try playerHandPictureBoxes[counter].Image.Update(); after setting the image. Haven't tested it though.

Upvotes: 0

Related Questions