0

I have been learning C# using the book "Programming in the Key of C#...", this book has been very good in helping me understand the language but only deals with Console programs. I am ready to move on to developing versions of my past coding projects as Windows form applications but one program in particular is causing me a lot of frustration. I developed a simple movie trivia program utilizing arrays to hold the questions, answer choices, and the correct answer. It worked by displaying on the console the questions, the possible answers and waited for the user to provide a response (basically A,B,C or D) by using Console.Readline() to assign the response.

Now I want to be able to have the user enter the answer by selecting 1 of 4 buttons (A through D). Based on my old code, I am unsure how I get the program to wait for the user to click one of the buttons. I assume i need to change the nature of the loops but I just cant figure out how. Any help would be much appreciated.

Here is a snippet of my Console code:

while (iAsked < 5)
    {   
        iLocation = rand.Next(0, astrQuestions.GetLength(0));

        if (list.Contains(iLocation))
            rand.Next(0, astrQuestions.GetLength(0));
        else
        {
            iAsked++;
            list.Add(iLocation);
            Console.WriteLine("Question {0}", iAsked);
            Console.WriteLine("------------");

            Console.WriteLine(astrQuestions[(iLocation)]);
            Console.WriteLine(astrChoices[(iLocation)]);
            Console.Write("Answer:");

            iResponse = Console.ReadLine();


            if (iResponse == astrAnswers[(iLocation)])
            {
                Console.WriteLine("Correct\n");
                iPoints += 5;
                iCorrect++;
            }
            else
            {
                Console.WriteLine("Incorrect\n");
            }
        }
  • 6
    You can use `Events` for that. Use radiobuttons for selecting the right answer and a seperate button to "send" the answer (wrong tap). – jAC Jan 23 '15 at 16:18

1 Answers1

0

Moving from a prompting-centric environment like a console program to an event-driven environment like Winforms, yes…that definitely will require at least some change in "the nature of the loops". :)

That said, the latest version of C# offers an async/await-based approach that can minimize some of the culture-shock that might come from moving from console to GUI. Writing and using async method is itself non-trivial, but IMHO the simpler scenarios are not too hard to understand. More importantly, because it allows you to structure the code in a more directly-imperative way, similar to that which would be used in a console program, it's very much worth learning this along with Winforms generally.

In your particular scenario, you have two separate things you'll need to deal with: prompting the user, and receiving the user's input.

Because of the way an event-driven system works, you need to separate these tasks. But .NET has a class, TaskCompletionSource, which we can use to keep the two glued together, even though they wind up in different places.

First, what happens when the user starts the process? Presumably, you'll have a form, where on that form is a button (or possible a menu item) which when clicked/selected, starts the whole thing. That might look something like this:

private TaskCompletionSource<bool> _completionSource;

private async void button1_Click(object sender, EventArgs e)
{
    int[] questionIndexes = ShuffleQuestions();

    for (int iAsked = 0; iAsked < 5; iAsked++)
    {
        textBoxQuestionNumber.Text = string.Format("Question {0}", iAsked);
        textBoxQuestion.Text = astrQuestions[questionIndexes[iAsked]];
        textBoxChoices.Text = astrChoices[questionIndexes[iAsked]];

        _completionSource =
            new TaskCompletionSource<bool>(astrAnswers[questionIndexes[iAsked]]);
        button2.Enabled = true;

        bool result = await _completionSource.Task;

        MessageBox.Show(result ? "Correct" : "Incorrect");
        if (result)
        {
            iPoints += 5;
            iCorrect++;
        }
        button2.Enabled = false;
        _completionSource = null;
    }
}

private void button2_Click(object sender, EventArgs e)
{
    if (_completionSource != null)
    {
        _completionSource.SetResult(
            textBoxUserAnswer.Text == (string)_completionsSource.Task.AsyncState);
    }
}

(I have changed your question-selection logic above to something more efficient, by assuming that you have a ShuffleQuestions() method. See Is using Random and OrderBy a good shuffle algorithm? for details on how to implement that).

What the above code does is, in response to the user clicking the button1 button (which presumably has text like "Start" or something), executes a loop that is very similar to what you had in your console program. The two main differences are:

  • Where in your console program, you use Console.WriteLine() to display text to the user, here I have shown the use of TextBox controls in your form which are used to display the same text.
  • What in your console program, you use Console.ReadLine() to receive input from the user, this loop creates a TaskCompletionSource object for a completely different method to use. That method, which is executed with your button2 button (which presumably has text like "Check Answer" or something) will read the text entered in a text box by the user (here, I've given it the name textBoxUserAnswer), compare it to the correct answer for the question (which has been provided to this method by the other method via the AsyncState property of the Task created by the TaskCompletionSource object I created), and set the Task's result to true or false, depending on whether the user got the answer correct or not.

The tricky part above is that "under the hood", that first method actually returns as soon as it is done filling in the text for the first question and reaches the await statement in the loop. The compiler rewrites the entire method to facilitate this.

When button2 is pushed, and sets the result of the Task, the framework then knows to resume executing the first method where it left off at the await statement, continuing your loop.

This sequence continues until the user has answered all of the question.

Some final notes about the UI:

  • I have used TextBox's everywhere for user input and output. Of course, there are other ways to display text. Also, the default state for a TextBox is a single-line, read/write text. But for displaying to the user, you may find that setting the ReadOnly property of the TextBox to true is better (i.e. to prevent the user from accidentally changing the text), and/or that you prefer setting the Multiline property to true (i.e. so that more than one line of text is displayed).
  • The above also assumes that the initial state for the button2 button's Enabled property is false. I.e. that button can't be clicked until the first method above explicitly enables the button at the appropriate time.
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thank you very much I used a lot of your recommendations to refine my code. I also appreciated the usage of the For loop, which I think works a lot better and makes the code easier to follow. – Adrian Jordan Feb 06 '15 at 15:24