1

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;
  }
}
Binarynova
  • 35
  • 3
  • 1
    The real issue was that I had already added a Timer in the Form Designer, so I didn't need to add the line: `this.timer1.Tick += new System.EventHandler(this.timer1_Tick);` that I saw on Microsoft's example. Removing that did the trick. – Binarynova Aug 20 '20 at 05:56

3 Answers3

1

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.

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Unfortunately none of these seem to have any effect when placed after I swap the image: `playerHandPictureBoxes[counter].Invalidate();` `playerHandPictureBoxes[counter].Update();` `playerHandPictureBoxes[counter].Refresh();` And PictureBox.Image has none of those as a method. – Binarynova Aug 20 '20 at 04:01
  • Even at 2000ms it still produces the effect of revealing two cards at a time (and then the final fifth card). And the same "two-at-a-time" effect can be seen when using breakpoints and stepping through the code. – Binarynova Aug 20 '20 at 04:21
  • I think it's a race condition or a timing thing with not enough images to make animation, but I can't see it so I'm just guessing here. This is similar to animating a gif image and maybe some of my tips there will help you to compare and diagnose the issue: https://stackoverflow.com/a/13486374/495455 – Jeremy Thompson Aug 20 '20 at 07:07
1

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;
        //}
    }
}
Biju Kalanjoor
  • 532
  • 1
  • 6
  • 12
  • That looks like basically the same idea. Weird that it works for you, updating one image at a time. What could be causing this issue on my end? – Binarynova Aug 20 '20 at 04:56
  • 1
    I edited my code check InitializeTimer() method. this will fix your isse. – Biju Kalanjoor Aug 20 '20 at 05:33
  • 1
    This fixed the problem, but my brother and I realized that the reason it was happening at all was that I had a Timer object I had added using the Form Designer. Removing the following code fixed the problem as well: `this.timer1.Tick += new System.EventHandler(this.timer1_Tick);` Thank you for the help! – Binarynova Aug 20 '20 at 05:56
  • Hmm...great . But if you didn't detach the Tick event in the InitializeTimer() method, issue will occur again in case of call the InitializeTimer() again. Happy coding my bro.. – Biju Kalanjoor Aug 20 '20 at 06:03
0

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.

ndogac
  • 1,185
  • 6
  • 15
  • No luck. .Image doesn't have a definition for Update(), but I tried `playerHandPictureBoxes[counter].Update()` instead and also tried using `.Refresh()` and neither one forces an update. Thanks for taking a look! – Binarynova Aug 20 '20 at 03:19