Winforms how to wait for user to click a button?

Viewed 199

I am trying to design a Blackjack game where, at some point, I need to make a choice on which action to take:Hit,Stand,Split or Double-Down. I have a button for each of those actions and I have added all of their click event handlers to the same method in the form designer, like so (I don't really know the exact way of describing it, but I'm refering to the link between the event handler and the btnRound_Click method)

this.btnSplit.Click += new System.EventHandler(this.btnRound_Click);
this.btnDoubled.Click += new System.EventHandler(this.btnRound_Click);
this.btnHit.Click += new System.EventHandler(this.btnRound_Click);
this.btnStand.Click += new System.EventHandler(this.btnRound_Click);
//Inside the Form1 class

enum RoundPlay
        {
            hit,
            stand,
            doubled,
            split
        }
        RoundPlay playchoice = RoundPlay.none;

private void btnRound_Click(object sender, EventArgs e)
        {
            switch (((Button)sender).Text)
            {                
                case "Hit":
                    playchoice = RoundPlay.hit;
                    break;
                case "Stand":
                    playchoice = RoundPlay.stand;
                    break;
                case "Split":
                    playchoice = RoundPlay.split;
                    break;
                case "Double Down":
                    playchoice = RoundPlay.doubled;
                    break;
            }            
        }

Inside the main method I would have:

//enabling the buttons so that I can click on one of them

//place to wait for the user to click on one of the buttons
//this is where I tried using await operator 

switch (playchoice)
                {
                    case RoundPlay.hit:
                        //one action
                        break;
                    case RoundPlay.stand:
                        //other action
                        break;
                    case RoundPlay.doubled:
                        //other action
                        break;
                    case RoundPlay.split:
                        //other action
                        break;
                }

Now then, what is the best way to "wait" for user input? I've searched quite a bit but all I could find were solutions to problems with only one button or situations where it didn't matter if the main method changed (creating a method for each eventhandler). In this case, I want to stay in the same method, so that I can repeat rounds, meaning, I want to have this inside a Do...While() loop.

I've tried using ManualResetEvent and async/await, but I haven't got much far with any of those, so if anyone has got any recomendations, I would appreciate it! :)

On another less important matter, I just noticed I'm writting all game progress related code inside the Form1_Load method, how incorrect is this? Is there a better place to write this inside? Sorry if anything was unclear, this is my second post here Thanks

2 Answers

Winforms is a framework not a library. Frameworks usually have Inversion of Control (IoC) where they call your code instead of you controlling them.

Easiest change is to add your while loop body into a new method like

private void NextAction() 
{
  switch(playchoice) 
  {
    ...
  }
  playchoice = RoundPlay.none;
}

and then call this method at the end of the button even handlers

private void btnRound_Click(...) 
{
  switch (((Button)sender).Text)
  {
    ...
  }
  NextAction(); 
}

The method below creates a Task<Control> that completes when any of the supplied Controls is clicked:

public static Task<Control> OnClickAnyAsync(params Control[] controls)
{
    var tcs = new TaskCompletionSource<Control>();
    foreach (var control in controls) control.Click += OnClick;
    return tcs.Task;

    void OnClick(object sender, EventArgs e)
    {
        foreach (var control in controls) control.Click -= OnClick;
        tcs.SetResult((Control)sender);
    }
}

You could use this method to await for a click from any of the four buttons, and return a Task<RoundPlay> with the appropriate value depending on which button was clicked:

private async Task<RoundPlay> GetPlayChoiceAsync()
{
    var clickedButton = await OnClickAnyAsync(btnHit, btnStand, btnDoubled, btnSplit);
    if (clickedButton == btnHit) return RoundPlay.hit;
    if (clickedButton == btnStand) return RoundPlay.stand;
    if (clickedButton == btnDoubled) return RoundPlay.doubled;
    if (clickedButton == btnSplit) return RoundPlay.split;
    throw new NotImplementedException();
}

Finally you could use the GetPlayChoiceAsync method like this:

// Enabling the buttons so that I can click on one of them
// Wait the player to make a choice, without blocking the UI
RoundPlay playchoice = await GetPlayChoiceAsync();
// Do something with the playchoice
Related